Merge branch 'develop' into TC-782-Mobile-Troubleshoot-notifications
This commit is contained in:
commit
10d954ebf1
|
@ -96,16 +96,23 @@ commands:
|
|||
- ~/.pods
|
||||
- ios/Pods
|
||||
|
||||
fetch-supported-versions:
|
||||
description: "Fetch supported versions from Cloud"
|
||||
steps:
|
||||
- run:
|
||||
name: "Fetch supported versions from Cloud"
|
||||
command: sh ./scripts/fetch-supported-versions.sh
|
||||
- store_artifacts:
|
||||
path: ./app-supportedversions.json
|
||||
|
||||
android-build:
|
||||
description: "Build Android app"
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache: *restore-npm-cache-linux
|
||||
|
||||
- run: *install-npm-modules
|
||||
|
||||
- restore_cache: *restore-gradle-cache
|
||||
- fetch-supported-versions
|
||||
|
||||
- run:
|
||||
name: Configure Gradle
|
||||
|
@ -200,6 +207,7 @@ commands:
|
|||
- run: *install-npm-modules
|
||||
- run: *update-fastlane-ios
|
||||
- manage-pods
|
||||
- fetch-supported-versions
|
||||
- run:
|
||||
name: Set Google Services
|
||||
command: |
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
||||
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null]},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
||||
|
||||
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"signed": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0aW1lc3RhbXAiOiIyMDIzLTEwLTExVDE5OjI0OjEwLjc5OTkwNjgzWiIsIm1lc3NhZ2VzIjpbeyJyZW1haW5pbmdEYXlzIjozMCwidGl0bGUiOiJ2ZXJzaW9uX3Vuc3VwcG9ydGVkX3RpdGxlIiwic3VidGl0bGUiOiJ2ZXJzaW9uX3Vuc3VwcG9ydGVkX3N1YnRpdGxlIiwiZGVzY3JpcHRpb24iOiJ2ZXJzaW9uX3Vuc3VwcG9ydGVkX2JvZHkiLCJ0eXBlIjoiaW5mbyIsInBhcmFtcyI6e30sImxpbmsiOiIifV0sInZlcnNpb25zIjpbeyJ2ZXJzaW9uIjoiNi4zLjEiLCJzZWN1cml0eSI6ZmFsc2UsImluZm9VcmwiOiJodHRwczovL2dpdGh1Yi5jb20vUm9ja2V0Q2hhdC9Sb2NrZXQuQ2hhdC9yZWxlYXNlcy90YWcvNi4zLjEiLCJleHBpcmF0aW9uIjoiMjAyMy0xMi0yNlQwMDowMDowMFoifSx7InZlcnNpb24iOiI2LjMuMCIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjMuMCIsImV4cGlyYXRpb24iOiIyMDIzLTEyLTI2VDAwOjAwOjAwWiJ9LHsidmVyc2lvbiI6IjYuMy4yIiwic2VjdXJpdHkiOmZhbHNlLCJpbmZvVXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL1JvY2tldENoYXQvUm9ja2V0LkNoYXQvcmVsZWFzZXMvdGFnLzYuMy4yIiwiZXhwaXJhdGlvbiI6IjIwMjMtMTItMjZUMDA6MDA6MDBaIn0seyJ2ZXJzaW9uIjoiNi4zLjMiLCJzZWN1cml0eSI6ZmFsc2UsImluZm9VcmwiOiJodHRwczovL2dpdGh1Yi5jb20vUm9ja2V0Q2hhdC9Sb2NrZXQuQ2hhdC9yZWxlYXNlcy90YWcvNi4zLjMiLCJleHBpcmF0aW9uIjoiMjAyMy0xMi0yNlQwMDowMDowMFoifSx7InZlcnNpb24iOiI2LjMuNCIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjMuNCIsImV4cGlyYXRpb24iOiIyMDIzLTEyLTI2VDAwOjAwOjAwWiJ9LHsidmVyc2lvbiI6IjYuMy41Iiwic2VjdXJpdHkiOmZhbHNlLCJpbmZvVXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL1JvY2tldENoYXQvUm9ja2V0LkNoYXQvcmVsZWFzZXMvdGFnLzYuMy41IiwiZXhwaXJhdGlvbiI6IjIwMjMtMTItMjZUMDA6MDA6MDBaIn0seyJ2ZXJzaW9uIjoiNi4zLjYiLCJzZWN1cml0eSI6ZmFsc2UsImluZm9VcmwiOiJodHRwczovL2dpdGh1Yi5jb20vUm9ja2V0Q2hhdC9Sb2NrZXQuQ2hhdC9yZWxlYXNlcy90YWcvNi4zLjYiLCJleHBpcmF0aW9uIjoiMjAyMy0xMi0yNlQwMDowMDowMFoifSx7InZlcnNpb24iOiI2LjMuNyIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjMuNyIsImV4cGlyYXRpb24iOiIyMDIzLTEyLTI2VDAwOjAwOjAwWiJ9LHsidmVyc2lvbiI6IjYuMy44Iiwic2VjdXJpdHkiOmZhbHNlLCJpbmZvVXJsIjoiaHR0cHM6Ly9naXRodWIuY29tL1JvY2tldENoYXQvUm9ja2V0LkNoYXQvcmVsZWFzZXMvdGFnLzYuMy44IiwiZXhwaXJhdGlvbiI6IjIwMjMtMTItMjZUMDA6MDA6MDBaIn0seyJ2ZXJzaW9uIjoiNi40LjAiLCJzZWN1cml0eSI6ZmFsc2UsImluZm9VcmwiOiJodHRwczovL2dpdGh1Yi5jb20vUm9ja2V0Q2hhdC9Sb2NrZXQuQ2hhdC9yZWxlYXNlcy90YWcvNi40LjAiLCJleHBpcmF0aW9uIjoiMjAyNC0wMS0xMVQwMDowMDowMFoifSx7InZlcnNpb24iOiI2LjMuOSIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjMuOSIsImV4cGlyYXRpb24iOiIyMDIzLTEyLTI2VDAwOjAwOjAwWiJ9LHsidmVyc2lvbiI6IjYuMy4xMCIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjMuMTAiLCJleHBpcmF0aW9uIjoiMjAyMy0xMi0yNlQwMDowMDowMFoifSx7InZlcnNpb24iOiI2LjQuMSIsInNlY3VyaXR5IjpmYWxzZSwiaW5mb1VybCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9Sb2NrZXRDaGF0L1JvY2tldC5DaGF0L3JlbGVhc2VzL3RhZy82LjQuMSIsImV4cGlyYXRpb24iOiIyMDI0LTAyLTA5VDIxOjA3OjUyLjMyOVoifSx7InZlcnNpb24iOiI2LjUuMC1kZXZlbG9wIiwic2VjdXJpdHkiOmZhbHNlLCJpbmZvVXJsIjoiIiwiZXhwaXJhdGlvbiI6IjIwMjMtMTAtMThUMTk6MjQ6MTAuODE0NzMxMTg5WiJ9XSwiaTE4biI6eyJlbiI6eyJ2ZXJzaW9uX3Vuc3VwcG9ydGVkX2JvZHkiOiJBbiBhdXRvbWF0aWMgMzAtZGF5IHdhcm5pbmcgcGVyaW9kIGhhcyBiZWVuIGFwcGxpZWQgdG8gYWxsb3cgdGltZSBmb3IgYSB3b3Jrc3BhY2UgYWRtaW4gdG8gdXBkYXRlIHdvcmtzcGFjZSB0byBhIHN1cHBvcnRlZCBzb2Z0d2FyZSB2ZXJzaW9uLiIsInZlcnNpb25fdW5zdXBwb3J0ZWRfc3VidGl0bGUiOiJNb2JpbGUgYW5kIGRlc2t0b3AgYXBwIGFjY2VzcyB0byB7e2luc3RhbmNlX3dzX25hbWV9fSB3aWxsIGJlIGN1dCBvZmYgaW4ge3tyZW1haW5pbmdfZGF5c319IGRheXMiLCJ2ZXJzaW9uX3Vuc3VwcG9ydGVkX3RpdGxlIjoie3tpbnN0YW5jZV93c19uYW1lfX0gaXMgcnVubmluZyBhbiB1bnN1cHBvcnRlZCB2ZXJzaW9uIG9mIFJvY2tldC5DaGF0In19LCJlbmZvcmNlbWVudFN0YXJ0RGF0ZSI6IjIwMjMtMTAtMzFUMDA6MDA6MDBaIn0.F83D93FJN-dQjSv4c2NjANHJ_aOou2WPioYiu7LMymvwSr9PvK87_xGA5wB_b532mRDThfbwkYU0mqvfaHylTKcteYpJn2zy1-ZGHj5Ugtt4wxBMYezW07_H7K8DEskwuGQQ5jmmy1VEwzIVcM7GoGQLDT2eafkloHBXQLRqmanhb03110NGiNu_9xkMU5cmsW2tP1-kShug89L-E4tHXzDG7TXX4yF4H_W3RdmdW6b5FNdmoYFF68I4HYGNmcgOZDBsw2gkA3xUg3vFsbnkjl_oyyBZqINFZRu0TtVu4S5OORnrM-9zJp_C6GMiueFLBy3lwjNIcAUPrZA93uu7Yg",
|
||||
"timestamp": "2023-10-11T19:24:10.79990683Z",
|
||||
"messages": [
|
||||
{
|
||||
"remainingDays": 30,
|
||||
"title": "version_unsupported_title",
|
||||
"subtitle": "version_unsupported_subtitle",
|
||||
"description": "version_unsupported_body",
|
||||
"type": "info",
|
||||
"params": {},
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "6.3.1",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.1",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.0",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.0",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.2",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.2",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.3",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.3",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.4",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.4",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.5",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.5",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.6",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.6",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.7",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.7",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.8",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.8",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.4.0",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.4.0",
|
||||
"expiration": "2024-01-11T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.9",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.9",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.3.10",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.3.10",
|
||||
"expiration": "2023-12-26T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"version": "6.4.1",
|
||||
"security": false,
|
||||
"infoUrl": "https://github.com/RocketChat/Rocket.Chat/releases/tag/6.4.1",
|
||||
"expiration": "2024-02-09T21:07:52.329Z"
|
||||
},
|
||||
{ "version": "6.5.0-develop", "security": false, "infoUrl": "", "expiration": "2023-10-18T19:24:10.814731189Z" }
|
||||
],
|
||||
"i18n": {
|
||||
"en": {
|
||||
"version_unsupported_body": "An automatic 30-day warning period has been applied to allow time for a workspace admin to update workspace to a supported software version.",
|
||||
"version_unsupported_subtitle": "Mobile and desktop app access to {{instance_ws_name}} will be cut off in {{remaining_days}} days",
|
||||
"version_unsupported_title": "{{instance_ws_name}} is running an unsupported version of Rocket.Chat"
|
||||
}
|
||||
},
|
||||
"enforcementStartDate": "2023-10-31T00:00:00Z"
|
||||
}
|
|
@ -97,3 +97,4 @@ export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
|||
'SET_CALLING'
|
||||
]);
|
||||
export const TROUBLESHOOTING_NOTIFICATION = createRequestTypes('TROUBLESHOOTING_NOTIFICATION', ['REQUEST', 'SET']);
|
||||
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Action } from 'redux';
|
|||
|
||||
import { SERVER } from './actionsTypes';
|
||||
|
||||
interface ISelectServer extends Action {
|
||||
export interface ISelectServerAction extends Action {
|
||||
server: string;
|
||||
version?: string;
|
||||
fetchVersion: boolean;
|
||||
|
@ -12,9 +12,10 @@ interface ISelectServer extends Action {
|
|||
interface ISelectServerSuccess extends Action {
|
||||
server: string;
|
||||
version: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IServer extends Action {
|
||||
export interface IServerRequestAction extends Action {
|
||||
server: string;
|
||||
username: string | null;
|
||||
fromServerHistory: boolean;
|
||||
|
@ -24,13 +25,14 @@ interface IServerInit extends Action {
|
|||
previousServer: string;
|
||||
}
|
||||
|
||||
interface IServerFailure extends Action {
|
||||
err: any;
|
||||
}
|
||||
export type TActionServer = ISelectServerAction & ISelectServerSuccess & IServerRequestAction & IServerInit;
|
||||
|
||||
export type TActionServer = ISelectServer & ISelectServerSuccess & IServer & IServerInit & IServerFailure;
|
||||
|
||||
export function selectServerRequest(server: string, version?: string, fetchVersion = true, changeServer = false): ISelectServer {
|
||||
export function selectServerRequest(
|
||||
server: string,
|
||||
version?: string,
|
||||
fetchVersion = true,
|
||||
changeServer = false
|
||||
): ISelectServerAction {
|
||||
return {
|
||||
type: SERVER.SELECT_REQUEST,
|
||||
server,
|
||||
|
@ -40,11 +42,20 @@ export function selectServerRequest(server: string, version?: string, fetchVersi
|
|||
};
|
||||
}
|
||||
|
||||
export function selectServerSuccess(server: string, version: string): ISelectServerSuccess {
|
||||
export function selectServerSuccess({
|
||||
server,
|
||||
version,
|
||||
name
|
||||
}: {
|
||||
server: string;
|
||||
version: string;
|
||||
name: string;
|
||||
}): ISelectServerSuccess {
|
||||
return {
|
||||
type: SERVER.SELECT_SUCCESS,
|
||||
server,
|
||||
version
|
||||
version,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -54,7 +65,7 @@ export function selectServerFailure(): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServer {
|
||||
export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServerRequestAction {
|
||||
return {
|
||||
type: SERVER.REQUEST,
|
||||
server,
|
||||
|
@ -69,10 +80,9 @@ export function serverSuccess(): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverFailure(err: any): IServerFailure {
|
||||
export function serverFailure(): Action {
|
||||
return {
|
||||
type: SERVER.FAILURE,
|
||||
err
|
||||
type: SERVER.FAILURE
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { SUPPORTED_VERSIONS } from './actionsTypes';
|
||||
import { TSVDictionary, TSVMessage, TSVStatus } from '../definitions';
|
||||
|
||||
type TSetSupportedVersions = {
|
||||
status: TSVStatus;
|
||||
message?: TSVMessage;
|
||||
i18n?: TSVDictionary;
|
||||
expiration?: string;
|
||||
};
|
||||
type TSetSupportedVersionsAction = Action & TSetSupportedVersions;
|
||||
|
||||
export type TActionSupportedVersions = TSetSupportedVersionsAction;
|
||||
|
||||
export function setSupportedVersions({ status, message, i18n, expiration }: TSetSupportedVersions): TSetSupportedVersionsAction {
|
||||
return {
|
||||
type: SUPPORTED_VERSIONS.SET,
|
||||
status,
|
||||
message,
|
||||
i18n,
|
||||
expiration
|
||||
};
|
||||
}
|
|
@ -51,8 +51,8 @@ export const withActionSheet = (Component: React.ComponentType<any>): typeof Com
|
|||
const actionSheetRef: React.Ref<IActionSheetProvider> = createRef();
|
||||
|
||||
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
||||
const getContext = () => ({
|
||||
showActionSheet: (options: TActionSheetOptions) => {
|
||||
const getContext = (): IActionSheetProvider => ({
|
||||
showActionSheet: options => {
|
||||
actionSheetRef.current?.showActionSheet(options);
|
||||
},
|
||||
hideActionSheet: () => {
|
||||
|
@ -69,6 +69,10 @@ export const ActionSheetProvider = React.memo(({ children }: { children: React.R
|
|||
);
|
||||
});
|
||||
|
||||
export const hideActionSheetRef = (): void => {
|
||||
export const showActionSheetRef: IActionSheetProvider['showActionSheet'] = options => {
|
||||
actionSheetRef?.current?.showActionSheet(options);
|
||||
};
|
||||
|
||||
export const hideActionSheetRef: IActionSheetProvider['hideActionSheet'] = () => {
|
||||
actionSheetRef?.current?.hideActionSheet();
|
||||
};
|
||||
|
|
|
@ -44,8 +44,8 @@ describe('ButtonTests', () => {
|
|||
});
|
||||
|
||||
test('find button using accessibilityLabel', async () => {
|
||||
const { findByA11yLabel } = render(<TestButton />);
|
||||
const Button = await findByA11yLabel(testProps.title);
|
||||
const { getByLabelText } = render(<TestButton />);
|
||||
const Button = await getByLabelText(testProps.title);
|
||||
expect(Button).toBeTruthy();
|
||||
});
|
||||
|
||||
|
|
|
@ -71,9 +71,7 @@ const Button = ({
|
|||
{loading ? (
|
||||
<ActivityIndicator color={textColor} />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]} accessibilityLabel={title}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]}>{title}</Text>
|
||||
)}
|
||||
</Touchable>
|
||||
);
|
||||
|
|
|
@ -43,7 +43,18 @@ const styles = StyleSheet.create({
|
|||
const Item = ({ title, iconName, onPress, testID, badge, color, disabled, ...props }: IHeaderButtonItem): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<PlatformPressable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} disabled={disabled} style={styles.container}>
|
||||
<PlatformPressable
|
||||
onPress={onPress}
|
||||
testID={testID}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
disabled={disabled}
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
opacity: disabled ? 0.5 : 1
|
||||
}
|
||||
]}
|
||||
>
|
||||
<>
|
||||
{iconName ? (
|
||||
<CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import { STATUS_COLORS } from '../../lib/constants';
|
||||
import UnreadBadge from '../UnreadBadge';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -18,6 +17,6 @@ const styles = StyleSheet.create({
|
|||
|
||||
export const BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
||||
|
||||
export const BadgeWarn = (): React.ReactElement => (
|
||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
||||
export const BadgeWarn = ({ color }: { color: string }): React.ReactElement => (
|
||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: color }]} />
|
||||
);
|
||||
|
|
|
@ -107,7 +107,7 @@ export const Badge = () => (
|
|||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn color='red' />} />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
/>
|
||||
|
@ -120,7 +120,7 @@ const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
|||
<HeaderExample
|
||||
left={() => (
|
||||
<HeaderButton.Container left>
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn color={colors[theme].dangerColor} />} />
|
||||
<HeaderButton.Item iconName='threads' />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from 'react';
|
||||
// @ts-ignore // TODO: Remove on react-native update
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import { IServerInfo } from '../../definitions';
|
||||
import Check from '../Check';
|
||||
import styles, { ROW_HEIGHT } from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
|
@ -13,7 +11,12 @@ import { useTheme } from '../../theme';
|
|||
export { ROW_HEIGHT };
|
||||
|
||||
export interface IServerItem {
|
||||
item: IServerInfo;
|
||||
item: {
|
||||
id: string;
|
||||
iconURL: string;
|
||||
name: string;
|
||||
useRealName?: boolean;
|
||||
};
|
||||
onPress(): void;
|
||||
onLongPress?(): void;
|
||||
hasCheck?: boolean;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Linking } from 'react-native';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import Button from '../Button';
|
||||
import { styles } from './styles';
|
||||
import { LEARN_MORE_URL } from './constants';
|
||||
|
||||
export const SupportedVersionsExpired = () => {
|
||||
const { colors } = useTheme();
|
||||
const { name } = useAppSelector(state => state.server);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: 120, backgroundColor: colors.focusedBackground }]}>
|
||||
<View style={styles.iconContainer}>
|
||||
<CustomIcon name='warning' size={36} color={colors.dangerColor} />
|
||||
</View>
|
||||
<Text style={[styles.title, { color: colors.titleText }]}>
|
||||
{I18n.t('Supported_versions_expired_title', { workspace_name: name })}
|
||||
</Text>
|
||||
<Text style={[styles.description, { color: colors.bodyText }]}>{I18n.t('Supported_versions_expired_description')}</Text>
|
||||
<Button
|
||||
title={I18n.t('Learn_more')}
|
||||
type='secondary'
|
||||
backgroundColor={colors.chatComponentBackground}
|
||||
onPress={() => Linking.openURL(LEARN_MORE_URL)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { mockedStore } from '../../reducers/mockedStore';
|
||||
import { SupportedVersionsWarning } from './SupportedVersionsWarning';
|
||||
import { setUser } from '../../actions/login';
|
||||
import { setSupportedVersions } from '../../actions/supportedVersions';
|
||||
import { selectServerSuccess } from '../../actions/server';
|
||||
|
||||
const Render = () => (
|
||||
<Provider store={mockedStore}>
|
||||
<SupportedVersionsWarning />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
const TODAY = '2023-04-01T00:00:00.000Z';
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date(TODAY));
|
||||
|
||||
describe('SupportedVersionsWarning', () => {
|
||||
test('empty', () => {
|
||||
render(<Render />);
|
||||
expect(screen.queryByTestId('sv-warn-title')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-subtitle')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-description')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-button')).toBeNull();
|
||||
});
|
||||
|
||||
test('render properly', () => {
|
||||
mockedStore.dispatch(
|
||||
setUser({ language: 'en', username: 'rocket.cat', emails: [{ address: 'test@test.com', verified: true }] })
|
||||
);
|
||||
mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '1.0', name: 'Test Server' }));
|
||||
mockedStore.dispatch(
|
||||
setSupportedVersions({
|
||||
status: 'warn',
|
||||
message: {
|
||||
link: 'Docs page',
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
remainingDays: 10,
|
||||
type: 'alert',
|
||||
params: {
|
||||
test_a: 'test A works',
|
||||
test_b: ':)'
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
en: {
|
||||
title_token: '{{instance_ws_name}} is running an unsupported version of Rocket.Chat',
|
||||
subtitle_token: 'Mobile and desktop app access to {{instance_domain}} will be cut off in {{remaining_days}} days.',
|
||||
description_token:
|
||||
'User: {{instance_username}} Email: {{instance_email}} Version: {{instance_version}} Extra params: {{test_a}} {{test_b}}'
|
||||
},
|
||||
'pt-BR': {
|
||||
title_token: 'Alô título',
|
||||
subtitle_token:
|
||||
'{{instance_ws_name}} {{instance_domain}} {{remaining_days}} {{instance_username}} {{instance_email}} {{instance_version}} {{test_a}} {{test_b}}'
|
||||
}
|
||||
},
|
||||
expiration: '2023-05-01T00:00:00.000Z'
|
||||
})
|
||||
);
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Test Server is running an unsupported version of Rocket.Chat')).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('Mobile and desktop app access to https://example.com will be cut off in 30 days.')
|
||||
).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('User: rocket.cat Email: test@test.com Version: 1.0 Extra params: test A works :)')
|
||||
).toBeOnTheScreen();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
|
||||
test('render another language', () => {
|
||||
mockedStore.dispatch(setUser({ language: 'pt-BR' }));
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Alô título')).toBeOnTheScreen();
|
||||
expect(screen.getByText('Test Server https://example.com 30 rocket.cat test@test.com 1.0 test A works :)')).toBeOnTheScreen();
|
||||
expect(screen.queryByTestId('sv-warn-description')).toBeNull();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
|
||||
test('user on unsupported language and fallback to en', () => {
|
||||
mockedStore.dispatch(setUser({ language: 'it' }));
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Test Server is running an unsupported version of Rocket.Chat')).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('Mobile and desktop app access to https://example.com will be cut off in 30 days.')
|
||||
).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('User: rocket.cat Email: test@test.com Version: 1.0 Extra params: test A works :)')
|
||||
).toBeOnTheScreen();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
import React, { ReactElement, useLayoutEffect } from 'react';
|
||||
import { View, Text, Linking } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import Button from '../Button';
|
||||
import { styles } from './styles';
|
||||
import { useSupportedVersionMessage } from './useSupportedVersionMessage';
|
||||
import * as HeaderButton from '../HeaderButton';
|
||||
import I18n from '../../i18n';
|
||||
import { LEARN_MORE_URL } from './constants';
|
||||
|
||||
export const SupportedVersionsWarning = ({ navigation, route }: { navigation?: any; route?: any }): ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const message = useSupportedVersionMessage();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation?.setOptions({
|
||||
title: I18n.t('Supported_versions_warning_update_required')
|
||||
});
|
||||
|
||||
if (route?.params?.showCloseButton) {
|
||||
navigation?.setOptions({
|
||||
headerLeft: () => <HeaderButton.CloseModal />
|
||||
});
|
||||
}
|
||||
}, [navigation, route]);
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.focusedBackground }]}>
|
||||
<View style={styles.iconContainer}>
|
||||
<CustomIcon name='warning' size={36} color={colors.dangerColor} />
|
||||
</View>
|
||||
{message.title ? (
|
||||
<Text testID='sv-warn-title' style={[styles.title, { color: colors.titleText }]}>
|
||||
{message.title}
|
||||
</Text>
|
||||
) : null}
|
||||
{message.subtitle ? (
|
||||
<Text testID='sv-warn-subtitle' style={[styles.subtitle, { color: colors.bodyText }]}>
|
||||
{message.subtitle}
|
||||
</Text>
|
||||
) : null}
|
||||
{message.description ? (
|
||||
<Text testID='sv-warn-description' style={[styles.description, { color: colors.bodyText }]}>
|
||||
{message.description}
|
||||
</Text>
|
||||
) : null}
|
||||
<Button
|
||||
testID='sv-warn-button'
|
||||
title='Learn more'
|
||||
type='secondary'
|
||||
backgroundColor={colors.chatComponentBackground}
|
||||
onPress={() => Linking.openURL(message.link || LEARN_MORE_URL)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export const LEARN_MORE_URL = 'https://go.rocket.chat/i/supported-versions';
|
|
@ -0,0 +1,2 @@
|
|||
export * from './SupportedVersionsWarning';
|
||||
export * from './SupportedVersionsExpired';
|
|
@ -0,0 +1,33 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
padding: 24
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
lineHeight: 30,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textBold
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textBold
|
||||
},
|
||||
description: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import moment from 'moment';
|
||||
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const applyParams = (message: string, params: Record<string, unknown>) => {
|
||||
const keys = Object.keys(params);
|
||||
const regex = new RegExp(`{{(${keys.join('|')})}}`, 'g');
|
||||
return message.replace(regex, (_, p1) => params[p1] as string);
|
||||
};
|
||||
|
||||
const useUser = () => {
|
||||
const { username, name, emails, language } = useAppSelector(state => state.login.user);
|
||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const user = useRealName ? name : username;
|
||||
return { user, email: emails?.[0]?.address, language };
|
||||
};
|
||||
|
||||
export const useSupportedVersionMessage = () => {
|
||||
const { message, i18n, expiration } = useAppSelector(state => state.supportedVersions);
|
||||
const { name, server, version } = useAppSelector(state => state.server);
|
||||
const { language = 'en', user, email } = useUser();
|
||||
|
||||
const params = {
|
||||
instance_username: user,
|
||||
instance_email: email,
|
||||
instance_ws_name: name,
|
||||
instance_domain: server,
|
||||
remaining_days: moment(expiration).diff(new Date(), 'days'),
|
||||
instance_version: version,
|
||||
...message?.params
|
||||
};
|
||||
|
||||
if (!message || !i18n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const i18nLang = i18n[language] ?? i18n.en;
|
||||
|
||||
const getTranslation = (key: string | undefined) => (key && i18nLang[key] ? applyParams(i18nLang[key], params) : undefined);
|
||||
|
||||
return {
|
||||
title: getTranslation(message.title),
|
||||
subtitle: getTranslation(message.subtitle),
|
||||
description: getTranslation(message.description),
|
||||
link: message.link
|
||||
};
|
||||
};
|
|
@ -3,7 +3,7 @@ import { Control, Controller } from 'react-hook-form';
|
|||
|
||||
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
|
||||
|
||||
interface IControlledFormTextInputProps extends IRCTextInputProps {
|
||||
interface IControlledFormTextInputProps extends Omit<IRCTextInputProps, 'inputRef'> {
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ export const ControlledFormTextInput = ({ control, name, ...props }: IControlled
|
|||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => <FormTextInput onChangeText={onChange} value={value} {...props} />}
|
||||
render={({ field: { onChange, value, ref } }) => (
|
||||
<FormTextInput onChangeText={onChange} value={value} inputRef={ref} {...props} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
import React from 'react';
|
||||
import { StyleProp, Text, TextStyle } from 'react-native';
|
||||
|
||||
import i18n from '../../i18n';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IUserChannel } from './interfaces';
|
||||
import styles from './styles';
|
||||
import { getSubscriptionByRoomId } from '../../lib/database/services/Subscription';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { showErrorAlert } from '../../lib/methods/helpers';
|
||||
import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||
import { Services } from '../../lib/services';
|
||||
import { useTheme } from '../../theme';
|
||||
import { sendLoadingEvent } from '../Loading';
|
||||
import { IUserChannel } from './interfaces';
|
||||
import styles from './styles';
|
||||
|
||||
interface IHashtag {
|
||||
hashtag: string;
|
||||
|
@ -30,8 +34,16 @@ const Hashtag = React.memo(({ hashtag, channels, navToRoomInfo, style = [] }: IH
|
|||
const room = navParam.rid && (await getSubscriptionByRoomId(navParam.rid));
|
||||
if (room) {
|
||||
goRoom({ item: room, isMasterDetail });
|
||||
} else {
|
||||
navToRoomInfo(navParam);
|
||||
} else if (navParam.rid) {
|
||||
sendLoadingEvent({ visible: true });
|
||||
try {
|
||||
await Services.getRoomInfo(navParam.rid);
|
||||
sendLoadingEvent({ visible: false });
|
||||
navToRoomInfo(navParam);
|
||||
} catch (error) {
|
||||
sendLoadingEvent({ visible: false });
|
||||
showErrorAlert(i18n.t('The_room_does_not_exist'), i18n.t('Room_not_found'));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,17 +9,19 @@ import { SubscriptionType } from '../../definitions';
|
|||
const MessageAvatar = React.memo(({ isHeader, avatar, author, small, navToRoomInfo, emoji, getCustomEmoji }: IMessageAvatar) => {
|
||||
const { user } = useContext(MessageContext);
|
||||
if (isHeader && author) {
|
||||
const navParam = {
|
||||
t: SubscriptionType.DIRECT,
|
||||
rid: author._id
|
||||
};
|
||||
const onPress = () =>
|
||||
navToRoomInfo({
|
||||
t: SubscriptionType.DIRECT,
|
||||
rid: author._id,
|
||||
itsMe: author._id === user.id
|
||||
});
|
||||
return (
|
||||
<Avatar
|
||||
style={small ? styles.avatarSmall : styles.avatar}
|
||||
text={avatar ? '' : author.username}
|
||||
size={small ? 20 : 36}
|
||||
borderRadius={4}
|
||||
onPress={author._id === user.id ? undefined : () => navToRoomInfo(navParam)}
|
||||
onPress={onPress}
|
||||
getCustomEmoji={getCustomEmoji}
|
||||
avatar={avatar}
|
||||
emoji={emoji}
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
import moment from 'moment';
|
||||
import React, { useContext } from 'react';
|
||||
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
||||
import moment from 'moment';
|
||||
|
||||
import { themes } from '../../lib/constants';
|
||||
import { useTheme } from '../../theme';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import messageStyles from './styles';
|
||||
import MessageContext from './Context';
|
||||
import { messageHaveAuthorName } from './utils';
|
||||
import { MessageType, MessageTypesValues, SubscriptionType } from '../../definitions';
|
||||
import { useTheme } from '../../theme';
|
||||
import { IRoomInfoParam } from '../../views/SearchMessagesView';
|
||||
import sharedStyles from '../../views/Styles';
|
||||
import RightIcons from './Components/RightIcons';
|
||||
import MessageContext from './Context';
|
||||
import messageStyles from './styles';
|
||||
import { messageHaveAuthorName } from './utils';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -66,21 +65,21 @@ interface IMessageUser {
|
|||
const User = React.memo(
|
||||
({ isHeader, useRealName, author, alias, ts, timeFormat, hasError, navToRoomInfo, type, isEdited, ...props }: IMessageUser) => {
|
||||
const { user } = useContext(MessageContext);
|
||||
const { theme } = useTheme();
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (isHeader) {
|
||||
const username = (useRealName && author?.name) || author?.username;
|
||||
const aliasUsername = alias ? (
|
||||
<Text style={[styles.alias, { color: themes[theme].auxiliaryText }]}> @{username}</Text>
|
||||
) : null;
|
||||
const aliasUsername = alias ? <Text style={[styles.alias, { color: colors.auxiliaryText }]}> @{username}</Text> : null;
|
||||
const time = moment(ts).format(timeFormat);
|
||||
const itsMe = author?._id === user.id;
|
||||
|
||||
const onUserPress = () => {
|
||||
navToRoomInfo?.({
|
||||
t: SubscriptionType.DIRECT,
|
||||
rid: author?._id || ''
|
||||
rid: author?._id || '',
|
||||
itsMe
|
||||
});
|
||||
};
|
||||
const isDisabled = author?._id === user.id;
|
||||
|
||||
const textContent = (
|
||||
<>
|
||||
|
@ -88,14 +87,10 @@ const User = React.memo(
|
|||
{aliasUsername}
|
||||
</>
|
||||
);
|
||||
|
||||
if (messageHaveAuthorName(type as MessageTypesValues)) {
|
||||
return (
|
||||
<Text
|
||||
style={[styles.usernameInfoMessage, { color: themes[theme].titleText }]}
|
||||
onPress={onUserPress}
|
||||
// @ts-ignore // TODO - check this prop
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<Text style={[styles.usernameInfoMessage, { color: colors.titleText }]} onPress={onUserPress}>
|
||||
{textContent}
|
||||
</Text>
|
||||
);
|
||||
|
@ -103,11 +98,11 @@ const User = React.memo(
|
|||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<TouchableOpacity style={styles.titleContainer} onPress={onUserPress} disabled={isDisabled}>
|
||||
<Text style={[styles.username, { color: themes[theme].titleText }]} numberOfLines={1}>
|
||||
<TouchableOpacity style={styles.titleContainer} onPress={onUserPress}>
|
||||
<Text style={[styles.username, { color: colors.titleText }]} numberOfLines={1}>
|
||||
{textContent}
|
||||
</Text>
|
||||
<Text style={[messageStyles.time, { color: themes[theme].auxiliaryText }]}>{time}</Text>
|
||||
<Text style={[messageStyles.time, { color: colors.auxiliaryText }]}>{time}</Text>
|
||||
</TouchableOpacity>
|
||||
<RightIcons
|
||||
type={type}
|
||||
|
|
|
@ -2,6 +2,68 @@ import Model from '@nozbe/watermelondb/Model';
|
|||
|
||||
import { IEnterpriseModules } from '../reducers/enterpriseModules';
|
||||
|
||||
export type TSVStatus = 'supported' | 'expired' | 'warn';
|
||||
|
||||
export type TSVDictionary = {
|
||||
[lng: string]: Record<string, string>;
|
||||
};
|
||||
|
||||
export type TSVMessage = {
|
||||
remainingDays: number;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
type: 'info' | 'alert' | 'error';
|
||||
params?: Record<string, unknown>;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export type TSVVersion = {
|
||||
version: string;
|
||||
expiration: string;
|
||||
messages?: TSVMessage[];
|
||||
};
|
||||
|
||||
export interface ISupportedVersionsData {
|
||||
timestamp: string;
|
||||
enforcementStartDate: string;
|
||||
messages?: TSVMessage[];
|
||||
versions: TSVVersion[];
|
||||
exceptions?: {
|
||||
domain: string;
|
||||
uniqueId: string;
|
||||
messages?: TSVMessage[];
|
||||
versions: TSVVersion[];
|
||||
};
|
||||
i18n?: TSVDictionary;
|
||||
}
|
||||
|
||||
export interface ISupportedVersions extends ISupportedVersionsData {
|
||||
signed: string;
|
||||
}
|
||||
|
||||
export interface IApiServerInfo {
|
||||
version: string;
|
||||
success: boolean;
|
||||
supportedVersions?: ISupportedVersions;
|
||||
minimumClientVersions?: {
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IServerInfo {
|
||||
version: string;
|
||||
success: boolean;
|
||||
supportedVersions?: ISupportedVersionsData | null; // no signed
|
||||
minimumClientVersions?: {
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type TCloudInfo = ISupportedVersions;
|
||||
|
||||
export interface IServer {
|
||||
name: string;
|
||||
iconURL: string;
|
||||
|
@ -17,13 +79,9 @@ export interface IServer {
|
|||
uniqueID: string;
|
||||
enterpriseModules: IEnterpriseModules;
|
||||
E2E_Enable: boolean;
|
||||
}
|
||||
|
||||
export interface IServerInfo {
|
||||
id: string;
|
||||
iconURL: string;
|
||||
name: string;
|
||||
useRealName?: boolean;
|
||||
supportedVersions?: ISupportedVersionsData;
|
||||
supportedVersionsWarningAt?: Date;
|
||||
supportedVersionsUpdatedAt?: Date;
|
||||
}
|
||||
|
||||
export type TServerModel = IServer & Model;
|
||||
|
|
|
@ -17,6 +17,7 @@ import { TActionUserTyping } from '../../actions/usersTyping';
|
|||
import { TActionPermissions } from '../../actions/permissions';
|
||||
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
||||
import { TActionVideoConf } from '../../actions/videoConf';
|
||||
import { TActionSupportedVersions } from '../../actions/supportedVersions';
|
||||
// REDUCERS
|
||||
import { IActiveUsers } from '../../reducers/activeUsers';
|
||||
import { IApp } from '../../reducers/app';
|
||||
|
@ -40,6 +41,7 @@ import { TActionUsersRoles } from '../../actions/usersRoles';
|
|||
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||
import { ITroubleshootingNotification } from '../../reducers/troubleshootingNotification';
|
||||
import { TActionTroubleshootingNotification } from '../../actions/troubleshootingNotification';
|
||||
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
|
||||
|
||||
export interface IApplicationState {
|
||||
settings: TSettingsState;
|
||||
|
@ -66,6 +68,7 @@ export interface IApplicationState {
|
|||
videoConf: IVideoConf;
|
||||
usersRoles: TUsersRoles;
|
||||
troubleshootingNotification: ITroubleshootingNotification;
|
||||
supportedVersions: ISupportedVersionsState;
|
||||
}
|
||||
|
||||
export type TApplicationActions = TActionActiveUsers &
|
||||
|
@ -87,4 +90,5 @@ export type TApplicationActions = TActionActiveUsers &
|
|||
TActionEnterpriseModules &
|
||||
TActionVideoConf &
|
||||
TActionUsersRoles &
|
||||
TActionTroubleshootingNotification;
|
||||
TActionTroubleshootingNotification &
|
||||
TActionSupportedVersions;
|
||||
|
|
|
@ -692,9 +692,9 @@
|
|||
"Call_ended": "Call ended",
|
||||
"Call_was_not_answered": "Call was not answered",
|
||||
"Call_rejected": "Call rejected",
|
||||
"Call_back": "Call Back",
|
||||
"Call_again": "Call Again",
|
||||
"Call_ongoing": "Call Ongoing",
|
||||
"Call_back": "Call back",
|
||||
"Call_again": "Call again",
|
||||
"Call_ongoing": "Call ongoing",
|
||||
"Call_issue": "Call issue",
|
||||
"Joined": "Joined",
|
||||
"Calling": "Calling",
|
||||
|
@ -771,5 +771,10 @@
|
|||
"Jitsi_authentication_before_making_calls_ask_admin": "If you believe there are problems with Jitsi and its authentication, ask a workspace administrator for help.",
|
||||
"Continue": "Continue",
|
||||
"Troubleshooting": "Troubleshooting",
|
||||
"Your_push_was_sent_to_s_devices": "Your push was sent to {{s}} devices"
|
||||
"Your_push_was_sent_to_s_devices": "Your push was sent to {{s}} devices",
|
||||
"Room_not_found": "Room not found",
|
||||
"The_room_does_not_exist": "The room does not exist or you may not have access permission",
|
||||
"Supported_versions_expired_title": "{{workspace_name}} is running an unsupported version of Rocket.Chat",
|
||||
"Supported_versions_expired_description": "An admin needs to update the workspace to a supported version in order to reenable access from mobile and desktop apps.",
|
||||
"Supported_versions_warning_update_required": "Update required"
|
||||
}
|
||||
|
|
|
@ -650,8 +650,8 @@
|
|||
"Removed__username__from_the_team": "removeu @{{userRemoved}} deste time",
|
||||
"Place_chat_on_hold": "Colocar conversa em espera",
|
||||
"Would_like_to_place_on_hold": "Gostaria de colocar essa conversa em espera?",
|
||||
"Open_Livechats": "Bate-papos em Andamento",
|
||||
"On_hold_Livechats": "Conversas Em Espera",
|
||||
"Open_Livechats": "Bate-papos em andamento",
|
||||
"On_hold_Livechats": "Conversas em espera",
|
||||
"Chat_is_on_hold": "Esta conversa está em espera devido à inatividade",
|
||||
"Resume": "Continuar",
|
||||
"Omnichannel_placed_chat_on_hold": "Conversa em espera: {{comment}}",
|
||||
|
@ -757,5 +757,10 @@
|
|||
"accept": "Aceitar",
|
||||
"Incoming_call_from": "Chamada recebida de",
|
||||
"Call_started": "Chamada Iniciada",
|
||||
"Your_push_was_sent_to_s_devices": "A sua notificação foi enviada para {{s}} dispositivos"
|
||||
"Your_push_was_sent_to_s_devices": "A sua notificação foi enviada para {{s}} dispositivos",
|
||||
"Room_not_found": "Sala não encontrada",
|
||||
"The_room_does_not_exist": "A sala não existe ou você pode não ter permissão de acesso",
|
||||
"Supported_versions_expired_title": "{{workspace_name}} está executando uma versão não suportada do Rocket.Chat",
|
||||
"Supported_versions_expired_description": "Um administrador precisa atualizar o espaço de trabalho para uma versão suportada a fim de reabilitar o acesso a partir de aplicativos móveis e de desktop.",
|
||||
"Supported_versions_warning_update_required": "Atualização necessária"
|
||||
}
|
|
@ -10,6 +10,7 @@ export * from './messagesStatus';
|
|||
export * from './messageTypeLoad';
|
||||
export * from './notifications';
|
||||
export * from './defaultSettings';
|
||||
export * from './supportedVersions';
|
||||
export * from './tablet';
|
||||
export * from './mediaAutoDownload';
|
||||
export * from './userAgent';
|
||||
|
|
|
@ -18,7 +18,6 @@ export const E2E_ROOM_TYPES: Record<string, string> = {
|
|||
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
|
||||
export const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
||||
export const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||
export const CURRENT_SERVER = 'currentServer';
|
||||
export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export const SIGNED_SUPPORTED_VERSIONS_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvZ/T/RHOr6+yo/iMLUlf
|
||||
agMiMLFxQR/5Qtc85ykMBvKZqbBGb9zU68VB9n54alrbZG5FdcHkSJXgJIBXF2bk
|
||||
TGTfBi58JmltZirSWzvXoXnT4ieGNZv+BqnP9zzj9HXOVhVncbRmJPEIJOZfL9AQ
|
||||
beix3rPgZx3ZepAaoMQnz11dZKDGzkMN75WkTdf324X3DeFgLVmjsYuAcLl/AJMA
|
||||
uPKSSt0XOQUsfrT7rEqXIrj8rIJcWxIHICMRrwfjw2Qh+3pfIrh7XSzxlW4zCKBN
|
||||
RpavrrCnpOFRfkC5T9eMKLgyapjufOtbjuzu25N3urBsg6oRFNzsGXWp1C7DwUO2
|
||||
kwIDAQAB
|
||||
-----END PUBLIC KEY-----`;
|
|
@ -1,5 +1,6 @@
|
|||
import { Model } from '@nozbe/watermelondb';
|
||||
import { date, field } from '@nozbe/watermelondb/decorators';
|
||||
import { date, field, json } from '@nozbe/watermelondb/decorators';
|
||||
import { sanitizer } from '../../utils';
|
||||
|
||||
export const SERVERS_TABLE = 'servers';
|
||||
|
||||
|
@ -33,4 +34,10 @@ export default class Server extends Model {
|
|||
@field('enterprise_modules') enterpriseModules;
|
||||
|
||||
@field('e2e_enable') E2E_Enable;
|
||||
|
||||
@json('supported_versions', sanitizer) supportedVersions;
|
||||
|
||||
@date('supported_versions_warning_at') supportedVersionsWarningAt;
|
||||
|
||||
@date('supported_versions_updated_at') supportedVersionsUpdatedAt;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,37 @@ export default schemaMigrations({
|
|||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 14,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'servers',
|
||||
columns: [
|
||||
{ name: 'supported_versions', type: 'string', isOptional: true },
|
||||
{
|
||||
name: 'supported_versions_warning_at',
|
||||
type: 'number',
|
||||
isOptional: true
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
},
|
||||
{
|
||||
toVersion: 15,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: 'servers',
|
||||
columns: [
|
||||
{
|
||||
name: 'supported_versions_updated_at',
|
||||
type: 'number',
|
||||
isOptional: true
|
||||
}
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||
|
||||
export default appSchema({
|
||||
version: 13,
|
||||
version: 15,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -38,7 +38,10 @@ export default appSchema({
|
|||
{ 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 }
|
||||
{ name: 'e2e_enable', type: 'boolean', isOptional: true },
|
||||
{ name: 'supported_versions', type: 'string', isOptional: true },
|
||||
{ name: 'supported_versions_warning_at', type: 'number', isOptional: true },
|
||||
{ name: 'supported_versions_updated_at', type: 'number', isOptional: true }
|
||||
]
|
||||
}),
|
||||
tableSchema({
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { TLoggedUserModel } from '../../../definitions';
|
||||
import database from '..';
|
||||
import { TServerDatabase } from '../interfaces';
|
||||
import { LOGGED_USERS_TABLE } from '../model';
|
||||
|
||||
const getCollection = (db: TServerDatabase) => db.get(LOGGED_USERS_TABLE);
|
||||
|
||||
export const getLoggedUserById = async (userId: string): Promise<TLoggedUserModel | null> => {
|
||||
const db = database.servers;
|
||||
const userCollection = getCollection(db);
|
||||
try {
|
||||
const result = await userCollection.find(userId);
|
||||
return result;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
import { TServerModel } from '../../../definitions';
|
||||
import database from '..';
|
||||
import { TServerDatabase } from '../interfaces';
|
||||
import { SERVERS_TABLE } from '../model';
|
||||
|
||||
const getCollection = (db: TServerDatabase) => db.get(SERVERS_TABLE);
|
||||
|
||||
export const getServerById = async (server: string): Promise<TServerModel | null> => {
|
||||
const db = database.servers;
|
||||
const serverCollection = getCollection(db);
|
||||
try {
|
||||
const result = await serverCollection.find(server);
|
||||
return result;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,523 @@
|
|||
import { ISupportedVersionsData } from '../../definitions';
|
||||
import { checkSupportedVersions, getMessage } from './checkSupportedVersions';
|
||||
|
||||
const MOCK_I18N = {
|
||||
en: {
|
||||
message_token: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
};
|
||||
const TODAY = '2023-04-01T00:00:00.000Z';
|
||||
const MOCK: ISupportedVersionsData = {
|
||||
timestamp: TODAY,
|
||||
enforcementStartDate: '2023-04-02T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
i18n: MOCK_I18N,
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.1.0',
|
||||
expiration: '2023-01-10T00:00:00.000Z'
|
||||
}
|
||||
],
|
||||
exceptions: {
|
||||
domain: 'https://open.rocket.chat',
|
||||
uniqueId: '123',
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-05-01T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const MOCK_BUILTIN_I18N = {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
};
|
||||
jest.mock('../../../app-supportedversions.json', () => ({
|
||||
timestamp: '2023-04-01T00:00:00.000Z',
|
||||
enforcementStartDate: '2023-04-02T00:00:00.000Z',
|
||||
i18n: {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'builtin_i18n',
|
||||
subtitle: 'builtin_i18n',
|
||||
description: 'builtin_i18n',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 10,
|
||||
message: '1.4',
|
||||
type: 'info'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
message: '1.3',
|
||||
type: 'info'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date(TODAY));
|
||||
|
||||
describe('checkSupportedVersions', () => {
|
||||
describe('Built-in supported versions', () => {
|
||||
test('no supported versions', () => {
|
||||
expect(checkSupportedVersions({ supportedVersions: undefined, serverVersion: '1.5.0' })).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
expect(checkSupportedVersions({ supportedVersions: undefined, serverVersion: '1.1.0' })).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'builtin_i18n',
|
||||
subtitle: 'builtin_i18n',
|
||||
description: 'builtin_i18n',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('deprecated version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'builtin_i18n',
|
||||
subtitle: 'builtin_i18n',
|
||||
description: 'builtin_i18n',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('valid version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.5.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
});
|
||||
|
||||
test('valid version with message', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 10,
|
||||
message: '1.4',
|
||||
type: 'info'
|
||||
},
|
||||
i18n: MOCK_BUILTIN_I18N
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Backend/Cloud and exceptions', () => {
|
||||
test('valid version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: MOCK_I18N,
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and valid exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.3.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and expired exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: MOCK_I18N,
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and no exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.1.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: MOCK_I18N,
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('server version is not supported', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.0.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
i18n: MOCK_I18N,
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Messages', () => {
|
||||
const MOCK_MESSAGES: ISupportedVersionsData = {
|
||||
timestamp: TODAY,
|
||||
enforcementStartDate: '2023-04-02T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 60,
|
||||
title: 'title_root',
|
||||
subtitle: 'subtitle_root',
|
||||
description: 'description_root',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
i18n: {
|
||||
en: {
|
||||
message_token: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
{
|
||||
remainingDays: 30,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
}
|
||||
],
|
||||
exceptions: {
|
||||
domain: 'https://open.rocket.chat',
|
||||
uniqueId: '123',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'title_exception',
|
||||
subtitle: 'subtitle_exception',
|
||||
description: 'description_exception',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-05-01T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 30,
|
||||
title: 'title_exception_version',
|
||||
subtitle: 'subtitle_exception_version',
|
||||
description: 'description_exception_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
test('from exception version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.3.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 30,
|
||||
title: 'title_exception_version',
|
||||
subtitle: 'subtitle_exception_version',
|
||||
description: 'description_exception_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('from exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'title_exception',
|
||||
subtitle: 'subtitle_exception',
|
||||
description: 'description_exception',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('from supported version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
i18n: MOCK_I18N
|
||||
});
|
||||
});
|
||||
|
||||
test('from root node', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.5.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 60,
|
||||
title: 'title_root',
|
||||
subtitle: 'subtitle_root',
|
||||
description: 'description_root',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
i18n: MOCK_I18N
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessage', () => {
|
||||
test('no messages', () => {
|
||||
expect(getMessage({ messages: undefined, expiration: '2023-04-10T00:00:00.000Z' })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('no expiration or already expired', () => {
|
||||
expect(getMessage({ messages: undefined, expiration: undefined })).toBeUndefined();
|
||||
expect(getMessage({ messages: undefined, expiration: '2023-01-10T00:00:00.000Z' })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('receives a message that should not be triggered yet', () => {
|
||||
expect(
|
||||
getMessage({
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 1,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('receives two messages and returns the appropriate one', () => {
|
||||
expect(
|
||||
getMessage({
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 11,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
{
|
||||
remainingDays: 10,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
})
|
||||
).toMatchObject({
|
||||
remainingDays: 10,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,502 @@
|
|||
import { ISupportedVersionsData } from '../../definitions';
|
||||
import { checkSupportedVersions, getMessage } from './checkSupportedVersions';
|
||||
|
||||
const MOCK_I18N = {
|
||||
en: {
|
||||
message_token: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
};
|
||||
const TODAY = '2023-04-01T00:00:00.000Z';
|
||||
const MOCK: ISupportedVersionsData = {
|
||||
timestamp: TODAY,
|
||||
enforcementStartDate: TODAY,
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
i18n: MOCK_I18N,
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.1.0',
|
||||
expiration: '2023-01-10T00:00:00.000Z'
|
||||
}
|
||||
],
|
||||
exceptions: {
|
||||
domain: 'https://open.rocket.chat',
|
||||
uniqueId: '123',
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-05-01T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const MOCK_BUILTIN_I18N = {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
};
|
||||
jest.mock('../../../app-supportedversions.json', () => ({
|
||||
timestamp: '2023-04-01T00:00:00.000Z',
|
||||
enforcementStartDate: TODAY,
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'builtin_i18n',
|
||||
subtitle: 'builtin_i18n',
|
||||
description: 'builtin_i18n',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
i18n: {
|
||||
en: {
|
||||
builtin_i18n: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 10,
|
||||
message: '1.4',
|
||||
type: 'info'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
message: '1.3',
|
||||
type: 'info'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}));
|
||||
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date(TODAY));
|
||||
|
||||
describe('checkSupportedVersions', () => {
|
||||
describe('General', () => {
|
||||
test('ignore the patch and compare as minor', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.5.1'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.2.1'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Built-in supported versions', () => {
|
||||
test('no supported versions', () => {
|
||||
expect(checkSupportedVersions({ supportedVersions: undefined, serverVersion: '1.5.0' })).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
expect(checkSupportedVersions({ supportedVersions: undefined, serverVersion: '1.1.0' })).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
|
||||
test('deprecated version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
|
||||
test('valid version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.5.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
});
|
||||
|
||||
test('valid version with message', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: { ...MOCK, timestamp: '2023-03-01T00:00:00.000Z' },
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 10,
|
||||
message: '1.4',
|
||||
type: 'info'
|
||||
},
|
||||
i18n: MOCK_BUILTIN_I18N
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Backend/Cloud and exceptions', () => {
|
||||
test('valid version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.5.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
});
|
||||
|
||||
test('warning version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'message_token',
|
||||
subtitle: 'message_token',
|
||||
description: 'message_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
i18n: MOCK_I18N
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and valid exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.3.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'supported'
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and expired exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
|
||||
test('expired version and no exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.1.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
|
||||
test('server version is not supported', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK,
|
||||
serverVersion: '1.0.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'expired'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Messages', () => {
|
||||
const MOCK_MESSAGES: ISupportedVersionsData = {
|
||||
timestamp: TODAY,
|
||||
enforcementStartDate: TODAY,
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 60,
|
||||
title: 'title_root',
|
||||
subtitle: 'subtitle_root',
|
||||
description: 'description_root',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
i18n: {
|
||||
en: {
|
||||
message_token: 'Your server is about to be deprecated. Please update to the latest version.'
|
||||
}
|
||||
},
|
||||
versions: [
|
||||
{
|
||||
version: '1.5.0',
|
||||
expiration: '2023-05-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.4.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
{
|
||||
remainingDays: 30,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-03-10T00:00:00.000Z'
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-02-10T00:00:00.000Z'
|
||||
}
|
||||
],
|
||||
exceptions: {
|
||||
domain: 'https://open.rocket.chat',
|
||||
uniqueId: '123',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 15,
|
||||
title: 'title_exception',
|
||||
subtitle: 'subtitle_exception',
|
||||
description: 'description_exception',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
versions: [
|
||||
{
|
||||
version: '1.3.0',
|
||||
expiration: '2023-05-01T00:00:00.000Z',
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 30,
|
||||
title: 'title_exception_version',
|
||||
subtitle: 'subtitle_exception_version',
|
||||
description: 'description_exception_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
version: '1.2.0',
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
test('from exception version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.3.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 30,
|
||||
title: 'title_exception_version',
|
||||
subtitle: 'subtitle_exception_version',
|
||||
description: 'description_exception_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('from exception', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.2.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'title_exception',
|
||||
subtitle: 'subtitle_exception',
|
||||
description: 'description_exception',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test('from supported version', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.4.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 15,
|
||||
title: 'title_version',
|
||||
subtitle: 'subtitle_version',
|
||||
description: 'description_version',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
i18n: MOCK_I18N
|
||||
});
|
||||
});
|
||||
|
||||
test('from root node', () => {
|
||||
expect(
|
||||
checkSupportedVersions({
|
||||
supportedVersions: MOCK_MESSAGES,
|
||||
serverVersion: '1.5.0'
|
||||
})
|
||||
).toMatchObject({
|
||||
status: 'warn',
|
||||
message: {
|
||||
remainingDays: 60,
|
||||
title: 'title_root',
|
||||
subtitle: 'subtitle_root',
|
||||
description: 'description_root',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
i18n: MOCK_I18N
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getMessage', () => {
|
||||
test('no messages', () => {
|
||||
expect(getMessage({ messages: undefined, expiration: '2023-04-10T00:00:00.000Z' })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('no expiration or already expired', () => {
|
||||
expect(getMessage({ messages: undefined, expiration: undefined })).toBeUndefined();
|
||||
expect(getMessage({ messages: undefined, expiration: '2023-01-10T00:00:00.000Z' })).toBeUndefined();
|
||||
});
|
||||
|
||||
test('receives a message that should not be triggered yet', () => {
|
||||
expect(
|
||||
getMessage({
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 1,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
})
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
test('receives two messages and returns the appropriate one', () => {
|
||||
expect(
|
||||
getMessage({
|
||||
messages: [
|
||||
{
|
||||
remainingDays: 11,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
},
|
||||
{
|
||||
remainingDays: 10,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
}
|
||||
],
|
||||
expiration: '2023-04-10T00:00:00.000Z'
|
||||
})
|
||||
).toMatchObject({
|
||||
remainingDays: 10,
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,92 @@
|
|||
import moment from 'moment';
|
||||
import coerce from 'semver/functions/coerce';
|
||||
import satisfies from 'semver/functions/satisfies';
|
||||
|
||||
import { ISupportedVersionsData, TSVDictionary, TSVMessage, TSVStatus } from '../../definitions';
|
||||
import builtInSupportedVersions from '../../../app-supportedversions.json';
|
||||
|
||||
export const getMessage = ({
|
||||
messages,
|
||||
expiration
|
||||
}: {
|
||||
messages?: TSVMessage[];
|
||||
expiration?: string;
|
||||
}): TSVMessage | undefined => {
|
||||
if (!messages?.length || !expiration || moment(expiration).diff(new Date(), 'days') < 0) {
|
||||
return;
|
||||
}
|
||||
const sortedMessages = messages.sort((a, b) => a.remainingDays - b.remainingDays);
|
||||
return sortedMessages.find(({ remainingDays }) => moment(expiration).diff(new Date(), 'hours') <= remainingDays * 24);
|
||||
};
|
||||
|
||||
const getStatus = ({ expiration, message }: { expiration?: string; message?: TSVMessage }): TSVStatus => {
|
||||
if (!(expiration && new Date(expiration) >= new Date())) {
|
||||
return 'expired';
|
||||
}
|
||||
if (message) {
|
||||
return 'warn';
|
||||
}
|
||||
return 'supported';
|
||||
};
|
||||
|
||||
export const checkSupportedVersions = function ({
|
||||
supportedVersions,
|
||||
serverVersion
|
||||
}: {
|
||||
supportedVersions?: ISupportedVersionsData;
|
||||
serverVersion: string;
|
||||
}): {
|
||||
status: TSVStatus;
|
||||
message?: TSVMessage;
|
||||
i18n?: TSVDictionary;
|
||||
expiration?: string;
|
||||
} {
|
||||
const serverVersionTilde = `~${serverVersion.split('.').slice(0, 2).join('.')}`;
|
||||
let sv: ISupportedVersionsData;
|
||||
if (!supportedVersions || supportedVersions.timestamp < builtInSupportedVersions.timestamp) {
|
||||
// Built-in supported versions
|
||||
sv = builtInSupportedVersions as ISupportedVersionsData;
|
||||
} else {
|
||||
// Backend/Cloud
|
||||
sv = supportedVersions;
|
||||
}
|
||||
|
||||
const versionInfo = sv.versions.find(({ version }) => satisfies(coerce(version)?.version ?? '', serverVersionTilde));
|
||||
if (versionInfo && new Date(versionInfo.expiration) >= new Date()) {
|
||||
const messages = versionInfo?.messages || sv?.messages;
|
||||
const message = getMessage({ messages, expiration: versionInfo.expiration });
|
||||
return {
|
||||
status: getStatus({ expiration: versionInfo?.expiration, message }),
|
||||
message,
|
||||
i18n: message ? sv?.i18n : undefined,
|
||||
expiration: versionInfo?.expiration
|
||||
};
|
||||
}
|
||||
|
||||
// Exceptions
|
||||
const exception = sv.exceptions?.versions?.find(({ version }) => satisfies(coerce(version)?.version ?? '', serverVersionTilde));
|
||||
const messages = exception?.messages || sv.exceptions?.messages || versionInfo?.messages || sv.messages;
|
||||
const message = getMessage({ messages, expiration: exception?.expiration });
|
||||
const status = getStatus({ expiration: exception?.expiration, message });
|
||||
|
||||
// TODO: enforcement start date is temp only. Remove after a few releases.
|
||||
if (status === 'expired' && sv?.enforcementStartDate && new Date(sv.enforcementStartDate) > new Date()) {
|
||||
const enforcementMessage = getMessage({
|
||||
messages,
|
||||
expiration: sv.enforcementStartDate
|
||||
});
|
||||
return {
|
||||
status: 'warn',
|
||||
message: enforcementMessage,
|
||||
i18n: enforcementMessage ? sv?.i18n : undefined,
|
||||
expiration: sv.enforcementStartDate
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status,
|
||||
message,
|
||||
i18n: message ? sv?.i18n : undefined,
|
||||
expiration: exception?.expiration
|
||||
};
|
||||
};
|
|
@ -0,0 +1,125 @@
|
|||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||
import { KJUR } from 'jsrsasign';
|
||||
import moment from 'moment';
|
||||
|
||||
import { getSupportedVersionsCloud } from '../services/restApi';
|
||||
import { TCloudInfo, IServerInfo, ISupportedVersions, ISupportedVersionsData, IApiServerInfo } from '../../definitions';
|
||||
import { selectServerFailure } from '../../actions/server';
|
||||
import { store } from '../store/auxStore';
|
||||
import I18n from '../../i18n';
|
||||
import { SIGNED_SUPPORTED_VERSIONS_PUBLIC_KEY } from '../constants';
|
||||
import { getServerById } from '../database/services/Server';
|
||||
|
||||
interface IServerInfoFailure {
|
||||
success: false;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface IServerInfoSuccess extends IServerInfo {
|
||||
success: true;
|
||||
}
|
||||
|
||||
export type TServerInfoResult = IServerInfoSuccess | IServerInfoFailure;
|
||||
|
||||
const SV_CLOUD_UPDATE_INTERVAL = 12; // hours
|
||||
|
||||
// Verifies if JWT is valid and returns the payload
|
||||
const verifyJWT = (jwt?: string): ISupportedVersionsData | null => {
|
||||
try {
|
||||
if (!jwt) {
|
||||
return null;
|
||||
}
|
||||
const isValid = KJUR.jws.JWS.verify(jwt, SIGNED_SUPPORTED_VERSIONS_PUBLIC_KEY, ['RS256']);
|
||||
if (!isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { payloadObj } = KJUR.jws.JWS.parse(jwt);
|
||||
return payloadObj as ISupportedVersions;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export async function getServerInfo(server: string): Promise<TServerInfoResult> {
|
||||
try {
|
||||
const response = await RNFetchBlob.fetch('GET', `${server}/api/info`, {
|
||||
...RocketChatSettings.customHeaders
|
||||
});
|
||||
try {
|
||||
const jsonRes: IApiServerInfo = response.json();
|
||||
if (!jsonRes?.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
|
||||
// Makes use of signed JWT to get supported versions
|
||||
const supportedVersions = verifyJWT(jsonRes.supportedVersions?.signed);
|
||||
|
||||
// if backend doesn't have supported versions or JWT is invalid, request from cloud
|
||||
if (!supportedVersions) {
|
||||
// fetches from cloud only every 12h
|
||||
const serverRecord = await getServerById(server);
|
||||
if (
|
||||
serverRecord?.supportedVersionsUpdatedAt &&
|
||||
moment(new Date()).diff(serverRecord?.supportedVersionsUpdatedAt, 'hours') <= SV_CLOUD_UPDATE_INTERVAL
|
||||
) {
|
||||
return {
|
||||
...jsonRes,
|
||||
success: true
|
||||
};
|
||||
}
|
||||
|
||||
const cloudInfo = await getCloudInfo(server);
|
||||
|
||||
// Makes use of signed JWT to get supported versions
|
||||
const supportedVersionsCloud = verifyJWT(cloudInfo?.signed);
|
||||
|
||||
return {
|
||||
...jsonRes,
|
||||
success: true,
|
||||
supportedVersions: supportedVersionsCloud
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...jsonRes,
|
||||
success: true,
|
||||
supportedVersions
|
||||
};
|
||||
} catch (error) {
|
||||
// Request is successful, but response isn't a json
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e?.message) {
|
||||
if (e.message === 'Aborted') {
|
||||
store.dispatch(selectServerFailure());
|
||||
throw e;
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: e.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
|
||||
const getUniqueId = async (server: string): Promise<string> => {
|
||||
const response = await fetch(`${server}/api/v1/settings.public?query={"_id": "uniqueID"}`);
|
||||
const result = await response.json();
|
||||
return result?.settings?.[0]?.value;
|
||||
};
|
||||
|
||||
export const getCloudInfo = async (domain: string): Promise<TCloudInfo | null> => {
|
||||
const uniqueId = await getUniqueId(domain);
|
||||
const response = await getSupportedVersionsCloud(uniqueId, domain);
|
||||
return response.json() as unknown as TCloudInfo;
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { useDebouncedCallback, Options } from 'use-debounce';
|
||||
|
||||
export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
|
@ -24,6 +24,6 @@ export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
|||
return _debounce;
|
||||
}
|
||||
|
||||
export function useDebounce(func: (...args: any) => any, wait?: number): (...args: any[]) => void {
|
||||
return useDebouncedCallback(func, wait || 1000);
|
||||
export function useDebounce(func: (...args: any) => any, wait?: number, options?: Options): (...args: any[]) => void {
|
||||
return useDebouncedCallback(func, wait || 1000, options);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export const headers: CustomHeaders = {
|
|||
};
|
||||
|
||||
let _basicAuth;
|
||||
export const setBasicAuth = (basicAuth: string): void => {
|
||||
export const setBasicAuth = (basicAuth: string | null): void => {
|
||||
_basicAuth = basicAuth;
|
||||
if (basicAuth) {
|
||||
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
||||
|
|
|
@ -21,6 +21,10 @@ export function getUidDirectMessage(room) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (room.itsMe) {
|
||||
return userId;
|
||||
}
|
||||
|
||||
// legacy method
|
||||
if (!room?.uids && room.rid && room.t === 'd' && userId) {
|
||||
return room.rid.replace(userId, '').trim();
|
||||
|
|
|
@ -3699,6 +3699,7 @@ const emojis: { [key: string]: string } = {
|
|||
':lacrosse:': '🥍',
|
||||
':large_blue_diamond:': '🔷',
|
||||
':large_orange_diamond:': '🔶',
|
||||
':large_blue_circle:': '🔵',
|
||||
':last_quarter_moon:': '🌗',
|
||||
':last_quarter_moon_with_face:': '🌜',
|
||||
':satisfied:': '😆',
|
||||
|
|
|
@ -38,4 +38,6 @@ export * from './parseSettings';
|
|||
export * from './subscribeRooms';
|
||||
export * from './serializeAsciiUrl';
|
||||
export * from './isRoomFederated';
|
||||
export * from './checkSupportedVersions';
|
||||
export * from './getServerInfo';
|
||||
export * from './isImageBase64';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
import { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { InteractionManager } from 'react-native';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -8,7 +7,6 @@ import log from '../methods/helpers/log';
|
|||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
import protectedFunction from '../methods/helpers/protectedFunction';
|
||||
import database from '../database';
|
||||
import { selectServerFailure } from '../../actions/server';
|
||||
import { twoFactor } from './twoFactor';
|
||||
import { store } from '../store/auxStore';
|
||||
import { loginRequest, setLoginServices, setUser } from '../../actions/login';
|
||||
|
@ -19,7 +17,7 @@ import { connectRequest, connectSuccess, disconnect as disconnectAction } from '
|
|||
import { updatePermission } from '../../actions/permissions';
|
||||
import EventEmitter from '../methods/helpers/events';
|
||||
import { updateSettings } from '../../actions/settings';
|
||||
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
||||
import { defaultSettings } from '../constants';
|
||||
import {
|
||||
getSettings,
|
||||
IActiveUsers,
|
||||
|
@ -49,7 +47,7 @@ let notifyAllListener: any;
|
|||
let rolesListener: any;
|
||||
let notifyLoggedListener: any;
|
||||
|
||||
function connect({ server, logoutOnError = false }: { server: string; logoutOnError: boolean }): Promise<void> {
|
||||
function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
if (sdk.current?.client?.host === server) {
|
||||
return resolve();
|
||||
|
@ -401,51 +399,11 @@ function disconnect() {
|
|||
return sdk.disconnect();
|
||||
}
|
||||
|
||||
async function getServerInfo(server: string) {
|
||||
try {
|
||||
const response = await RNFetchBlob.fetch('GET', `${server}/api/info`, { ...RocketChatSettings.customHeaders });
|
||||
try {
|
||||
// Try to resolve as json
|
||||
const jsonRes: { version?: string; success: boolean } = response.json();
|
||||
if (!jsonRes?.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
if (compareServerVersion(jsonRes.version, 'lowerThan', MIN_ROCKETCHAT_VERSION)) {
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Invalid_server_version', {
|
||||
currentVersion: jsonRes.version,
|
||||
minVersion: MIN_ROCKETCHAT_VERSION
|
||||
})
|
||||
};
|
||||
}
|
||||
return jsonRes;
|
||||
} catch (error) {
|
||||
// Request is successful, but response isn't a json
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e?.message) {
|
||||
if (e.message === 'Aborted') {
|
||||
store.dispatch(selectServerFailure());
|
||||
throw e;
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: e.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
|
||||
async function getWebsocketInfo({ server }: { server: string }) {
|
||||
async function getWebsocketInfo({
|
||||
server
|
||||
}: {
|
||||
server: string;
|
||||
}): Promise<{ success: true } | { success: false; message: string }> {
|
||||
const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
||||
|
||||
try {
|
||||
|
@ -530,7 +488,6 @@ export {
|
|||
abort,
|
||||
connect,
|
||||
disconnect,
|
||||
getServerInfo,
|
||||
getWebsocketInfo,
|
||||
stopListener,
|
||||
getLoginServices,
|
||||
|
|
|
@ -1004,3 +1004,6 @@ export const notifyUser = (type: string, params: Record<string, any>): Promise<b
|
|||
sdk.methodCall('stream-notify-user', type, params);
|
||||
|
||||
export const getUsersRoles = (): Promise<boolean> => sdk.methodCall('getUserRoles');
|
||||
|
||||
export const getSupportedVersionsCloud = (uniqueId?: string, domain?: string) =>
|
||||
fetch(`https://releases.rocket.chat/v2/server/supportedVersions?uniqueId=${uniqueId}&domain=${domain}&source=mobile`);
|
||||
|
|
|
@ -24,6 +24,7 @@ import roles from './roles';
|
|||
import videoConf from './videoConf';
|
||||
import usersRoles from './usersRoles';
|
||||
import troubleshootingNotification from './troubleshootingNotification';
|
||||
import supportedVersions from './supportedVersions';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -49,5 +50,6 @@ export default combineReducers({
|
|||
roles,
|
||||
videoConf,
|
||||
usersRoles,
|
||||
troubleshootingNotification
|
||||
troubleshootingNotification,
|
||||
supportedVersions
|
||||
});
|
||||
|
|
|
@ -41,9 +41,10 @@ describe('test server reducer', () => {
|
|||
it('should return modified store after selectServerSucess', () => {
|
||||
const server = 'https://open.rocket.chat/';
|
||||
const version = '4.1.0';
|
||||
mockedStore.dispatch(selectServerSuccess(server, version));
|
||||
const name = 'Rocket.Chat';
|
||||
mockedStore.dispatch(selectServerSuccess({ server, version, name: 'Rocket.Chat' }));
|
||||
const state = mockedStore.getState().server;
|
||||
const manipulated = { ...initialState, server, version, connected: true, loading: false };
|
||||
const manipulated = { ...initialState, server, version, connected: true, loading: false, name };
|
||||
expect(state).toEqual(manipulated);
|
||||
});
|
||||
|
||||
|
@ -61,7 +62,7 @@ describe('test server reducer', () => {
|
|||
});
|
||||
|
||||
it('should return modified store after serverRequestFailure', () => {
|
||||
mockedStore.dispatch(serverFailure('error'));
|
||||
mockedStore.dispatch(serverFailure());
|
||||
const state = mockedStore.getState().server;
|
||||
expect(state.failure).toEqual(true);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface IServer {
|
|||
failure: boolean;
|
||||
server: string;
|
||||
version: string | null;
|
||||
name: string | null;
|
||||
loading: boolean;
|
||||
previousServer: string | null;
|
||||
changingServer: boolean;
|
||||
|
@ -18,6 +19,7 @@ export const initialState: IServer = {
|
|||
failure: false,
|
||||
server: '',
|
||||
version: null,
|
||||
name: null,
|
||||
loading: true,
|
||||
previousServer: null,
|
||||
changingServer: false
|
||||
|
@ -53,6 +55,7 @@ export default function server(state = initialState, action: TActionServer): ISe
|
|||
...state,
|
||||
server: action.server,
|
||||
version: action.version,
|
||||
name: action.name,
|
||||
connecting: false,
|
||||
connected: true,
|
||||
loading: false,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { TSVMessage } from '../definitions';
|
||||
import { setSupportedVersions } from '../actions/supportedVersions';
|
||||
import { mockedStore } from './mockedStore';
|
||||
import { initialState } from './supportedVersions';
|
||||
|
||||
describe('test supportedVersions reducer', () => {
|
||||
test('initial state', () => {
|
||||
const state = mockedStore.getState().supportedVersions;
|
||||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
test('set supported versions', () => {
|
||||
const status = 'supported';
|
||||
const message: TSVMessage = {
|
||||
remainingDays: 15,
|
||||
title: 'title',
|
||||
subtitle: 'subtitle',
|
||||
description: 'description',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
};
|
||||
const i18n = {
|
||||
en: {
|
||||
title: '{{workspace-name}} is running an unsupported version of Rocket.Chat',
|
||||
subtitle: 'Mobile and desktop app access to {{workspace-name}} will be cut off in XX days.',
|
||||
description:
|
||||
'An automatic 30-day warning period has been applied to allow time for a workspace admin to update workspace to a supported software version.'
|
||||
}
|
||||
};
|
||||
mockedStore.dispatch(setSupportedVersions({ status, message, i18n }));
|
||||
const state = mockedStore.getState().supportedVersions;
|
||||
expect(state).toEqual({ status, message, i18n });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { TSVDictionary, TSVMessage, TSVStatus } from '../definitions';
|
||||
import { SUPPORTED_VERSIONS } from '../actions/actionsTypes';
|
||||
import { TActionSupportedVersions } from '../actions/supportedVersions';
|
||||
|
||||
export interface ISupportedVersionsState {
|
||||
status: TSVStatus;
|
||||
message?: TSVMessage;
|
||||
i18n?: TSVDictionary;
|
||||
expiration?: string;
|
||||
}
|
||||
|
||||
export const initialState: ISupportedVersionsState = { status: 'supported' };
|
||||
|
||||
export default (state = initialState, action: TActionSupportedVersions): ISupportedVersionsState => {
|
||||
switch (action.type) {
|
||||
case SUPPORTED_VERSIONS.SET:
|
||||
return {
|
||||
...state,
|
||||
status: action.status,
|
||||
message: action.message,
|
||||
i18n: action.i18n,
|
||||
expiration: action.expiration
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -14,7 +14,7 @@ import { loginRequest } from '../actions/login';
|
|||
import log from '../lib/methods/helpers/log';
|
||||
import { RootEnum } from '../definitions';
|
||||
import { CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||
import { callJitsi, callJitsiWithoutServer, canOpenRoom } from '../lib/methods';
|
||||
import { callJitsi, callJitsiWithoutServer, canOpenRoom, getServerInfo } from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
|
||||
const roomTypes = {
|
||||
|
@ -163,7 +163,7 @@ const handleOpen = function* handleOpen({ params }) {
|
|||
// do nothing?
|
||||
}
|
||||
// if deep link is from a different server
|
||||
const result = yield Services.getServerInfo(host);
|
||||
const result = yield getServerInfo(host);
|
||||
if (!result.success) {
|
||||
// Fallback to prevent the app from being stuck on splash screen
|
||||
yield fallbackNavigation();
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import moment from 'moment';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions/app';
|
||||
import { selectServerRequest, serverFinishAdd } from '../actions/server';
|
||||
|
@ -38,12 +40,40 @@ import {
|
|||
} from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { setUsersRoles } from '../actions/usersRoles';
|
||||
import { getServerById } from '../lib/database/services/Server';
|
||||
import appNavigation from '../lib/navigation/appNavigation';
|
||||
import { showActionSheetRef } from '../containers/ActionSheet';
|
||||
import { SupportedVersionsWarning } from '../containers/SupportedVersions';
|
||||
|
||||
const getServer = state => state.server.server;
|
||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
|
||||
const logoutCall = args => logout(args);
|
||||
|
||||
const showSupportedVersionsWarning = function* showSupportedVersionsWarning(server) {
|
||||
const { status: supportedVersionsStatus } = yield select(state => state.supportedVersions);
|
||||
if (supportedVersionsStatus !== 'warn') {
|
||||
return;
|
||||
}
|
||||
const serverRecord = yield getServerById(server);
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (!serverRecord || moment(new Date()).diff(serverRecord?.supportedVersionsWarningAt, 'hours') <= 12) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serversDB = database.servers;
|
||||
yield serversDB.write(async () => {
|
||||
await serverRecord.update(r => {
|
||||
r.supportedVersionsWarningAt = new Date();
|
||||
});
|
||||
});
|
||||
if (isMasterDetail) {
|
||||
appNavigation.navigate('ModalStackNavigator', { screen: 'SupportedVersionsWarning', params: { showCloseButton: true } });
|
||||
} else {
|
||||
showActionSheetRef({ children: <SupportedVersionsWarning /> });
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginRequest = function* handleLoginRequest({
|
||||
credentials,
|
||||
logoutOnError = false,
|
||||
|
@ -210,6 +240,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
if (inviteLinkToken) {
|
||||
yield put(inviteLinksRequest(inviteLinkToken));
|
||||
}
|
||||
yield showSupportedVersionsWarning(server);
|
||||
yield put(requestTroubleshootingNotification());
|
||||
} catch (e) {
|
||||
log(e);
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
import { put, takeLatest, select } from 'redux-saga/effects';
|
||||
import { Alert } from 'react-native';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import valid from 'semver/functions/valid';
|
||||
import coerce from 'semver/functions/coerce';
|
||||
|
||||
import Navigation from '../lib/navigation/appNavigation';
|
||||
import { SERVER } from '../actions/actionsTypes';
|
||||
import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server';
|
||||
import { clearSettings } from '../actions/settings';
|
||||
import { clearUser, setUser } from '../actions/login';
|
||||
import { clearActiveUsers } from '../actions/activeUsers';
|
||||
import database from '../lib/database';
|
||||
import log, { logServerVersion } from '../lib/methods/helpers/log';
|
||||
import I18n from '../i18n';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../lib/methods/helpers/fetch';
|
||||
import { appStart } from '../actions/app';
|
||||
import UserPreferences from '../lib/methods/userPreferences';
|
||||
import { encryptionStop } from '../actions/encryption';
|
||||
import SSLPinning from '../lib/methods/helpers/sslPinning';
|
||||
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||
import { RootEnum } from '../definitions';
|
||||
import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||
import { getLoginSettings, setCustomEmojis, setEnterpriseModules, setPermissions, setRoles, setSettings } from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { connect } from '../lib/services/connect';
|
||||
|
||||
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||
try {
|
||||
const serverInfo = yield Services.getServerInfo(server);
|
||||
let websocketInfo = { success: true };
|
||||
if (raiseError) {
|
||||
websocketInfo = yield Services.getWebsocketInfo({ server });
|
||||
}
|
||||
if (!serverInfo.success || !websocketInfo.success) {
|
||||
if (raiseError) {
|
||||
const info = serverInfo.success ? websocketInfo : serverInfo;
|
||||
Alert.alert(I18n.t('Oops'), info.message);
|
||||
}
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
let serverVersion = valid(serverInfo.version);
|
||||
if (!serverVersion) {
|
||||
({ version: serverVersion } = coerce(serverInfo.version));
|
||||
}
|
||||
|
||||
const serversDB = database.servers;
|
||||
const serversCollection = serversDB.get('servers');
|
||||
yield serversDB.action(async () => {
|
||||
try {
|
||||
const serverRecord = await serversCollection.find(server);
|
||||
await serverRecord.update(record => {
|
||||
record.version = serverVersion;
|
||||
});
|
||||
} catch (e) {
|
||||
await serversCollection.create(record => {
|
||||
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||
record.version = serverVersion;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return serverInfo;
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
SSLPinning.setCertificate(certificate, server);
|
||||
yield put(inquiryReset());
|
||||
yield put(encryptionStop());
|
||||
yield put(clearActiveUsers());
|
||||
const serversDB = database.servers;
|
||||
const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`);
|
||||
const userCollections = serversDB.get('users');
|
||||
let user = null;
|
||||
if (userId) {
|
||||
try {
|
||||
// search credentials on database
|
||||
const userRecord = yield userCollections.find(userId);
|
||||
user = {
|
||||
id: userRecord.id,
|
||||
token: userRecord.token,
|
||||
username: userRecord.username,
|
||||
name: userRecord.name,
|
||||
language: userRecord.language,
|
||||
status: userRecord.status,
|
||||
statusText: userRecord.statusText,
|
||||
roles: userRecord.roles,
|
||||
avatarETag: userRecord.avatarETag,
|
||||
bio: userRecord.bio,
|
||||
nickname: userRecord.nickname
|
||||
};
|
||||
} catch {
|
||||
// search credentials on shared credentials (Experimental/Official)
|
||||
const token = UserPreferences.getString(`${TOKEN_KEY}-${userId}`);
|
||||
if (token) {
|
||||
user = { token };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${server}`);
|
||||
setBasicAuth(basicAuth);
|
||||
|
||||
if (user) {
|
||||
yield put(clearSettings());
|
||||
yield put(setUser(user));
|
||||
yield connect({ server, logoutOnError: true });
|
||||
yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
|
||||
UserPreferences.setString(CURRENT_SERVER, server); // only set server after have a user
|
||||
} else {
|
||||
yield put(clearUser());
|
||||
yield connect({ server });
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
}
|
||||
|
||||
// We can't use yield here because fetch of Settings & Custom Emojis is slower
|
||||
// and block the selectServerSuccess raising multiples errors
|
||||
setSettings();
|
||||
setCustomEmojis();
|
||||
setPermissions();
|
||||
setRoles();
|
||||
setEnterpriseModules();
|
||||
|
||||
let serverInfo;
|
||||
if (fetchVersion) {
|
||||
serverInfo = yield getServerInfo({ server, raiseError: false });
|
||||
}
|
||||
|
||||
// Return server version even when offline
|
||||
const serverVersion = (serverInfo && serverInfo.version) || version;
|
||||
|
||||
// we'll set serverVersion as metadata for bugsnag
|
||||
logServerVersion(serverVersion);
|
||||
yield put(selectServerSuccess(server, serverVersion));
|
||||
} catch (e) {
|
||||
yield put(selectServerFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
SSLPinning.setCertificate(certificate, server);
|
||||
|
||||
const serverInfo = yield getServerInfo({ server });
|
||||
const serversDB = database.servers;
|
||||
const serversHistoryCollection = serversDB.get('servers_history');
|
||||
|
||||
if (serverInfo) {
|
||||
yield Services.getLoginServices(server);
|
||||
yield getLoginSettings({ server });
|
||||
Navigation.navigate('WorkspaceView');
|
||||
|
||||
const Accounts_iframe_enabled = yield select(state => state.settings.Accounts_iframe_enabled);
|
||||
if (fromServerHistory && !Accounts_iframe_enabled) {
|
||||
Navigation.navigate('LoginView', { username });
|
||||
}
|
||||
|
||||
yield serversDB.action(async () => {
|
||||
try {
|
||||
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
|
||||
if (!serversHistory?.length) {
|
||||
await serversHistoryCollection.create(s => {
|
||||
s.url = server;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(serverFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(SERVER.REQUEST, handleServerRequest);
|
||||
yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer);
|
||||
};
|
||||
export default root;
|
|
@ -0,0 +1,263 @@
|
|||
import { put, takeLatest } from 'redux-saga/effects';
|
||||
import { Alert } from 'react-native';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import valid from 'semver/functions/valid';
|
||||
import coerce from 'semver/functions/coerce';
|
||||
import { call } from 'typed-redux-saga';
|
||||
|
||||
import Navigation from '../lib/navigation/appNavigation';
|
||||
import { SERVER } from '../actions/actionsTypes';
|
||||
import {
|
||||
ISelectServerAction,
|
||||
IServerRequestAction,
|
||||
selectServerFailure,
|
||||
selectServerRequest,
|
||||
selectServerSuccess,
|
||||
serverFailure
|
||||
} from '../actions/server';
|
||||
import { clearSettings } from '../actions/settings';
|
||||
import { clearUser, setUser } from '../actions/login';
|
||||
import { clearActiveUsers } from '../actions/activeUsers';
|
||||
import database from '../lib/database';
|
||||
import log, { logServerVersion } from '../lib/methods/helpers/log';
|
||||
import I18n from '../i18n';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../lib/methods/helpers/fetch';
|
||||
import { appStart } from '../actions/app';
|
||||
import { setSupportedVersions } from '../actions/supportedVersions';
|
||||
import UserPreferences from '../lib/methods/userPreferences';
|
||||
import { encryptionStop } from '../actions/encryption';
|
||||
import SSLPinning from '../lib/methods/helpers/sslPinning';
|
||||
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||
import { IServerInfo, RootEnum, TServerModel } from '../definitions';
|
||||
import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||
import {
|
||||
checkSupportedVersions,
|
||||
getLoginSettings,
|
||||
getServerInfo,
|
||||
setCustomEmojis,
|
||||
setEnterpriseModules,
|
||||
setPermissions,
|
||||
setRoles,
|
||||
setSettings
|
||||
} from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { connect } from '../lib/services/connect';
|
||||
import { appSelector } from '../lib/hooks';
|
||||
import { getServerById } from '../lib/database/services/Server';
|
||||
import { getLoggedUserById } from '../lib/database/services/LoggedUser';
|
||||
|
||||
const getServerVersion = function (version: string | null) {
|
||||
let validVersion = valid(version);
|
||||
if (validVersion) {
|
||||
return validVersion;
|
||||
}
|
||||
const coercedVersion = coerce(version);
|
||||
if (coercedVersion) {
|
||||
validVersion = valid(coercedVersion);
|
||||
}
|
||||
if (validVersion) {
|
||||
return validVersion;
|
||||
}
|
||||
throw new Error('Server version not found');
|
||||
};
|
||||
|
||||
const upsertServer = async function ({ server, serverInfo }: { server: string; serverInfo: IServerInfo }): Promise<TServerModel> {
|
||||
const serversDB = database.servers;
|
||||
const serversCollection = serversDB.get('servers');
|
||||
const serverVersion = getServerVersion(serverInfo.version);
|
||||
const record = await getServerById(server);
|
||||
if (record) {
|
||||
await serversDB.write(async () => {
|
||||
await record.update(r => {
|
||||
r.version = serverVersion;
|
||||
if (serverInfo.supportedVersions) {
|
||||
r.supportedVersions = serverInfo.supportedVersions;
|
||||
r.supportedVersionsUpdatedAt = new Date();
|
||||
}
|
||||
});
|
||||
});
|
||||
return record;
|
||||
}
|
||||
|
||||
let newRecord;
|
||||
await serversDB.write(async () => {
|
||||
newRecord = await serversCollection.create(r => {
|
||||
r._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||
r.version = serverVersion;
|
||||
if (serverInfo.supportedVersions) {
|
||||
r.supportedVersions = serverInfo.supportedVersions;
|
||||
r.supportedVersionsUpdatedAt = new Date();
|
||||
}
|
||||
});
|
||||
});
|
||||
if (newRecord) {
|
||||
return newRecord;
|
||||
}
|
||||
throw new Error('Error creating server record');
|
||||
};
|
||||
|
||||
const getServerInfoSaga = function* getServerInfoSaga({ server, raiseError = true }: { server: string; raiseError?: boolean }) {
|
||||
try {
|
||||
const serverInfoResult = yield* call(getServerInfo, server);
|
||||
if (raiseError) {
|
||||
if (!serverInfoResult.success) {
|
||||
Alert.alert(I18n.t('Oops'), serverInfoResult.message);
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
const websocketInfo = yield* call(Services.getWebsocketInfo, { server });
|
||||
if (!websocketInfo.success) {
|
||||
Alert.alert(I18n.t('Oops'), websocketInfo.message);
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let serverRecord: TServerModel | null;
|
||||
if (serverInfoResult.success) {
|
||||
serverRecord = yield* call(upsertServer, { server, serverInfo: serverInfoResult });
|
||||
} else {
|
||||
serverRecord = yield* call(getServerById, server);
|
||||
}
|
||||
if (!serverRecord) {
|
||||
throw new Error('Server not found');
|
||||
}
|
||||
const supportedVersionsResult = yield* call(checkSupportedVersions, {
|
||||
supportedVersions: serverRecord.supportedVersions,
|
||||
serverVersion: serverRecord.version
|
||||
});
|
||||
yield put(setSupportedVersions(supportedVersionsResult));
|
||||
|
||||
return serverRecord;
|
||||
} catch (e) {
|
||||
log(e);
|
||||
yield put(serverFailure());
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }: ISelectServerAction) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
if (certificate) {
|
||||
SSLPinning?.setCertificate(certificate, server);
|
||||
}
|
||||
yield put(inquiryReset());
|
||||
yield put(encryptionStop());
|
||||
yield put(clearActiveUsers());
|
||||
const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`);
|
||||
let user = null;
|
||||
if (userId) {
|
||||
// search credentials on database
|
||||
const userRecord = yield* call(getLoggedUserById, userId);
|
||||
if (userRecord) {
|
||||
user = {
|
||||
id: userRecord.id,
|
||||
token: userRecord.token,
|
||||
username: userRecord.username,
|
||||
name: userRecord.name,
|
||||
language: userRecord.language,
|
||||
status: userRecord.status,
|
||||
statusText: userRecord.statusText,
|
||||
roles: userRecord.roles,
|
||||
avatarETag: userRecord.avatarETag,
|
||||
bio: userRecord.bio,
|
||||
nickname: userRecord.nickname
|
||||
};
|
||||
} else {
|
||||
const token = UserPreferences.getString(`${TOKEN_KEY}-${userId}`);
|
||||
if (token) {
|
||||
user = { token };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${server}`);
|
||||
setBasicAuth(basicAuth);
|
||||
|
||||
if (user) {
|
||||
yield put(clearSettings());
|
||||
yield put(setUser(user));
|
||||
yield connect({ server, logoutOnError: true });
|
||||
yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
|
||||
UserPreferences.setString(CURRENT_SERVER, server); // only set server after have a user
|
||||
} else {
|
||||
yield put(clearUser());
|
||||
yield connect({ server });
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
}
|
||||
|
||||
// We can't use yield here because fetch of Settings & Custom Emojis is slower
|
||||
// and block the selectServerSuccess raising multiples errors
|
||||
setSettings();
|
||||
setCustomEmojis();
|
||||
setPermissions();
|
||||
setRoles();
|
||||
setEnterpriseModules();
|
||||
|
||||
// We need uniqueId from settings to get cloud info, so setSettings needs to be called first
|
||||
let serverInfo;
|
||||
if (fetchVersion) {
|
||||
serverInfo = yield* getServerInfoSaga({ server, raiseError: false });
|
||||
}
|
||||
|
||||
// Return server version even when offline
|
||||
const serverVersion = (serverInfo && serverInfo.version) || (version as string);
|
||||
|
||||
// we'll set serverVersion as metadata for bugsnag
|
||||
logServerVersion(serverVersion);
|
||||
yield put(selectServerSuccess({ server, version: serverVersion, name: serverInfo?.name || 'Rocket.Chat' }));
|
||||
} catch (e) {
|
||||
yield put(selectServerFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }: IServerRequestAction) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
if (certificate) {
|
||||
SSLPinning?.setCertificate(certificate, server);
|
||||
}
|
||||
|
||||
const serverInfo = yield* getServerInfoSaga({ server });
|
||||
const serversDB = database.servers;
|
||||
const serversHistoryCollection = serversDB.get('servers_history');
|
||||
|
||||
if (serverInfo) {
|
||||
yield Services.getLoginServices(server);
|
||||
yield getLoginSettings({ server });
|
||||
Navigation.navigate('WorkspaceView');
|
||||
|
||||
const Accounts_iframe_enabled = yield* appSelector(state => state.settings.Accounts_iframe_enabled);
|
||||
if (fromServerHistory && !Accounts_iframe_enabled) {
|
||||
Navigation.navigate('LoginView', { username });
|
||||
}
|
||||
|
||||
yield serversDB.write(async () => {
|
||||
try {
|
||||
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
|
||||
if (!serversHistory?.length) {
|
||||
await serversHistoryCollection.create(s => {
|
||||
s.url = server;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(serverFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest<IServerRequestAction>(SERVER.REQUEST, handleServerRequest);
|
||||
yield takeLatest<ISelectServerAction>(SERVER.SELECT_REQUEST, handleSelectServer);
|
||||
};
|
||||
export default root;
|
|
@ -51,7 +51,8 @@ function* onDirectCall(payload: ICallInfo) {
|
|||
const currentCall = calls.find(c => c.callId === payload.callId);
|
||||
const hasAnotherCall = calls.find(c => c.action === 'call');
|
||||
if (hasAnotherCall && hasAnotherCall.callId !== payload.callId) return;
|
||||
if (!currentCall) {
|
||||
const foreground = yield* appSelector(state => state.app.foreground);
|
||||
if (!currentCall && foreground) {
|
||||
yield put(setVideoConfCall(payload));
|
||||
EventEmitter.emit(INAPP_NOTIFICATION_EMITTER, {
|
||||
// @ts-ignore - Component props do not match Event emitter props
|
||||
|
|
|
@ -77,6 +77,7 @@ import {
|
|||
} from './types';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { TNavigation } from '../stackType';
|
||||
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
|
||||
|
@ -186,6 +187,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
<ModalStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} />
|
||||
<ModalStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
|
||||
<ModalStack.Screen name='PushTroubleshootView' component={PushTroubleshootView} />
|
||||
<ModalStack.Screen name='SupportedVersionsWarning' component={SupportedVersionsWarning} />
|
||||
</ModalStack.Navigator>
|
||||
</ModalContainer>
|
||||
);
|
||||
|
|
|
@ -197,6 +197,9 @@ export type ModalStackParamList = {
|
|||
MediaAutoDownloadView: undefined;
|
||||
E2EEncryptionSecurityView: undefined;
|
||||
PushTroubleshootView: undefined;
|
||||
SupportedVersionsWarning: {
|
||||
showCloseButton?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type MasterDetailInsideStackParamList = {
|
||||
|
|
|
@ -50,11 +50,7 @@ const OutsideStackModal = () => {
|
|||
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation, presentation: 'transparentModal' }}
|
||||
>
|
||||
<OutsideModal.Screen name='OutsideStack' component={OutsideStack} options={{ headerShown: false }} />
|
||||
<OutsideModal.Screen
|
||||
name='AuthenticationWebView'
|
||||
component={AuthenticationWebView}
|
||||
options={AuthenticationWebView.navigationOptions}
|
||||
/>
|
||||
<OutsideModal.Screen name='AuthenticationWebView' component={AuthenticationWebView} />
|
||||
</OutsideModal.Navigator>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -69,6 +69,7 @@ export type ChatsStackParamList = {
|
|||
t: SubscriptionType;
|
||||
showCloseModal?: boolean;
|
||||
fromRid?: string;
|
||||
itsMe?: boolean;
|
||||
};
|
||||
RoomInfoEditView: {
|
||||
rid: string;
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import React from 'react';
|
||||
import { WebView, WebViewNavigation } from 'react-native-webview';
|
||||
import { connect } from 'react-redux';
|
||||
import parse from 'url-parse';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes';
|
||||
import { RouteProp } from '@react-navigation/core';
|
||||
import { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import { WebView, WebViewNavigation } from 'react-native-webview';
|
||||
import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes';
|
||||
import parse from 'url-parse';
|
||||
|
||||
import { OutsideModalParamList } from '../stacks/types';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { userAgent } from '../lib/constants';
|
||||
import { debounce } from '../lib/methods/helpers';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { ICredentials } from '../definitions';
|
||||
import { userAgent } from '../lib/constants';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
import { useDebounce } from '../lib/methods/helpers';
|
||||
import { Services } from '../lib/services';
|
||||
import { IApplicationState, ICredentials } from '../definitions';
|
||||
import { OutsideModalParamList } from '../stacks/types';
|
||||
|
||||
// iframe uses a postMessage to send the token to the client
|
||||
// We'll handle this sending the token to the hash of the window.location
|
||||
|
@ -40,95 +40,56 @@ window.addEventListener('popstate', function() {
|
|||
});
|
||||
`;
|
||||
|
||||
interface INavigationOption {
|
||||
navigation: StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
||||
route: RouteProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
||||
}
|
||||
const AuthenticationWebView = () => {
|
||||
const [logging, setLogging] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
interface IAuthenticationWebView extends INavigationOption {
|
||||
server: string;
|
||||
Accounts_Iframe_api_url: string;
|
||||
Accounts_Iframe_api_method: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
const navigation = useNavigation<StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||
const {
|
||||
params: { authType, url, ssoToken }
|
||||
} = useRoute<RouteProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||
|
||||
interface IState {
|
||||
logging: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
const { Accounts_Iframe_api_method, Accounts_Iframe_api_url, server } = useAppSelector(state => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
||||
}));
|
||||
|
||||
class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView, IState> {
|
||||
private oauthRedirectRegex: RegExp;
|
||||
private iframeRedirectRegex: RegExp;
|
||||
const oauthRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||
const iframeRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(event|loginToken|token))`, 'g');
|
||||
|
||||
static navigationOptions = ({ route, navigation }: INavigationOption) => {
|
||||
const { authType } = route.params;
|
||||
return {
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
};
|
||||
};
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
const debouncedLogin = useDebounce((params: ICredentials) => login(params), 3000);
|
||||
|
||||
constructor(props: IAuthenticationWebView) {
|
||||
super(props);
|
||||
this.state = {
|
||||
logging: false,
|
||||
loading: false
|
||||
};
|
||||
this.oauthRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||
this.iframeRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(event|loginToken|token))`, 'g');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.debouncedLogin && this.debouncedLogin.stop) {
|
||||
this.debouncedLogin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
dismiss = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.pop();
|
||||
};
|
||||
|
||||
login = (params: ICredentials) => {
|
||||
const { logging } = this.state;
|
||||
const login = (params: ICredentials) => {
|
||||
if (logging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ logging: true });
|
||||
|
||||
setLogging(true);
|
||||
try {
|
||||
Services.loginOAuthOrSso(params);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
this.setState({ logging: false });
|
||||
this.dismiss();
|
||||
setLogging(false);
|
||||
navigation.pop();
|
||||
};
|
||||
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
debouncedLogin = debounce((params: ICredentials) => this.login(params), 3000);
|
||||
|
||||
tryLogin = debounce(
|
||||
const tryLogin = useDebounce(
|
||||
async () => {
|
||||
const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props;
|
||||
const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json());
|
||||
const resume = data?.login || data?.loginToken;
|
||||
if (resume) {
|
||||
this.login({ resume });
|
||||
login({ resume });
|
||||
}
|
||||
},
|
||||
3000,
|
||||
true
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||
const onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||
const url = decodeURIComponent(webViewState.url);
|
||||
const { route } = this.props;
|
||||
const { authType } = route.params;
|
||||
if (authType === 'saml' || authType === 'cas') {
|
||||
const { ssoToken } = route.params;
|
||||
const parsedUrl = parse(url, true);
|
||||
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||
|
@ -140,28 +101,28 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
} else {
|
||||
payload = { cas: { credentialToken: ssoToken } };
|
||||
}
|
||||
this.debouncedLogin(payload);
|
||||
debouncedLogin(payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (authType === 'oauth') {
|
||||
if (this.oauthRedirectRegex.test(url)) {
|
||||
if (oauthRedirectRegex.test(url)) {
|
||||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
this.debouncedLogin({ oauth: { ...credentials } });
|
||||
debouncedLogin({ oauth: { ...credentials } });
|
||||
}
|
||||
}
|
||||
|
||||
if (authType === 'iframe') {
|
||||
if (this.iframeRedirectRegex.test(url)) {
|
||||
if (iframeRedirectRegex.test(url)) {
|
||||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
switch (credentials.event) {
|
||||
case 'try-iframe-login':
|
||||
this.tryLogin();
|
||||
tryLogin();
|
||||
break;
|
||||
case 'login-with-token':
|
||||
this.debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
||||
debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
|
@ -170,39 +131,31 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading } = this.state;
|
||||
const { route } = this.props;
|
||||
const { url, authType } = route.params;
|
||||
const isIframe = authType === 'iframe';
|
||||
const isIframe = authType === 'iframe';
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
source={{ uri: url }}
|
||||
userAgent={userAgent}
|
||||
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
|
||||
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
injectedJavaScript={isIframe ? injectedJavaScript : undefined}
|
||||
onLoadStart={() => {
|
||||
this.setState({ loading: true });
|
||||
}}
|
||||
onLoadEnd={() => {
|
||||
this.setState({ loading: false });
|
||||
}}
|
||||
/>
|
||||
{loading ? <ActivityIndicator size='large' absolute /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => <HeaderButton.CloseModal />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
});
|
||||
}, [authType, navigation]);
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
source={{ uri: url }}
|
||||
userAgent={userAgent}
|
||||
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
|
||||
onMessage={({ nativeEvent }) => onNavigationStateChange(nativeEvent)}
|
||||
onNavigationStateChange={onNavigationStateChange}
|
||||
injectedJavaScript={isIframe ? injectedJavaScript : undefined}
|
||||
onLoadStart={() => setLoading(true)}
|
||||
onLoadEnd={() => setLoading(false)}
|
||||
/>
|
||||
{loading ? <ActivityIndicator size='large' absolute /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthenticationWebView));
|
||||
export default AuthenticationWebView;
|
||||
|
|
|
@ -1,264 +0,0 @@
|
|||
import { dequal } from 'dequal';
|
||||
import React from 'react';
|
||||
import { Alert, Keyboard, StyleSheet, Text, View, TextInput as RNTextInput } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { loginRequest } from '../actions/login';
|
||||
import { themes } from '../lib/constants';
|
||||
import Button from '../containers/Button';
|
||||
import FormContainer, { FormContainerInner } from '../containers/FormContainer';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import LoginServices from '../containers/LoginServices';
|
||||
import { FormTextInput } from '../containers/TextInput';
|
||||
import { IApplicationState, IBaseScreen } from '../definitions';
|
||||
import I18n from '../i18n';
|
||||
import { OutsideParamList } from '../stacks/types';
|
||||
import { withTheme } from '../theme';
|
||||
import sharedStyles from './Styles';
|
||||
import UGCRules from '../containers/UserGeneratedContentRules';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
registerDisabled: {
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textAlignCenter,
|
||||
fontSize: 16
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textBold,
|
||||
fontSize: 22
|
||||
},
|
||||
inputContainer: {
|
||||
marginVertical: 16
|
||||
},
|
||||
bottomContainer: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
bottomContainerText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 13
|
||||
},
|
||||
bottomContainerTextBold: {
|
||||
...sharedStyles.textSemibold,
|
||||
fontSize: 13
|
||||
},
|
||||
loginButton: {
|
||||
marginTop: 16
|
||||
},
|
||||
ugcContainer: {
|
||||
marginTop: 32
|
||||
}
|
||||
});
|
||||
|
||||
interface ILoginViewProps extends IBaseScreen<OutsideParamList, 'LoginView'> {
|
||||
Site_Name: string;
|
||||
Accounts_RegistrationForm: string;
|
||||
Accounts_RegistrationForm_LinkReplacementText: string;
|
||||
Accounts_EmailOrUsernamePlaceholder: string;
|
||||
Accounts_PasswordPlaceholder: string;
|
||||
Accounts_PasswordReset: boolean;
|
||||
Accounts_ShowFormLogin: boolean;
|
||||
isFetching: boolean;
|
||||
error: {
|
||||
error: string;
|
||||
};
|
||||
failure: boolean;
|
||||
loginRequest: Function;
|
||||
inviteLinkToken: string;
|
||||
}
|
||||
|
||||
interface ILoginViewState {
|
||||
user: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
class LoginView extends React.Component<ILoginViewProps, ILoginViewState> {
|
||||
private passwordInput: RNTextInput | null | undefined;
|
||||
|
||||
static navigationOptions = ({ route, navigation }: ILoginViewProps) => ({
|
||||
title: route?.params?.title ?? 'Rocket.Chat',
|
||||
headerRight: () => <HeaderButton.Legal testID='login-view-more' navigation={navigation} />
|
||||
});
|
||||
|
||||
constructor(props: ILoginViewProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
user: props.route.params?.username ?? '',
|
||||
password: ''
|
||||
};
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: ILoginViewProps) {
|
||||
const { error } = this.props;
|
||||
if (nextProps.failure && !dequal(error, nextProps.error)) {
|
||||
if (nextProps.error?.error === 'error-invalid-email') {
|
||||
this.resendEmailConfirmation();
|
||||
} else {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get showRegistrationButton() {
|
||||
const { Accounts_RegistrationForm, inviteLinkToken } = this.props;
|
||||
return Accounts_RegistrationForm === 'Public' || (Accounts_RegistrationForm === 'Secret URL' && inviteLinkToken?.length);
|
||||
}
|
||||
|
||||
login = () => {
|
||||
const { navigation, Site_Name } = this.props;
|
||||
navigation.navigate('LoginView', { title: Site_Name });
|
||||
};
|
||||
|
||||
register = () => {
|
||||
const { navigation, Site_Name } = this.props;
|
||||
navigation.navigate('RegisterView', { title: Site_Name });
|
||||
};
|
||||
|
||||
forgotPassword = () => {
|
||||
const { navigation, Site_Name } = this.props;
|
||||
navigation.navigate('ForgotPasswordView', { title: Site_Name });
|
||||
};
|
||||
|
||||
resendEmailConfirmation = () => {
|
||||
const { user } = this.state;
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('SendEmailConfirmationView', { user });
|
||||
};
|
||||
|
||||
valid = () => {
|
||||
const { user, password } = this.state;
|
||||
return user.trim() && password.trim();
|
||||
};
|
||||
|
||||
submit = () => {
|
||||
if (!this.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { user, password } = this.state;
|
||||
const { dispatch } = this.props;
|
||||
Keyboard.dismiss();
|
||||
dispatch(loginRequest({ user, password }));
|
||||
};
|
||||
|
||||
renderUserForm = () => {
|
||||
const { user } = this.state;
|
||||
const {
|
||||
Accounts_EmailOrUsernamePlaceholder,
|
||||
Accounts_PasswordPlaceholder,
|
||||
Accounts_PasswordReset,
|
||||
Accounts_RegistrationForm_LinkReplacementText,
|
||||
isFetching,
|
||||
theme,
|
||||
Accounts_ShowFormLogin
|
||||
} = this.props;
|
||||
|
||||
if (!Accounts_ShowFormLogin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style={[styles.title, sharedStyles.textBold, { color: themes[theme].titleText }]}>{I18n.t('Login')}</Text>
|
||||
<FormTextInput
|
||||
label={I18n.t('Username_or_email')}
|
||||
containerStyle={styles.inputContainer}
|
||||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='next'
|
||||
onChangeText={(value: string) => this.setState({ user: value })}
|
||||
onSubmitEditing={() => {
|
||||
this.passwordInput?.focus();
|
||||
}}
|
||||
testID='login-view-email'
|
||||
textContentType='username'
|
||||
autoComplete='username'
|
||||
value={user}
|
||||
/>
|
||||
<FormTextInput
|
||||
label={I18n.t('Password')}
|
||||
containerStyle={styles.inputContainer}
|
||||
inputRef={e => {
|
||||
this.passwordInput = e;
|
||||
}}
|
||||
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
returnKeyType='send'
|
||||
secureTextEntry
|
||||
onSubmitEditing={this.submit}
|
||||
onChangeText={(value: string) => this.setState({ password: value })}
|
||||
testID='login-view-password'
|
||||
textContentType='password'
|
||||
autoComplete='password'
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Login')}
|
||||
type='primary'
|
||||
onPress={this.submit}
|
||||
testID='login-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={!this.valid()}
|
||||
style={styles.loginButton}
|
||||
/>
|
||||
{Accounts_PasswordReset && (
|
||||
<Button
|
||||
title={I18n.t('Forgot_password')}
|
||||
type='secondary'
|
||||
onPress={this.forgotPassword}
|
||||
testID='login-view-forgot-password'
|
||||
color={themes[theme].auxiliaryText}
|
||||
fontSize={14}
|
||||
/>
|
||||
)}
|
||||
{this.showRegistrationButton ? (
|
||||
<View style={styles.bottomContainer}>
|
||||
<Text style={[styles.bottomContainerText, { color: themes[theme].auxiliaryText }]}>
|
||||
{I18n.t('Dont_Have_An_Account')}
|
||||
</Text>
|
||||
<Text
|
||||
style={[styles.bottomContainerTextBold, { color: themes[theme].actionTintColor }]}
|
||||
onPress={this.register}
|
||||
testID='login-view-register'
|
||||
>
|
||||
{I18n.t('Create_account')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[styles.registerDisabled, { color: themes[theme].auxiliaryText }]}>
|
||||
{Accounts_RegistrationForm_LinkReplacementText}
|
||||
</Text>
|
||||
)}
|
||||
<UGCRules styleContainer={styles.ugcContainer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { Accounts_ShowFormLogin } = this.props;
|
||||
|
||||
return (
|
||||
<FormContainer testID='login-view'>
|
||||
<FormContainerInner>
|
||||
<LoginServices separator={Accounts_ShowFormLogin} />
|
||||
{this.renderUserForm()}
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
server: state.server.server,
|
||||
Site_Name: state.settings.Site_Name as string,
|
||||
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin as boolean,
|
||||
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm as string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText as string,
|
||||
isFetching: state.login.isFetching,
|
||||
failure: state.login.failure,
|
||||
error: state.login.error && state.login.error.data,
|
||||
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder as string,
|
||||
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder as string,
|
||||
Accounts_PasswordReset: state.settings.Accounts_PasswordReset as boolean,
|
||||
inviteLinkToken: state.inviteLinks.token
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(LoginView));
|
|
@ -0,0 +1,171 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { Keyboard, Text, View, Alert } from 'react-native';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import * as yup from 'yup';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
import { loginRequest } from '../../actions/login';
|
||||
import Button from '../../containers/Button';
|
||||
import { ControlledFormTextInput } from '../../containers/TextInput';
|
||||
import I18n from '../../i18n';
|
||||
import { OutsideParamList } from '../../stacks/types';
|
||||
import { useTheme } from '../../theme';
|
||||
import sharedStyles from '../Styles';
|
||||
import UGCRules from '../../containers/UserGeneratedContentRules';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import styles from './styles';
|
||||
|
||||
interface ISubmit {
|
||||
user: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const schema = yup.object().shape({
|
||||
user: yup.string().required(),
|
||||
password: yup.string().required()
|
||||
});
|
||||
|
||||
const UserForm = () => {
|
||||
const { colors } = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const navigation = useNavigation<StackNavigationProp<OutsideParamList, 'LoginView'>>();
|
||||
|
||||
const {
|
||||
params: { username }
|
||||
} = useRoute<RouteProp<OutsideParamList, 'LoginView'>>();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { isValid },
|
||||
getValues,
|
||||
setFocus
|
||||
} = useForm<ISubmit>({ mode: 'onChange', resolver: yupResolver(schema), defaultValues: { user: username || '' } });
|
||||
|
||||
const {
|
||||
Accounts_EmailOrUsernamePlaceholder,
|
||||
Accounts_PasswordPlaceholder,
|
||||
Accounts_PasswordReset,
|
||||
Accounts_RegistrationForm_LinkReplacementText,
|
||||
isFetching,
|
||||
Accounts_RegistrationForm,
|
||||
Site_Name,
|
||||
inviteLinkToken,
|
||||
error,
|
||||
failure
|
||||
} = useAppSelector(state => ({
|
||||
Accounts_RegistrationForm: state.settings.Accounts_RegistrationForm as string,
|
||||
Accounts_RegistrationForm_LinkReplacementText: state.settings.Accounts_RegistrationForm_LinkReplacementText as string,
|
||||
isFetching: state.login.isFetching,
|
||||
Accounts_EmailOrUsernamePlaceholder: state.settings.Accounts_EmailOrUsernamePlaceholder as string,
|
||||
Accounts_PasswordPlaceholder: state.settings.Accounts_PasswordPlaceholder as string,
|
||||
Accounts_PasswordReset: state.settings.Accounts_PasswordReset as boolean,
|
||||
Site_Name: state.settings.Site_Name as string,
|
||||
inviteLinkToken: state.inviteLinks.token,
|
||||
failure: state.login.failure,
|
||||
error: state.login.error && state.login.error.data
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (failure) {
|
||||
if (error?.error === 'error-invalid-email') {
|
||||
const user = getValues('user');
|
||||
navigation.navigate('SendEmailConfirmationView', { user });
|
||||
} else {
|
||||
Alert.alert(I18n.t('Oops'), I18n.t('Login_error'));
|
||||
}
|
||||
}
|
||||
}, [error?.error, failure, getValues, navigation]);
|
||||
|
||||
const showRegistrationButton =
|
||||
Accounts_RegistrationForm === 'Public' || (Accounts_RegistrationForm === 'Secret URL' && inviteLinkToken?.length);
|
||||
|
||||
const register = () => {
|
||||
navigation.navigate('RegisterView', { title: Site_Name });
|
||||
};
|
||||
|
||||
const forgotPassword = () => {
|
||||
navigation.navigate('ForgotPasswordView', { title: Site_Name });
|
||||
};
|
||||
|
||||
const submit = ({ password, user }: ISubmit) => {
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
Keyboard.dismiss();
|
||||
dispatch(loginRequest({ user, password }));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Text style={[styles.title, sharedStyles.textBold, { color: colors.titleText }]}>{I18n.t('Login')}</Text>
|
||||
<ControlledFormTextInput
|
||||
name='user'
|
||||
control={control}
|
||||
label={I18n.t('Username_or_email')}
|
||||
containerStyle={styles.inputContainer}
|
||||
placeholder={Accounts_EmailOrUsernamePlaceholder || I18n.t('Username_or_email')}
|
||||
keyboardType='email-address'
|
||||
returnKeyType='next'
|
||||
onSubmitEditing={() => setFocus('password')}
|
||||
testID='login-view-email'
|
||||
textContentType='username'
|
||||
autoComplete='username'
|
||||
/>
|
||||
<ControlledFormTextInput
|
||||
name='password'
|
||||
control={control}
|
||||
label={I18n.t('Password')}
|
||||
containerStyle={styles.inputContainer}
|
||||
placeholder={Accounts_PasswordPlaceholder || I18n.t('Password')}
|
||||
returnKeyType='send'
|
||||
secureTextEntry
|
||||
onSubmitEditing={handleSubmit(submit)}
|
||||
testID='login-view-password'
|
||||
textContentType='password'
|
||||
autoComplete='password'
|
||||
/>
|
||||
<Button
|
||||
title={I18n.t('Login')}
|
||||
type='primary'
|
||||
onPress={handleSubmit(submit)}
|
||||
testID='login-view-submit'
|
||||
loading={isFetching}
|
||||
disabled={!isValid}
|
||||
style={styles.loginButton}
|
||||
/>
|
||||
{Accounts_PasswordReset ? (
|
||||
<Button
|
||||
title={I18n.t('Forgot_password')}
|
||||
type='secondary'
|
||||
onPress={forgotPassword}
|
||||
testID='login-view-forgot-password'
|
||||
color={colors.auxiliaryText}
|
||||
fontSize={14}
|
||||
/>
|
||||
) : null}
|
||||
{showRegistrationButton ? (
|
||||
<View style={styles.bottomContainer}>
|
||||
<Text style={[styles.bottomContainerText, { color: colors.auxiliaryText }]}>{I18n.t('Dont_Have_An_Account')}</Text>
|
||||
<Text
|
||||
style={[styles.bottomContainerTextBold, { color: colors.actionTintColor }]}
|
||||
onPress={register}
|
||||
testID='login-view-register'
|
||||
>
|
||||
{I18n.t('Create_account')}
|
||||
</Text>
|
||||
</View>
|
||||
) : (
|
||||
<Text style={[styles.registerDisabled, { color: colors.auxiliaryText }]}>
|
||||
{Accounts_RegistrationForm_LinkReplacementText}
|
||||
</Text>
|
||||
)}
|
||||
<UGCRules styleContainer={styles.ugcContainer} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserForm;
|
|
@ -0,0 +1,40 @@
|
|||
import React, { useLayoutEffect } from 'react';
|
||||
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import FormContainer, { FormContainerInner } from '../../containers/FormContainer';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import LoginServices from '../../containers/LoginServices';
|
||||
import { OutsideParamList } from '../../stacks/types';
|
||||
import UserForm from './UserForm';
|
||||
|
||||
const LoginView = () => {
|
||||
const navigation = useNavigation<StackNavigationProp<OutsideParamList, 'LoginView'>>();
|
||||
|
||||
const {
|
||||
params: { title }
|
||||
} = useRoute<RouteProp<OutsideParamList, 'LoginView'>>();
|
||||
|
||||
const { Accounts_ShowFormLogin } = useAppSelector(state => ({
|
||||
Accounts_ShowFormLogin: state.settings.Accounts_ShowFormLogin as boolean
|
||||
}));
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
title: title ?? 'Rocket.Chat',
|
||||
headerRight: () => <HeaderButton.Legal testID='login-view-more' navigation={navigation} />
|
||||
});
|
||||
}, [navigation, title]);
|
||||
|
||||
return (
|
||||
<FormContainer testID='login-view'>
|
||||
<FormContainerInner>
|
||||
<LoginServices separator={Accounts_ShowFormLogin} />
|
||||
{Accounts_ShowFormLogin ? <UserForm /> : null}
|
||||
</FormContainerInner>
|
||||
</FormContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginView;
|
|
@ -0,0 +1,36 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../Styles';
|
||||
|
||||
export default StyleSheet.create({
|
||||
registerDisabled: {
|
||||
...sharedStyles.textRegular,
|
||||
...sharedStyles.textAlignCenter,
|
||||
fontSize: 16
|
||||
},
|
||||
title: {
|
||||
...sharedStyles.textBold,
|
||||
fontSize: 22
|
||||
},
|
||||
inputContainer: {
|
||||
marginVertical: 16
|
||||
},
|
||||
bottomContainer: {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
bottomContainerText: {
|
||||
...sharedStyles.textRegular,
|
||||
fontSize: 13
|
||||
},
|
||||
bottomContainerTextBold: {
|
||||
...sharedStyles.textSemibold,
|
||||
fontSize: 13
|
||||
},
|
||||
loginButton: {
|
||||
marginTop: 16
|
||||
},
|
||||
ugcContainer: {
|
||||
marginTop: 32
|
||||
}
|
||||
});
|
|
@ -126,10 +126,7 @@ class MessagesView extends React.Component<IMessagesViewProps, IMessagesViewStat
|
|||
};
|
||||
|
||||
navToRoomInfo = (navParam: IRoomInfoParam) => {
|
||||
const { navigation, user } = this.props;
|
||||
if (navParam.rid === user.id) {
|
||||
return;
|
||||
}
|
||||
const { navigation } = this.props;
|
||||
navigation.navigate('RoomInfoView', navParam);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,40 +1,38 @@
|
|||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import React from 'react';
|
||||
import { Keyboard, ScrollView, TextInput, View } from 'react-native';
|
||||
import { connect } from 'react-redux';
|
||||
import { sha256 } from 'js-sha256';
|
||||
import RNPickerSelect from 'react-native-picker-select';
|
||||
import { dequal } from 'dequal';
|
||||
import omit from 'lodash/omit';
|
||||
import { StackNavigationOptions } from '@react-navigation/stack';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Touch from '../../containers/Touch';
|
||||
import KeyboardView from '../../containers/KeyboardView';
|
||||
import sharedStyles from '../Styles';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import { showErrorAlert, showConfirmationAlert, compareServerVersion } from '../../lib/methods/helpers';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { FormTextInput } from '../../containers/TextInput';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import I18n from '../../i18n';
|
||||
import Button from '../../containers/Button';
|
||||
import { AvatarWithEdit } from '../../containers/Avatar';
|
||||
import { setUser } from '../../actions/login';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import styles from './styles';
|
||||
import { ProfileStackParamList } from '../../stacks/types';
|
||||
import { Services } from '../../lib/services';
|
||||
import { IApplicationState, IAvatarButton, IBaseScreen, IProfileParams, IUser } from '../../definitions';
|
||||
import { twoFactor } from '../../lib/services/twoFactor';
|
||||
import { TwoFactorMethods } from '../../definitions/ITotp';
|
||||
import { withActionSheet, IActionSheetProvider } from '../../containers/ActionSheet';
|
||||
import { DeleteAccountActionSheetContent } from './components/DeleteAccountActionSheetContent';
|
||||
import { IActionSheetProvider, withActionSheet } from '../../containers/ActionSheet';
|
||||
import ActionSheetContentWithInputAndSubmit from '../../containers/ActionSheet/ActionSheetContentWithInputAndSubmit';
|
||||
import { AvatarWithEdit } from '../../containers/Avatar';
|
||||
import Button from '../../containers/Button';
|
||||
import * as HeaderButton from '../../containers/HeaderButton';
|
||||
import KeyboardView from '../../containers/KeyboardView';
|
||||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import StatusBar from '../../containers/StatusBar';
|
||||
import { FormTextInput } from '../../containers/TextInput';
|
||||
import { LISTENER } from '../../containers/Toast';
|
||||
import Touch from '../../containers/Touch';
|
||||
import { IApplicationState, IAvatarButton, IBaseScreen, IProfileParams, IUser } from '../../definitions';
|
||||
import { TwoFactorMethods } from '../../definitions/ITotp';
|
||||
import I18n from '../../i18n';
|
||||
import { themes } from '../../lib/constants';
|
||||
import { compareServerVersion, showConfirmationAlert, showErrorAlert } from '../../lib/methods/helpers';
|
||||
import EventEmitter from '../../lib/methods/helpers/events';
|
||||
import { events, logEvent } from '../../lib/methods/helpers/log';
|
||||
import scrollPersistTaps from '../../lib/methods/helpers/scrollPersistTaps';
|
||||
import { Services } from '../../lib/services';
|
||||
import { twoFactor } from '../../lib/services/twoFactor';
|
||||
import { getUserSelector } from '../../selectors/login';
|
||||
import { ProfileStackParamList } from '../../stacks/types';
|
||||
import { TSupportedThemes, withTheme } from '../../theme';
|
||||
import sharedStyles from '../Styles';
|
||||
import { DeleteAccountActionSheetContent } from './components/DeleteAccountActionSheetContent';
|
||||
import styles from './styles';
|
||||
|
||||
// https://github.com/RocketChat/Rocket.Chat/blob/174c28d40b3d5a52023ee2dca2e81dd77ff33fa5/apps/meteor/app/lib/server/functions/saveUser.js#L24-L25
|
||||
const MAX_BIO_LENGTH = 260;
|
||||
|
@ -81,6 +79,7 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
private newPassword?: TextInput | null;
|
||||
private nickname?: TextInput | null;
|
||||
private bio?: TextInput | null;
|
||||
private focusListener = () => {};
|
||||
|
||||
setHeader = () => {
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
|
@ -116,20 +115,13 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.init();
|
||||
this.focusListener = this.props.navigation.addListener('focus', () => {
|
||||
this.init();
|
||||
});
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps: IProfileViewProps) {
|
||||
const { user } = this.props;
|
||||
/*
|
||||
* We need to ignore status because on Android ImagePicker
|
||||
* changes the activity, so, the user status changes and
|
||||
* it's resetting the avatar right after
|
||||
* select some image from gallery.
|
||||
*/
|
||||
if (!dequal(omit(user, ['status']), omit(nextProps.user, ['status']))) {
|
||||
this.init(nextProps.user);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
this.focusListener();
|
||||
}
|
||||
|
||||
init = (user?: IUser) => {
|
||||
|
@ -260,11 +252,12 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
}
|
||||
if (customFields) {
|
||||
dispatch(setUser({ customFields, ...params }));
|
||||
this.setState({ ...this.state, customFields, ...params });
|
||||
} else {
|
||||
dispatch(setUser({ ...params }));
|
||||
this.setState({ ...this.state, ...params });
|
||||
}
|
||||
EventEmitter.emit(LISTENER, { message: I18n.t('Profile_saved_successfully') });
|
||||
this.init();
|
||||
}
|
||||
this.setState({ saving: false, currentPassword: null, twoFactorCode: null });
|
||||
} catch (e: any) {
|
||||
|
@ -306,7 +299,13 @@ class ProfileView extends React.Component<IProfileViewProps, IProfileViewState>
|
|||
if (I18n.isTranslated(e.error)) {
|
||||
return showErrorAlert(I18n.t(e.error));
|
||||
}
|
||||
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t(action) }));
|
||||
let msg = I18n.t('There_was_an_error_while_action', { action: I18n.t(action) });
|
||||
let title = '';
|
||||
if (typeof e.reason === 'string') {
|
||||
title = msg;
|
||||
msg = e.reason;
|
||||
}
|
||||
showErrorAlert(msg, title);
|
||||
};
|
||||
|
||||
handleEditAvatar = () => {
|
||||
|
|
|
@ -75,15 +75,14 @@ export const RoomInfoButtons = ({
|
|||
}: IRoomInfoButtons): React.ReactElement => {
|
||||
const room = roomFromRid || roomFromProps;
|
||||
// Following the web behavior, when is a DM with myself, shouldn't appear block or ignore option
|
||||
const isDmWithMyself = room?.uids && room.uids?.filter((uid: string) => uid !== roomUserId).length === 0;
|
||||
|
||||
const isDmWithMyself = room?.uids?.filter((uid: string) => uid !== roomUserId).length === 0;
|
||||
const isFromDm = room?.t === SubscriptionType.DIRECT;
|
||||
const isDirectFromSaved = isDirect && fromRid && room;
|
||||
const isIgnored = room?.ignored?.includes?.(roomUserId || '');
|
||||
const isBlocked = room?.blocker;
|
||||
|
||||
const renderIgnoreUser = isDirectFromSaved && !isFromDm && !isDmWithMyself;
|
||||
const renderBlockUser = isDirectFromSaved && isFromDm;
|
||||
const renderBlockUser = isDirectFromSaved && isFromDm && !isDmWithMyself;
|
||||
|
||||
return (
|
||||
<View style={styles.roomButtonsContainer}>
|
||||
|
|
|
@ -39,7 +39,7 @@ type TRoomInfoViewRouteProp = RouteProp<ChatsStackParamList, 'RoomInfoView'>;
|
|||
|
||||
const RoomInfoView = (): React.ReactElement => {
|
||||
const {
|
||||
params: { rid, t, fromRid, member, room: roomParam, showCloseModal }
|
||||
params: { rid, t, fromRid, member, room: roomParam, showCloseModal, itsMe }
|
||||
} = useRoute<TRoomInfoViewRouteProp>();
|
||||
const { addListener, setOptions, navigate, goBack } = useNavigation<TRoomInfoViewNavigationProp>();
|
||||
|
||||
|
@ -157,7 +157,7 @@ const RoomInfoView = (): React.ReactElement => {
|
|||
const loadUser = async () => {
|
||||
if (isEmpty(roomUser)) {
|
||||
try {
|
||||
const roomUserId = getUidDirectMessage(room || { rid, t });
|
||||
const roomUserId = getUidDirectMessage(room || { rid, t, itsMe });
|
||||
const result = await Services.getUserInfo(roomUserId);
|
||||
if (result.success) {
|
||||
const { user } = result;
|
||||
|
|
|
@ -1133,13 +1133,9 @@ class RoomView extends React.Component<IRoomViewProps, IRoomViewState> {
|
|||
};
|
||||
|
||||
navToRoomInfo = (navParam: any) => {
|
||||
const { navigation, user, isMasterDetail } = this.props;
|
||||
const { navigation, isMasterDetail } = this.props;
|
||||
const { room } = this.state;
|
||||
|
||||
logEvent(events[`ROOM_GO_${navParam.t === 'd' ? 'USER' : 'ROOM'}_INFO`]);
|
||||
if (navParam.rid === user.id) {
|
||||
return;
|
||||
}
|
||||
navParam.fromRid = room.rid;
|
||||
if (isMasterDetail) {
|
||||
navParam.showCloseModal = true;
|
||||
|
|
|
@ -6,6 +6,7 @@ import sharedStyles from '../../Styles';
|
|||
import { CustomIcon } from '../../../containers/CustomIcon';
|
||||
import { useTheme } from '../../../theme';
|
||||
import SearchHeader from '../../../containers/SearchHeader';
|
||||
import { useAppSelector } from '../../../lib/hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -54,13 +55,16 @@ const Header = React.memo(
|
|||
onSearchChangeText,
|
||||
onPress
|
||||
}: IRoomHeader) => {
|
||||
const { status: supportedVersionsStatus } = useAppSelector(state => state.supportedVersions);
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (showSearchHeader) {
|
||||
return <SearchHeader onSearchChangeText={onSearchChangeText} testID='rooms-list-view-search-input' />;
|
||||
}
|
||||
let subtitle;
|
||||
if (connecting) {
|
||||
if (supportedVersionsStatus === 'expired') {
|
||||
subtitle = 'Cannot connect';
|
||||
} else if (connecting) {
|
||||
subtitle = I18n.t('Connecting');
|
||||
} else if (isFetching) {
|
||||
subtitle = I18n.t('Updating');
|
||||
|
|
|
@ -26,7 +26,7 @@ import { goRoom } from '../../lib/methods/helpers/goRoom';
|
|||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
|
||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, ISubscription, IUser, TSVStatus, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import styles from './styles';
|
||||
import ServerDropdown from './ServerDropdown';
|
||||
import ListHeader, { TEncryptionBanner } from './ListHeader';
|
||||
|
@ -44,8 +44,9 @@ import {
|
|||
isTablet,
|
||||
compareServerVersion
|
||||
} from '../../lib/methods/helpers';
|
||||
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants';
|
||||
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes, STATUS_COLORS, colors } from '../../lib/constants';
|
||||
import { Services } from '../../lib/services';
|
||||
import { SupportedVersionsExpired } from '../../containers/SupportedVersions';
|
||||
|
||||
type TNavigation = CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
|
||||
|
@ -73,6 +74,7 @@ interface IRoomsListViewProps {
|
|||
useRealName: boolean;
|
||||
isMasterDetail: boolean;
|
||||
notificationPresenceCap: boolean;
|
||||
supportedVersionsStatus: TSVStatus;
|
||||
subscribedRoom: string;
|
||||
width: number;
|
||||
insets: {
|
||||
|
@ -145,7 +147,8 @@ const shouldUpdateProps = [
|
|||
'createPublicChannelPermission',
|
||||
'createPrivateChannelPermission',
|
||||
'createDiscussionPermission',
|
||||
'inAlertNotification'
|
||||
'inAlertNotification',
|
||||
'supportedVersionsStatus'
|
||||
];
|
||||
|
||||
const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread'];
|
||||
|
@ -332,7 +335,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
createDiscussionPermission,
|
||||
showAvatar,
|
||||
displayMode,
|
||||
inAlertNotification
|
||||
inAlertNotification,
|
||||
supportedVersionsStatus
|
||||
} = this.props;
|
||||
const { item } = this.state;
|
||||
|
||||
|
@ -356,7 +360,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
insets.left !== prevProps.insets.left ||
|
||||
insets.right !== prevProps.insets.right ||
|
||||
notificationPresenceCap !== prevProps.notificationPresenceCap ||
|
||||
inAlertNotification !== prevProps.inAlertNotification
|
||||
inAlertNotification !== prevProps.inAlertNotification ||
|
||||
supportedVersionsStatus !== prevProps.supportedVersionsStatus
|
||||
) {
|
||||
this.setHeader();
|
||||
}
|
||||
|
@ -409,7 +414,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
|
||||
getHeader = (): StackNavigationOptions => {
|
||||
const { searching, canCreateRoom } = this.state;
|
||||
const { navigation, isMasterDetail, notificationPresenceCap, inAlertNotification, theme } = this.props;
|
||||
const { navigation, isMasterDetail, notificationPresenceCap, inAlertNotification, supportedVersionsStatus, theme } =
|
||||
this.props;
|
||||
if (searching) {
|
||||
return {
|
||||
headerTitleAlign: 'left',
|
||||
|
@ -425,6 +431,16 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
};
|
||||
}
|
||||
|
||||
const getBadge = () => {
|
||||
if (supportedVersionsStatus === 'warn') {
|
||||
return <HeaderButton.BadgeWarn color={colors[theme].dangerColor} />;
|
||||
}
|
||||
if (notificationPresenceCap) {
|
||||
return <HeaderButton.BadgeWarn color={STATUS_COLORS.disabled} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return {
|
||||
headerTitleAlign: 'left',
|
||||
headerTitleContainerStyle: { flex: 1, marginHorizontal: 4, maxWidth: undefined },
|
||||
|
@ -439,7 +455,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
: // @ts-ignore
|
||||
() => navigation.toggleDrawer()
|
||||
}
|
||||
badge={() => (notificationPresenceCap ? <HeaderButton.BadgeWarn /> : null)}
|
||||
badge={() => getBadge()}
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
),
|
||||
headerTitle: () => <RoomsListHeaderView />,
|
||||
|
@ -452,10 +469,25 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
color={inAlertNotification ? themes[theme].fontDanger : themes[theme].headerTintColor}
|
||||
/>
|
||||
{canCreateRoom ? (
|
||||
<HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' />
|
||||
<HeaderButton.Item
|
||||
iconName='create'
|
||||
onPress={this.goToNewMessage}
|
||||
testID='rooms-list-view-create-channel'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' />
|
||||
<HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' />
|
||||
<HeaderButton.Item
|
||||
iconName='search'
|
||||
onPress={this.initSearching}
|
||||
testID='rooms-list-view-search'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
<HeaderButton.Item
|
||||
iconName='directory'
|
||||
onPress={this.goDirectory}
|
||||
testID='rooms-list-view-directory'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
};
|
||||
|
@ -914,7 +946,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
|
||||
renderScroll = () => {
|
||||
const { loading, chats, search, searching } = this.state;
|
||||
const { theme, refreshing, displayMode } = this.props;
|
||||
const { theme, refreshing, displayMode, supportedVersionsStatus } = this.props;
|
||||
|
||||
const height = displayMode === DisplayMode.Condensed ? ROW_HEIGHT_CONDENSED : ROW_HEIGHT;
|
||||
|
||||
|
@ -922,6 +954,10 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
if (supportedVersionsStatus === 'expired') {
|
||||
return <SupportedVersionsExpired />;
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
ref={this.getScrollRef}
|
||||
|
@ -967,6 +1003,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||
supportedVersionsStatus: state.supportedVersions.status,
|
||||
server: state.server.server,
|
||||
changingServer: state.server.changingServer,
|
||||
searchText: state.rooms.searchText,
|
||||
|
|
|
@ -55,6 +55,7 @@ export interface IRoomInfoParam {
|
|||
rid: string;
|
||||
t: SubscriptionType;
|
||||
joined?: boolean;
|
||||
itsMe?: boolean;
|
||||
}
|
||||
|
||||
interface INavigationOption {
|
||||
|
|
|
@ -10,13 +10,14 @@ interface SidebarItemProps {
|
|||
left: JSX.Element;
|
||||
right?: JSX.Element;
|
||||
text: string;
|
||||
textColor?: string;
|
||||
current?: boolean;
|
||||
onPress(): void;
|
||||
testID: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme }: SidebarItemProps) => (
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme, textColor }: SidebarItemProps) => (
|
||||
<Touch
|
||||
key={testID}
|
||||
testID={testID}
|
||||
|
@ -25,7 +26,11 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme }:
|
|||
>
|
||||
<View style={styles.itemHorizontal}>{left}</View>
|
||||
<View style={styles.itemCenter}>
|
||||
<Text style={[styles.itemText, { color: themes[theme].titleText }]} numberOfLines={1} accessibilityLabel={text}>
|
||||
<Text
|
||||
style={[styles.itemText, { color: textColor || themes[theme].titleText }]}
|
||||
numberOfLines={1}
|
||||
accessibilityLabel={text}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -21,9 +21,11 @@ import Navigation from '../../lib/navigation/appNavigation';
|
|||
import SidebarItem from './SidebarItem';
|
||||
import styles from './styles';
|
||||
import { DrawerParamList } from '../../stacks/types';
|
||||
import { IApplicationState, IUser } from '../../definitions';
|
||||
import { IApplicationState, IUser, TSVStatus } from '../../definitions';
|
||||
import * as List from '../../containers/List';
|
||||
import { IActionSheetProvider, showActionSheetRef, withActionSheet } from '../../containers/ActionSheet';
|
||||
import { setNotificationPresenceCap } from '../../actions/app';
|
||||
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||
|
||||
interface ISidebarState {
|
||||
showStatus: boolean;
|
||||
|
@ -42,11 +44,13 @@ interface ISidebarProps {
|
|||
allowStatusMessage: boolean;
|
||||
notificationPresenceCap: boolean;
|
||||
Presence_broadcast_disabled: boolean;
|
||||
supportedVersionsStatus: TSVStatus;
|
||||
isMasterDetail: boolean;
|
||||
viewStatisticsPermission: string[];
|
||||
viewRoomAdministrationPermission: string[];
|
||||
viewUserAdministrationPermission: string[];
|
||||
viewPrivilegedSettingPermission: string[];
|
||||
showActionSheet: IActionSheetProvider['showActionSheet'];
|
||||
}
|
||||
|
||||
class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||
|
@ -69,6 +73,7 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
useRealName,
|
||||
theme,
|
||||
Presence_broadcast_disabled,
|
||||
supportedVersionsStatus,
|
||||
viewStatisticsPermission,
|
||||
viewRoomAdministrationPermission,
|
||||
viewUserAdministrationPermission,
|
||||
|
@ -108,6 +113,9 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
if (nextProps.Presence_broadcast_disabled !== Presence_broadcast_disabled) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.supportedVersionsStatus !== supportedVersionsStatus) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.viewStatisticsPermission, viewStatisticsPermission)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -197,6 +205,15 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
);
|
||||
};
|
||||
|
||||
onPressSupportedVersionsWarning = () => {
|
||||
const { isMasterDetail } = this.props;
|
||||
if (isMasterDetail) {
|
||||
Navigation.navigate('ModalStackNavigator', { screen: 'SupportedVersionsWarning' });
|
||||
} else {
|
||||
showActionSheetRef({ children: <SupportedVersionsWarning /> });
|
||||
}
|
||||
};
|
||||
|
||||
renderAdmin = () => {
|
||||
const { theme, isMasterDetail } = this.props;
|
||||
if (!this.getIsAdmin()) {
|
||||
|
@ -286,6 +303,23 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
);
|
||||
};
|
||||
|
||||
renderSupportedVersionsWarn = () => {
|
||||
const { theme, supportedVersionsStatus } = this.props;
|
||||
if (supportedVersionsStatus === 'warn') {
|
||||
return (
|
||||
<SidebarItem
|
||||
text={I18n.t('Supported_versions_warning_update_required')}
|
||||
textColor={themes[theme!].dangerColor}
|
||||
left={<CustomIcon name='warning' size={20} color={themes[theme!].dangerColor} />}
|
||||
theme={theme!}
|
||||
onPress={() => this.onPressSupportedVersionsWarning()}
|
||||
testID={`sidebar-supported-versions-warn`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme } = this.props;
|
||||
|
||||
|
@ -323,6 +357,9 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<List.Separator />
|
||||
{this.renderSupportedVersionsWarn()}
|
||||
|
||||
<List.Separator />
|
||||
|
||||
{allowStatusMessage ? this.renderCustomStatus() : null}
|
||||
|
@ -350,6 +387,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
||||
Presence_broadcast_disabled: state.settings.Presence_broadcast_disabled as boolean,
|
||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||
supportedVersionsStatus: state.supportedVersions.status,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
||||
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
||||
|
@ -357,4 +395,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
viewPrivilegedSettingPermission: state.permissions['view-privileged-setting'] as string[]
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(Sidebar));
|
||||
export default connect(mapStateToProps)(withActionSheet(withTheme(Sidebar)));
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
import '@testing-library/jest-native/extend-expect';
|
||||
// @ts-ignore
|
||||
import mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js';
|
||||
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
|
||||
|
||||
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => {
|
||||
const inset = { top: 0, right: 0, bottom: 0, left: 0 };
|
||||
return {
|
||||
...jest.requireActual('react-native-safe-area-context'),
|
||||
SafeAreaProvider: jest.fn(({ children }) => children),
|
||||
SafeAreaConsumer: jest.fn(({ children }) => children(inset)),
|
||||
useSafeAreaInsets: jest.fn(() => inset),
|
||||
useSafeAreaFrame: jest.fn(() => ({ x: 0, y: 0, width: 390, height: 844 }))
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
global.__reanimatedWorkletInit = () => {};
|
||||
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
"i18n-js": "3.9.2",
|
||||
"js-base64": "3.6.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jsrsasign": "^10.8.6",
|
||||
"lint-staged": "^11.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
|
@ -173,13 +174,14 @@
|
|||
"@storybook/addon-storyshots": "6.3",
|
||||
"@storybook/react": "6.3",
|
||||
"@storybook/react-native": "^6.0.1-beta.7",
|
||||
"@testing-library/jest-native": "^4.0.4",
|
||||
"@testing-library/jest-native": "^5.4.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/react-native": "^9.0.0",
|
||||
"@testing-library/react-native": "^12.1.2",
|
||||
"@types/bytebuffer": "^5.0.44",
|
||||
"@types/ejson": "^2.1.3",
|
||||
"@types/i18n-js": "^3.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsrsasign": "^10.5.8",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-native": "0.68.1",
|
||||
|
@ -242,8 +244,7 @@
|
|||
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
"@testing-library/jest-native/extend-expect",
|
||||
"./jest.setup.js"
|
||||
"./jest.setup.ts"
|
||||
]
|
||||
},
|
||||
"jest-junit": {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# URL to fetch data from
|
||||
URL="https://releases.rocket.chat/v2/server/supportedVersions?source=mobile"
|
||||
|
||||
# Output file name
|
||||
OUTPUT_FILE="app-supportedversions.json"
|
||||
|
||||
# Use curl to fetch data and save it to a temporary file
|
||||
TEMP_FILE=$(mktemp)
|
||||
curl -s "$URL" > "$TEMP_FILE"
|
||||
|
||||
# Check if the curl command was successful (HTTP status code 200)
|
||||
if [ $? -eq 0 ]; then
|
||||
# Use jq to pretty-print the JSON and save it to the output file
|
||||
jq '.' "$TEMP_FILE" > "$OUTPUT_FILE"
|
||||
echo "Data fetched and saved to $OUTPUT_FILE"
|
||||
else
|
||||
echo "Failed to fetch data from $URL"
|
||||
fi
|
||||
|
||||
# Clean up the temporary file
|
||||
rm "$TEMP_FILE"
|
196
yarn.lock
196
yarn.lock
|
@ -4564,6 +4564,13 @@
|
|||
dependencies:
|
||||
"@sinclair/typebox" "^0.24.1"
|
||||
|
||||
"@jest/schemas@^29.6.3":
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
|
||||
integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
|
||||
dependencies:
|
||||
"@sinclair/typebox" "^0.27.8"
|
||||
|
||||
"@jest/source-map@^28.1.2":
|
||||
version "28.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24"
|
||||
|
@ -4645,15 +4652,6 @@
|
|||
slash "^3.0.0"
|
||||
write-file-atomic "^4.0.1"
|
||||
|
||||
"@jest/types@^24.9.0":
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59"
|
||||
integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
|
@ -5638,8 +5636,8 @@
|
|||
integrity sha512-WgaWLMCFWcmhRb7cEm1Q8GoD8lgpPuTniG27Qmfw8k86MuZfdHj+cdOfhvkmdNORxx181RhfksTO0k6IkRxh6A==
|
||||
|
||||
"@rocket.chat/sdk@RocketChat/Rocket.Chat.js.SDK#mobile":
|
||||
version "1.3.0-mobile"
|
||||
resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/ad71e7daa5bcb1a3b457b5de20fb0fc86581d04d"
|
||||
version "1.3.1-mobile"
|
||||
resolved "https://codeload.github.com/RocketChat/Rocket.Chat.js.SDK/tar.gz/501cd6ceec5f198af288aadc355f2fbbeda2b353"
|
||||
dependencies:
|
||||
js-sha256 "^0.9.0"
|
||||
lru-cache "^4.1.1"
|
||||
|
@ -5682,6 +5680,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794"
|
||||
integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==
|
||||
|
||||
"@sinclair/typebox@^0.27.8":
|
||||
version "0.27.8"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
|
@ -6287,17 +6290,16 @@
|
|||
resolve-from "^5.0.0"
|
||||
store2 "^2.12.0"
|
||||
|
||||
"@testing-library/jest-native@^4.0.4":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-4.0.4.tgz#25e2046896118f887683202a6e5fd8a4056131cd"
|
||||
integrity sha512-4q5FeTFyFgPCmQH18uMJsZkVnYvBtK24yhSfbd9hQi0SZzCpbjSeQQcsGXIaX+WjWcMeeip8B7NUvZmLhGHiZw==
|
||||
"@testing-library/jest-native@^5.4.2":
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.3.tgz#9334c68eaf45db9eb20d0876728cc5d7fc2c3ea2"
|
||||
integrity sha512-/sSDGaOuE+PJ1Z9Kp4u7PQScSVVXGud59I/qsBFFJvIbcn4P6yYw6cBnBmbPF+X9aRIsTJRDl6gzw5ZkJNm66w==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
jest-diff "^24.0.0"
|
||||
jest-matcher-utils "^24.0.0"
|
||||
pretty-format "^27.3.1"
|
||||
ramda "^0.26.1"
|
||||
redent "^2.0.0"
|
||||
chalk "^4.1.2"
|
||||
jest-diff "^29.0.1"
|
||||
jest-matcher-utils "^29.0.1"
|
||||
pretty-format "^29.0.3"
|
||||
redent "^3.0.0"
|
||||
|
||||
"@testing-library/react-hooks@^8.0.1":
|
||||
version "8.0.1"
|
||||
|
@ -6307,12 +6309,12 @@
|
|||
"@babel/runtime" "^7.12.5"
|
||||
react-error-boundary "^3.1.0"
|
||||
|
||||
"@testing-library/react-native@^9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-9.0.0.tgz#e9c63411e93d2e8e70d744b12aeb78c58025c5fc"
|
||||
integrity sha512-UE3FWOsDUr+2l3Pg6JTpn2rV5uzYsxIus6ZyN1uMOTmn30bIuBBDDlWQtdWGJx92YcY4xgJA4vViCEKv7wVzJA==
|
||||
"@testing-library/react-native@^12.1.2":
|
||||
version "12.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-12.2.2.tgz#4b2275d5d1feb689c9b1e5cd9cb03ffe32a43228"
|
||||
integrity sha512-aLr7YQ6pyn8PbLmdbtADG2aKcmarTLI7VhgWNVzJLxQHOtsDxLpJGKMSw10j406BE/GyGHbB0Gln3Of8/2TjnA==
|
||||
dependencies:
|
||||
pretty-format "^27.0.0"
|
||||
pretty-format "^29.0.0"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
|
@ -6581,6 +6583,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/jsrsasign@^10.5.8":
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.8.tgz#0d6c638505454b5e95c684d6f604d57641417336"
|
||||
integrity sha512-1oZ3TbarAhKtKUpyrCIqXpbx3ZAfoSulleJs6/UzzyYty0ut+kjRX7zHLAaHwVIuw8CBjIymwW4J2LK944HoHQ==
|
||||
|
||||
"@types/lodash@^4.14.175":
|
||||
version "4.14.182"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
|
@ -6885,13 +6892,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
|
||||
|
||||
"@types/yargs@^13.0.0":
|
||||
version "13.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1"
|
||||
integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^15.0.0":
|
||||
version "15.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
|
||||
|
@ -7435,7 +7435,7 @@ ansi-regex@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||
|
||||
ansi-regex@^4.0.0, ansi-regex@^4.1.0:
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
@ -10170,11 +10170,6 @@ detox@^20.1.2:
|
|||
yargs-parser "^20.2.9"
|
||||
yargs-unparser "^2.0.0"
|
||||
|
||||
diff-sequences@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
|
||||
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
|
||||
|
||||
diff-sequences@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
||||
|
@ -10190,6 +10185,11 @@ diff-sequences@^28.1.1:
|
|||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6"
|
||||
integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==
|
||||
|
||||
diff-sequences@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
|
@ -12913,11 +12913,6 @@ imurmurhash@^0.1.4:
|
|||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
indent-string@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
|
||||
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
|
||||
|
||||
indent-string@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||
|
@ -13705,16 +13700,6 @@ jest-config@^28.1.3:
|
|||
slash "^3.0.0"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
jest-diff@^24.0.0, jest-diff@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
|
||||
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
diff-sequences "^24.9.0"
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^25.2.1:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
||||
|
@ -13745,6 +13730,16 @@ jest-diff@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-diff@^29.0.1, jest-diff@^29.6.4:
|
||||
version "29.6.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a"
|
||||
integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
diff-sequences "^29.6.3"
|
||||
jest-get-type "^29.6.3"
|
||||
pretty-format "^29.6.3"
|
||||
|
||||
jest-docblock@^28.1.1:
|
||||
version "28.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8"
|
||||
|
@ -13790,11 +13785,6 @@ jest-expo@^46.0.1:
|
|||
lodash "^4.17.19"
|
||||
react-test-renderer "~18.0.0"
|
||||
|
||||
jest-get-type@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
|
||||
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
|
||||
|
||||
jest-get-type@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
|
||||
|
@ -13810,6 +13800,11 @@ jest-get-type@^28.0.2:
|
|||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203"
|
||||
integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==
|
||||
|
||||
jest-get-type@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
|
||||
integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
|
||||
|
||||
jest-haste-map@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa"
|
||||
|
@ -13888,16 +13883,6 @@ jest-leak-detector@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-matcher-utils@^24.0.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
|
||||
integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
jest-diff "^24.9.0"
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-matcher-utils@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a"
|
||||
|
@ -13918,6 +13903,16 @@ jest-matcher-utils@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-matcher-utils@^29.0.1:
|
||||
version "29.6.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24"
|
||||
integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
jest-diff "^29.6.4"
|
||||
jest-get-type "^29.6.3"
|
||||
pretty-format "^29.6.3"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
|
@ -14544,6 +14539,11 @@ jsonfile@^6.0.1:
|
|||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsrsasign@^10.8.6:
|
||||
version "10.8.6"
|
||||
resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.8.6.tgz#ebf7f3c812c6517af84f0d8a10115e0dbfabe145"
|
||||
integrity sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==
|
||||
|
||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb"
|
||||
|
@ -16406,7 +16406,7 @@ p-all@^2.1.0:
|
|||
p-defer@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
|
||||
integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=
|
||||
integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==
|
||||
|
||||
p-event@^4.1.0:
|
||||
version "4.2.0"
|
||||
|
@ -17056,16 +17056,6 @@ pretty-error@^2.1.1:
|
|||
renderkid "^2.0.1"
|
||||
utila "~0.4"
|
||||
|
||||
pretty-format@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
|
||||
integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
|
||||
dependencies:
|
||||
"@jest/types" "^24.9.0"
|
||||
ansi-regex "^4.0.0"
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||
|
@ -17086,16 +17076,7 @@ pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2:
|
|||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.0.0:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
|
||||
integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.3.1, pretty-format@^27.5.1:
|
||||
pretty-format@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||
|
@ -17114,6 +17095,15 @@ pretty-format@^28.1.3:
|
|||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
pretty-format@^29.0.0, pretty-format@^29.0.3, pretty-format@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7"
|
||||
integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.6.3"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
pretty-format@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
|
@ -17435,11 +17425,6 @@ ramda@^0.21.0:
|
|||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
||||
integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=
|
||||
|
||||
ramda@^0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
|
||||
|
||||
random-bytes@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||
|
@ -17622,7 +17607,7 @@ react-hook-form@^7.34.2:
|
|||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de"
|
||||
integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==
|
||||
|
||||
react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
@ -18274,13 +18259,13 @@ recursive-readdir@2.2.2:
|
|||
dependencies:
|
||||
minimatch "3.0.4"
|
||||
|
||||
redent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
|
||||
integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||
dependencies:
|
||||
indent-string "^3.0.0"
|
||||
strip-indent "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reduce-flatten@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -19793,11 +19778,6 @@ strip-final-newline@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||
|
||||
strip-indent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
||||
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
|
||||
|
||||
strip-indent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
||||
|
@ -20199,7 +20179,7 @@ timm@^1.6.1:
|
|||
tiny-events@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-events/-/tiny-events-1.0.1.tgz#74690e99abb8a43c8fed3236a3c3872b27ce6376"
|
||||
integrity sha1-dGkOmau4pDyP7TI2o8OHKyfOY3Y=
|
||||
integrity sha512-QuhRLBsUWwrj+7mVvffHWmtHmMjt4GihlCN8/WucyHBqDINW9n9K5xsdfK3MdIeJIHRlmI4zI6izU1jbD3kn6Q==
|
||||
|
||||
tinycolor2@^1.4.1:
|
||||
version "1.4.1"
|
||||
|
|
Loading…
Reference in New Issue