Merge branch 'develop' into feat.new-audio-player
This commit is contained in:
commit
b1e0e1211c
|
@ -96,16 +96,23 @@ commands:
|
||||||
- ~/.pods
|
- ~/.pods
|
||||||
- ios/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:
|
android-build:
|
||||||
description: "Build Android app"
|
description: "Build Android app"
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
|
|
||||||
- restore_cache: *restore-npm-cache-linux
|
- restore_cache: *restore-npm-cache-linux
|
||||||
|
|
||||||
- run: *install-npm-modules
|
- run: *install-npm-modules
|
||||||
|
|
||||||
- restore_cache: *restore-gradle-cache
|
- restore_cache: *restore-gradle-cache
|
||||||
|
- fetch-supported-versions
|
||||||
|
|
||||||
- run:
|
- run:
|
||||||
name: Configure Gradle
|
name: Configure Gradle
|
||||||
|
@ -200,6 +207,7 @@ commands:
|
||||||
- run: *install-npm-modules
|
- run: *install-npm-modules
|
||||||
- run: *update-fastlane-ios
|
- run: *update-fastlane-ios
|
||||||
- manage-pods
|
- manage-pods
|
||||||
|
- fetch-supported-versions
|
||||||
- run:
|
- run:
|
||||||
name: Set Google Services
|
name: Set Google Services
|
||||||
command: |
|
command: |
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// 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 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 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
|
// 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\\"]}]}]}]"`;
|
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
|
@ -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"
|
||||||
|
}
|
|
@ -96,3 +96,4 @@ export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
|
||||||
'ACCEPT_CALL',
|
'ACCEPT_CALL',
|
||||||
'SET_CALLING'
|
'SET_CALLING'
|
||||||
]);
|
]);
|
||||||
|
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Action } from 'redux';
|
||||||
|
|
||||||
import { SERVER } from './actionsTypes';
|
import { SERVER } from './actionsTypes';
|
||||||
|
|
||||||
interface ISelectServer extends Action {
|
export interface ISelectServerAction extends Action {
|
||||||
server: string;
|
server: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
fetchVersion: boolean;
|
fetchVersion: boolean;
|
||||||
|
@ -12,9 +12,10 @@ interface ISelectServer extends Action {
|
||||||
interface ISelectServerSuccess extends Action {
|
interface ISelectServerSuccess extends Action {
|
||||||
server: string;
|
server: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IServer extends Action {
|
export interface IServerRequestAction extends Action {
|
||||||
server: string;
|
server: string;
|
||||||
username: string | null;
|
username: string | null;
|
||||||
fromServerHistory: boolean;
|
fromServerHistory: boolean;
|
||||||
|
@ -24,13 +25,14 @@ interface IServerInit extends Action {
|
||||||
previousServer: string;
|
previousServer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IServerFailure extends Action {
|
export type TActionServer = ISelectServerAction & ISelectServerSuccess & IServerRequestAction & IServerInit;
|
||||||
err: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TActionServer = ISelectServer & ISelectServerSuccess & IServer & IServerInit & IServerFailure;
|
export function selectServerRequest(
|
||||||
|
server: string,
|
||||||
export function selectServerRequest(server: string, version?: string, fetchVersion = true, changeServer = false): ISelectServer {
|
version?: string,
|
||||||
|
fetchVersion = true,
|
||||||
|
changeServer = false
|
||||||
|
): ISelectServerAction {
|
||||||
return {
|
return {
|
||||||
type: SERVER.SELECT_REQUEST,
|
type: SERVER.SELECT_REQUEST,
|
||||||
server,
|
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 {
|
return {
|
||||||
type: SERVER.SELECT_SUCCESS,
|
type: SERVER.SELECT_SUCCESS,
|
||||||
server,
|
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 {
|
return {
|
||||||
type: SERVER.REQUEST,
|
type: SERVER.REQUEST,
|
||||||
server,
|
server,
|
||||||
|
@ -69,10 +80,9 @@ export function serverSuccess(): Action {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serverFailure(err: any): IServerFailure {
|
export function serverFailure(): Action {
|
||||||
return {
|
return {
|
||||||
type: SERVER.FAILURE,
|
type: SERVER.FAILURE
|
||||||
err
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
const actionSheetRef: React.Ref<IActionSheetProvider> = createRef();
|
||||||
|
|
||||||
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
||||||
const getContext = () => ({
|
const getContext = (): IActionSheetProvider => ({
|
||||||
showActionSheet: (options: TActionSheetOptions) => {
|
showActionSheet: options => {
|
||||||
actionSheetRef.current?.showActionSheet(options);
|
actionSheetRef.current?.showActionSheet(options);
|
||||||
},
|
},
|
||||||
hideActionSheet: () => {
|
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();
|
actionSheetRef?.current?.hideActionSheet();
|
||||||
};
|
};
|
||||||
|
|
|
@ -30,7 +30,8 @@ const Avatar = React.memo(
|
||||||
borderRadius = 4,
|
borderRadius = 4,
|
||||||
type = SubscriptionType.DIRECT,
|
type = SubscriptionType.DIRECT,
|
||||||
avatarExternalProviderUrl,
|
avatarExternalProviderUrl,
|
||||||
roomAvatarExternalProviderUrl
|
roomAvatarExternalProviderUrl,
|
||||||
|
cdnPrefix
|
||||||
}: IAvatar) => {
|
}: IAvatar) => {
|
||||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -61,7 +62,8 @@ const Avatar = React.memo(
|
||||||
rid,
|
rid,
|
||||||
blockUnauthenticatedAccess,
|
blockUnauthenticatedAccess,
|
||||||
avatarExternalProviderUrl,
|
avatarExternalProviderUrl,
|
||||||
roomAvatarExternalProviderUrl
|
roomAvatarExternalProviderUrl,
|
||||||
|
cdnPrefix
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,9 +32,10 @@ const AvatarContainer = ({
|
||||||
shallowEqual
|
shallowEqual
|
||||||
);
|
);
|
||||||
|
|
||||||
const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl } = useSelector((state: IApplicationState) => ({
|
const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl, cdnPrefix } = useSelector((state: IApplicationState) => ({
|
||||||
avatarExternalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string,
|
avatarExternalProviderUrl: state.settings.Accounts_AvatarExternalProviderUrl as string,
|
||||||
roomAvatarExternalProviderUrl: state.settings.Accounts_RoomAvatarExternalProviderUrl as string
|
roomAvatarExternalProviderUrl: state.settings.Accounts_RoomAvatarExternalProviderUrl as string,
|
||||||
|
cdnPrefix: state.settings.CDN_PREFIX as string
|
||||||
}));
|
}));
|
||||||
const blockUnauthenticatedAccess = useSelector(
|
const blockUnauthenticatedAccess = useSelector(
|
||||||
(state: IApplicationState) =>
|
(state: IApplicationState) =>
|
||||||
|
@ -67,6 +68,7 @@ const AvatarContainer = ({
|
||||||
roomAvatarExternalProviderUrl={roomAvatarExternalProviderUrl}
|
roomAvatarExternalProviderUrl={roomAvatarExternalProviderUrl}
|
||||||
avatarETag={avatarETag}
|
avatarETag={avatarETag}
|
||||||
serverVersion={serverVersion}
|
serverVersion={serverVersion}
|
||||||
|
cdnPrefix={cdnPrefix}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,4 +24,5 @@ export interface IAvatar {
|
||||||
serverVersion?: string | null;
|
serverVersion?: string | null;
|
||||||
avatarExternalProviderUrl?: string;
|
avatarExternalProviderUrl?: string;
|
||||||
roomAvatarExternalProviderUrl?: string;
|
roomAvatarExternalProviderUrl?: string;
|
||||||
|
cdnPrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,8 +44,8 @@ describe('ButtonTests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('find button using accessibilityLabel', async () => {
|
test('find button using accessibilityLabel', async () => {
|
||||||
const { findByA11yLabel } = render(<TestButton />);
|
const { getByLabelText } = render(<TestButton />);
|
||||||
const Button = await findByA11yLabel(testProps.title);
|
const Button = await getByLabelText(testProps.title);
|
||||||
expect(Button).toBeTruthy();
|
expect(Button).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -71,9 +71,7 @@ const Button = ({
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<ActivityIndicator color={textColor} />
|
<ActivityIndicator color={textColor} />
|
||||||
) : (
|
) : (
|
||||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]} accessibilityLabel={title}>
|
<Text style={[styles.text, { color: textColor, fontSize }, styleText]}>{title}</Text>
|
||||||
{title}
|
|
||||||
</Text>
|
|
||||||
)}
|
)}
|
||||||
</Touchable>
|
</Touchable>
|
||||||
);
|
);
|
||||||
|
|
|
@ -43,7 +43,18 @@ const styles = StyleSheet.create({
|
||||||
const Item = ({ title, iconName, onPress, testID, badge, color, disabled, ...props }: IHeaderButtonItem): React.ReactElement => {
|
const Item = ({ title, iconName, onPress, testID, badge, color, disabled, ...props }: IHeaderButtonItem): React.ReactElement => {
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
return (
|
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 ? (
|
{iconName ? (
|
||||||
<CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
|
<CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
import { STATUS_COLORS } from '../../lib/constants';
|
|
||||||
import UnreadBadge from '../UnreadBadge';
|
import UnreadBadge from '../UnreadBadge';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
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 BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
||||||
|
|
||||||
export const BadgeWarn = (): React.ReactElement => (
|
export const BadgeWarn = ({ color }: { color: string }): React.ReactElement => (
|
||||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
<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]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
||||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[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>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -120,7 +120,7 @@ const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
||||||
<HeaderExample
|
<HeaderExample
|
||||||
left={() => (
|
left={() => (
|
||||||
<HeaderButton.Container left>
|
<HeaderButton.Container left>
|
||||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn color={colors[theme].dangerColor} />} />
|
||||||
<HeaderButton.Item iconName='threads' />
|
<HeaderButton.Item iconName='threads' />
|
||||||
</HeaderButton.Container>
|
</HeaderButton.Container>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -44,6 +44,7 @@ export interface IMessageActionsProps {
|
||||||
deleteOwnMessagePermission?: string[];
|
deleteOwnMessagePermission?: string[];
|
||||||
pinMessagePermission?: string[];
|
pinMessagePermission?: string[];
|
||||||
createDirectMessagePermission?: string[];
|
createDirectMessagePermission?: string[];
|
||||||
|
createDiscussionOtherUserPermission?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMessageActions {
|
export interface IMessageActions {
|
||||||
|
@ -76,6 +77,7 @@ const MessageActions = React.memo(
|
||||||
deleteOwnMessagePermission,
|
deleteOwnMessagePermission,
|
||||||
pinMessagePermission,
|
pinMessagePermission,
|
||||||
createDirectMessagePermission,
|
createDirectMessagePermission,
|
||||||
|
createDiscussionOtherUserPermission,
|
||||||
serverVersion
|
serverVersion
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
|
@ -85,7 +87,9 @@ const MessageActions = React.memo(
|
||||||
hasDeletePermission: false,
|
hasDeletePermission: false,
|
||||||
hasForceDeletePermission: false,
|
hasForceDeletePermission: false,
|
||||||
hasPinPermission: false,
|
hasPinPermission: false,
|
||||||
hasDeleteOwnPermission: false
|
hasDeleteOwnPermission: false,
|
||||||
|
hasCreateDirectMessagePermission: false,
|
||||||
|
hasCreateDiscussionOtherUserPermission: false
|
||||||
};
|
};
|
||||||
const { showActionSheet, hideActionSheet } = useActionSheet();
|
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||||
|
|
||||||
|
@ -96,7 +100,9 @@ const MessageActions = React.memo(
|
||||||
deleteMessagePermission,
|
deleteMessagePermission,
|
||||||
forceDeleteMessagePermission,
|
forceDeleteMessagePermission,
|
||||||
pinMessagePermission,
|
pinMessagePermission,
|
||||||
deleteOwnMessagePermission
|
deleteOwnMessagePermission,
|
||||||
|
createDirectMessagePermission,
|
||||||
|
createDiscussionOtherUserPermission
|
||||||
];
|
];
|
||||||
const result = await hasPermission(permission, room.rid);
|
const result = await hasPermission(permission, room.rid);
|
||||||
permissions = {
|
permissions = {
|
||||||
|
@ -104,7 +110,9 @@ const MessageActions = React.memo(
|
||||||
hasDeletePermission: result[1],
|
hasDeletePermission: result[1],
|
||||||
hasForceDeletePermission: result[2],
|
hasForceDeletePermission: result[2],
|
||||||
hasPinPermission: result[3],
|
hasPinPermission: result[3],
|
||||||
hasDeleteOwnPermission: result[4]
|
hasDeleteOwnPermission: result[4],
|
||||||
|
hasCreateDirectMessagePermission: result[5],
|
||||||
|
hasCreateDiscussionOtherUserPermission: result[6]
|
||||||
};
|
};
|
||||||
} catch {
|
} catch {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -385,7 +393,7 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reply in DM
|
// Reply in DM
|
||||||
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission && !videoConfBlock) {
|
if (room.t !== 'd' && room.t !== 'l' && permissions.hasCreateDirectMessagePermission && !videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
title: I18n.t('Reply_in_direct_message'),
|
title: I18n.t('Reply_in_direct_message'),
|
||||||
icon: 'arrow-back',
|
icon: 'arrow-back',
|
||||||
|
@ -394,11 +402,13 @@ const MessageActions = React.memo(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Discussion
|
// Create Discussion
|
||||||
options.push({
|
if (permissions.hasCreateDiscussionOtherUserPermission) {
|
||||||
title: I18n.t('Start_a_Discussion'),
|
options.push({
|
||||||
icon: 'discussions',
|
title: I18n.t('Start_a_Discussion'),
|
||||||
onPress: () => handleCreateDiscussion(message)
|
icon: 'discussions',
|
||||||
});
|
onPress: () => handleCreateDiscussion(message)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.2.0') && !videoConfBlock) {
|
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.2.0') && !videoConfBlock) {
|
||||||
options.push({
|
options.push({
|
||||||
|
@ -541,7 +551,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
deleteOwnMessagePermission: state.permissions['delete-own-message'],
|
deleteOwnMessagePermission: state.permissions['delete-own-message'],
|
||||||
forceDeleteMessagePermission: state.permissions['force-delete-message'],
|
forceDeleteMessagePermission: state.permissions['force-delete-message'],
|
||||||
pinMessagePermission: state.permissions['pin-message'],
|
pinMessagePermission: state.permissions['pin-message'],
|
||||||
createDirectMessagePermission: state.permissions['create-d']
|
createDirectMessagePermission: state.permissions['create-d'],
|
||||||
|
createDiscussionOtherUserPermission: state.permissions['start-discussion-other-user']
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
|
export default connect(mapStateToProps, null, null, { forwardRef: true })(MessageActions);
|
||||||
|
|
|
@ -112,6 +112,7 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
|
||||||
isActionsEnabled: boolean;
|
isActionsEnabled: boolean;
|
||||||
usedCannedResponse: string;
|
usedCannedResponse: string;
|
||||||
uploadFilePermission: string[];
|
uploadFilePermission: string[];
|
||||||
|
createDiscussionPermission: string[];
|
||||||
goToCannedResponses: () => void | null;
|
goToCannedResponses: () => void | null;
|
||||||
serverVersion: string;
|
serverVersion: string;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +131,7 @@ interface IMessageBoxState {
|
||||||
tshow: boolean;
|
tshow: boolean;
|
||||||
mentionLoading: boolean;
|
mentionLoading: boolean;
|
||||||
permissionToUpload: boolean;
|
permissionToUpload: boolean;
|
||||||
|
hasCreateDiscussionPermission: boolean;
|
||||||
showEmojiSearchbar: boolean;
|
showEmojiSearchbar: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +186,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
tshow: this.sendThreadToChannel,
|
tshow: this.sendThreadToChannel,
|
||||||
mentionLoading: false,
|
mentionLoading: false,
|
||||||
permissionToUpload: true,
|
permissionToUpload: true,
|
||||||
showEmojiSearchbar: false
|
showEmojiSearchbar: false,
|
||||||
|
hasCreateDiscussionPermission: false
|
||||||
};
|
};
|
||||||
this.text = '';
|
this.text = '';
|
||||||
this.selection = { start: 0, end: 0 };
|
this.selection = { start: 0, end: 0 };
|
||||||
|
@ -325,6 +328,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
mentionLoading,
|
mentionLoading,
|
||||||
trackingType,
|
trackingType,
|
||||||
permissionToUpload,
|
permissionToUpload,
|
||||||
|
hasCreateDiscussionPermission,
|
||||||
showEmojiSearchbar
|
showEmojiSearchbar
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
|
@ -337,6 +341,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
theme,
|
theme,
|
||||||
usedCannedResponse,
|
usedCannedResponse,
|
||||||
uploadFilePermission,
|
uploadFilePermission,
|
||||||
|
createDiscussionPermission,
|
||||||
goToCannedResponses
|
goToCannedResponses
|
||||||
} = this.props;
|
} = this.props;
|
||||||
if (nextProps.theme !== theme) {
|
if (nextProps.theme !== theme) {
|
||||||
|
@ -378,6 +383,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (nextState.permissionToUpload !== permissionToUpload) {
|
if (nextState.permissionToUpload !== permissionToUpload) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (nextState.hasCreateDiscussionPermission !== hasCreateDiscussionPermission) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (!dequal(nextState.mentions, mentions)) {
|
if (!dequal(nextState.mentions, mentions)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -390,6 +398,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
|
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!dequal(nextProps.createDiscussionPermission, createDiscussionPermission)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -400,13 +411,18 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: IMessageBoxProps) {
|
componentDidUpdate(prevProps: IMessageBoxProps) {
|
||||||
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
|
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled, createDiscussionPermission } =
|
||||||
|
this.props;
|
||||||
if (prevProps.replyWithMention !== replyWithMention) {
|
if (prevProps.replyWithMention !== replyWithMention) {
|
||||||
if (threadsEnabled && replyWithMention) {
|
if (threadsEnabled && replyWithMention) {
|
||||||
this.setState({ tshow: this.sendThreadToChannel });
|
this.setState({ tshow: this.sendThreadToChannel });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!dequal(prevProps.uploadFilePermission, uploadFilePermission) || prevProps.goToCannedResponses !== goToCannedResponses) {
|
if (
|
||||||
|
!dequal(prevProps.uploadFilePermission, uploadFilePermission) ||
|
||||||
|
!dequal(prevProps.createDiscussionPermission, createDiscussionPermission) ||
|
||||||
|
prevProps.goToCannedResponses !== goToCannedResponses
|
||||||
|
) {
|
||||||
this.setOptions();
|
this.setOptions();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -441,7 +457,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions = async () => {
|
setOptions = async () => {
|
||||||
const { uploadFilePermission, rid } = this.props;
|
const { uploadFilePermission, rid, createDiscussionPermission } = this.props;
|
||||||
|
|
||||||
// Servers older than 4.2
|
// Servers older than 4.2
|
||||||
if (!uploadFilePermission) {
|
if (!uploadFilePermission) {
|
||||||
|
@ -449,8 +465,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const permissionToUpload = await hasPermission([uploadFilePermission], rid);
|
const permissions = await hasPermission([uploadFilePermission, createDiscussionPermission], rid);
|
||||||
this.setState({ permissionToUpload: permissionToUpload[0] });
|
this.setState({ permissionToUpload: permissions[0], hasCreateDiscussionPermission: permissions[1] });
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeText: any = (text: string): void => {
|
onChangeText: any = (text: string): void => {
|
||||||
|
@ -877,7 +893,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
|
|
||||||
showMessageBoxActions = () => {
|
showMessageBoxActions = () => {
|
||||||
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
||||||
const { permissionToUpload } = this.state;
|
const { permissionToUpload, hasCreateDiscussionPermission } = this.state;
|
||||||
const { showActionSheet, goToCannedResponses } = this.props;
|
const { showActionSheet, goToCannedResponses } = this.props;
|
||||||
|
|
||||||
const options: TActionSheetOptionsItem[] = [];
|
const options: TActionSheetOptionsItem[] = [];
|
||||||
|
@ -913,11 +929,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.push({
|
if (hasCreateDiscussionPermission) {
|
||||||
title: I18n.t('Create_Discussion'),
|
options.push({
|
||||||
icon: 'discussions',
|
title: I18n.t('Create_Discussion'),
|
||||||
onPress: this.createDiscussion
|
icon: 'discussions',
|
||||||
});
|
onPress: this.createDiscussion
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.closeEmojiAndAction(showActionSheet, { options });
|
this.closeEmojiAndAction(showActionSheet, { options });
|
||||||
};
|
};
|
||||||
|
@ -1316,6 +1334,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
||||||
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
|
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
|
||||||
uploadFilePermission: state.permissions['mobile-upload-file'],
|
uploadFilePermission: state.permissions['mobile-upload-file'],
|
||||||
|
createDiscussionPermission: state.permissions['start-discussion'],
|
||||||
serverVersion: state.server.version
|
serverVersion: state.server.version
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
// @ts-ignore // TODO: Remove on react-native update
|
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
|
|
||||||
import { IServerInfo } from '../../definitions';
|
|
||||||
import Check from '../Check';
|
import Check from '../Check';
|
||||||
import styles, { ROW_HEIGHT } from './styles';
|
import styles, { ROW_HEIGHT } from './styles';
|
||||||
import { themes } from '../../lib/constants';
|
import { themes } from '../../lib/constants';
|
||||||
|
@ -13,7 +11,12 @@ import { useTheme } from '../../theme';
|
||||||
export { ROW_HEIGHT };
|
export { ROW_HEIGHT };
|
||||||
|
|
||||||
export interface IServerItem {
|
export interface IServerItem {
|
||||||
item: IServerInfo;
|
item: {
|
||||||
|
id: string;
|
||||||
|
iconURL: string;
|
||||||
|
name: string;
|
||||||
|
useRealName?: boolean;
|
||||||
|
};
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
onLongPress?(): void;
|
onLongPress?(): void;
|
||||||
hasCheck?: boolean;
|
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';
|
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
|
||||||
|
|
||||||
interface IControlledFormTextInputProps extends IRCTextInputProps {
|
interface IControlledFormTextInputProps extends Omit<IRCTextInputProps, 'inputRef'> {
|
||||||
control: Control<any>;
|
control: Control<any>;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ export const ControlledFormTextInput = ({ control, name, ...props }: IControlled
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name={name}
|
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} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { IAttachment, IUserMessage } from '../../definitions';
|
||||||
import { downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
import { downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
||||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||||
import AudioPlayer from '../AudioPlayer';
|
import AudioPlayer from '../AudioPlayer';
|
||||||
|
import { useAppSelector } from '../../lib/hooks';
|
||||||
|
|
||||||
interface IMessageAudioProps {
|
interface IMessageAudioProps {
|
||||||
file: IAttachment;
|
file: IAttachment;
|
||||||
|
@ -16,6 +17,7 @@ interface IMessageAudioProps {
|
||||||
getCustomEmoji: TGetCustomEmoji;
|
getCustomEmoji: TGetCustomEmoji;
|
||||||
author?: IUserMessage;
|
author?: IUserMessage;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
|
cdnPrefix?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => {
|
const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMessageAudioProps) => {
|
||||||
|
@ -25,10 +27,14 @@ const MessageAudio = ({ file, getCustomEmoji, author, isReply, style, msg }: IMe
|
||||||
|
|
||||||
const { baseUrl, user } = useContext(MessageContext);
|
const { baseUrl, user } = useContext(MessageContext);
|
||||||
|
|
||||||
|
const { cdnPrefix } = useAppSelector(state => ({
|
||||||
|
cdnPrefix: state.settings.CDN_PREFIX as string
|
||||||
|
}));
|
||||||
|
|
||||||
const getUrl = () => {
|
const getUrl = () => {
|
||||||
let url = file.audio_url;
|
let url = file.audio_url;
|
||||||
if (url && !url.startsWith('http')) {
|
if (url && !url.startsWith('http')) {
|
||||||
url = `${baseUrl}${file.audio_url}`;
|
url = `${cdnPrefix || baseUrl}${file.audio_url}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,7 @@ import BlurComponent from './Components/BlurComponent';
|
||||||
import MessageContext from './Context';
|
import MessageContext from './Context';
|
||||||
import Touchable from './Touchable';
|
import Touchable from './Touchable';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
|
import { isImageBase64 } from '../../lib/methods';
|
||||||
|
|
||||||
interface IMessageButton {
|
interface IMessageButton {
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
|
@ -110,7 +111,12 @@ const ImageContainer = ({
|
||||||
await handleAutoDownload();
|
await handleAutoDownload();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
handleCache();
|
if (isImageBase64(imgUrlToCache)) {
|
||||||
|
setLoading(false);
|
||||||
|
setCached(true);
|
||||||
|
} else {
|
||||||
|
handleCache();
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!img) {
|
if (!img) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,68 @@ import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
import { IEnterpriseModules } from '../reducers/enterpriseModules';
|
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 {
|
export interface IServer {
|
||||||
name: string;
|
name: string;
|
||||||
iconURL: string;
|
iconURL: string;
|
||||||
|
@ -17,13 +79,8 @@ export interface IServer {
|
||||||
uniqueID: string;
|
uniqueID: string;
|
||||||
enterpriseModules: IEnterpriseModules;
|
enterpriseModules: IEnterpriseModules;
|
||||||
E2E_Enable: boolean;
|
E2E_Enable: boolean;
|
||||||
}
|
supportedVersions?: ISupportedVersionsData;
|
||||||
|
supportedVersionsWarningAt?: Date;
|
||||||
export interface IServerInfo {
|
|
||||||
id: string;
|
|
||||||
iconURL: string;
|
|
||||||
name: string;
|
|
||||||
useRealName?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TServerModel = IServer & Model;
|
export type TServerModel = IServer & Model;
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { TActionUserTyping } from '../../actions/usersTyping';
|
||||||
import { TActionPermissions } from '../../actions/permissions';
|
import { TActionPermissions } from '../../actions/permissions';
|
||||||
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
||||||
import { TActionVideoConf } from '../../actions/videoConf';
|
import { TActionVideoConf } from '../../actions/videoConf';
|
||||||
|
import { TActionSupportedVersions } from '../../actions/supportedVersions';
|
||||||
// REDUCERS
|
// REDUCERS
|
||||||
import { IActiveUsers } from '../../reducers/activeUsers';
|
import { IActiveUsers } from '../../reducers/activeUsers';
|
||||||
import { IApp } from '../../reducers/app';
|
import { IApp } from '../../reducers/app';
|
||||||
|
@ -38,6 +39,7 @@ import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
||||||
import { IVideoConf } from '../../reducers/videoConf';
|
import { IVideoConf } from '../../reducers/videoConf';
|
||||||
import { TActionUsersRoles } from '../../actions/usersRoles';
|
import { TActionUsersRoles } from '../../actions/usersRoles';
|
||||||
import { TUsersRoles } from '../../reducers/usersRoles';
|
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||||
|
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
|
||||||
|
|
||||||
export interface IApplicationState {
|
export interface IApplicationState {
|
||||||
settings: TSettingsState;
|
settings: TSettingsState;
|
||||||
|
@ -63,6 +65,7 @@ export interface IApplicationState {
|
||||||
roles: IRoles;
|
roles: IRoles;
|
||||||
videoConf: IVideoConf;
|
videoConf: IVideoConf;
|
||||||
usersRoles: TUsersRoles;
|
usersRoles: TUsersRoles;
|
||||||
|
supportedVersions: ISupportedVersionsState;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TApplicationActions = TActionActiveUsers &
|
export type TApplicationActions = TActionActiveUsers &
|
||||||
|
@ -83,4 +86,5 @@ export type TApplicationActions = TActionActiveUsers &
|
||||||
TActionPermissions &
|
TActionPermissions &
|
||||||
TActionEnterpriseModules &
|
TActionEnterpriseModules &
|
||||||
TActionVideoConf &
|
TActionVideoConf &
|
||||||
TActionUsersRoles;
|
TActionUsersRoles &
|
||||||
|
TActionSupportedVersions;
|
||||||
|
|
|
@ -692,9 +692,9 @@
|
||||||
"Call_ended": "Call ended",
|
"Call_ended": "Call ended",
|
||||||
"Call_was_not_answered": "Call was not answered",
|
"Call_was_not_answered": "Call was not answered",
|
||||||
"Call_rejected": "Call rejected",
|
"Call_rejected": "Call rejected",
|
||||||
"Call_back": "Call Back",
|
"Call_back": "Call back",
|
||||||
"Call_again": "Call Again",
|
"Call_again": "Call again",
|
||||||
"Call_ongoing": "Call Ongoing",
|
"Call_ongoing": "Call ongoing",
|
||||||
"Call_issue": "Call issue",
|
"Call_issue": "Call issue",
|
||||||
"Joined": "Joined",
|
"Joined": "Joined",
|
||||||
"Calling": "Calling",
|
"Calling": "Calling",
|
||||||
|
@ -749,5 +749,8 @@
|
||||||
"Jitsi_authentication_before_making_calls_ask_admin": "If you believe there are problems with Jitsi and its authentication, ask a workspace administrator for help.",
|
"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",
|
"Continue": "Continue",
|
||||||
"Message_has_been_shared": "Message has been shared",
|
"Message_has_been_shared": "Message has been shared",
|
||||||
"No_channels_in_team": "No Channels on this team"
|
"No_channels_in_team": "No Channels on this team",
|
||||||
|
"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",
|
"Removed__username__from_the_team": "removeu @{{userRemoved}} deste time",
|
||||||
"Place_chat_on_hold": "Colocar conversa em espera",
|
"Place_chat_on_hold": "Colocar conversa em espera",
|
||||||
"Would_like_to_place_on_hold": "Gostaria de colocar essa conversa em espera?",
|
"Would_like_to_place_on_hold": "Gostaria de colocar essa conversa em espera?",
|
||||||
"Open_Livechats": "Bate-papos em Andamento",
|
"Open_Livechats": "Bate-papos em andamento",
|
||||||
"On_hold_Livechats": "Conversas Em Espera",
|
"On_hold_Livechats": "Conversas em espera",
|
||||||
"Chat_is_on_hold": "Esta conversa está em espera devido à inatividade",
|
"Chat_is_on_hold": "Esta conversa está em espera devido à inatividade",
|
||||||
"Resume": "Continuar",
|
"Resume": "Continuar",
|
||||||
"Omnichannel_placed_chat_on_hold": "Conversa em espera: {{comment}}",
|
"Omnichannel_placed_chat_on_hold": "Conversa em espera: {{comment}}",
|
||||||
|
@ -736,5 +736,8 @@
|
||||||
"decline": "Recusar",
|
"decline": "Recusar",
|
||||||
"accept": "Aceitar",
|
"accept": "Aceitar",
|
||||||
"Incoming_call_from": "Chamada recebida de",
|
"Incoming_call_from": "Chamada recebida de",
|
||||||
"Call_started": "Chamada Iniciada"
|
"Call_started": "Chamada iniciada",
|
||||||
|
"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"
|
||||||
}
|
}
|
|
@ -246,5 +246,8 @@ export const defaultSettings = {
|
||||||
Omnichannel_call_provider: {
|
Omnichannel_call_provider: {
|
||||||
type: 'valueAsBoolean'
|
type: 'valueAsBoolean'
|
||||||
},
|
},
|
||||||
|
CDN_PREFIX: {
|
||||||
|
type: 'valueAsString'
|
||||||
|
},
|
||||||
...deprecatedSettings
|
...deprecatedSettings
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -10,6 +10,7 @@ export * from './messagesStatus';
|
||||||
export * from './messageTypeLoad';
|
export * from './messageTypeLoad';
|
||||||
export * from './notifications';
|
export * from './notifications';
|
||||||
export * from './defaultSettings';
|
export * from './defaultSettings';
|
||||||
|
export * from './supportedVersions';
|
||||||
export * from './tablet';
|
export * from './tablet';
|
||||||
export * from './mediaAutoDownload';
|
export * from './mediaAutoDownload';
|
||||||
export * from './userAgent';
|
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 THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||||
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||||
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_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 TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
export const CURRENT_SERVER = 'currentServer';
|
export const CURRENT_SERVER = 'currentServer';
|
||||||
export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
|
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 { 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';
|
export const SERVERS_TABLE = 'servers';
|
||||||
|
|
||||||
|
@ -33,4 +34,8 @@ export default class Server extends Model {
|
||||||
@field('enterprise_modules') enterpriseModules;
|
@field('enterprise_modules') enterpriseModules;
|
||||||
|
|
||||||
@field('e2e_enable') E2E_Enable;
|
@field('e2e_enable') E2E_Enable;
|
||||||
|
|
||||||
|
@json('supported_versions', sanitizer) supportedVersions;
|
||||||
|
|
||||||
|
@date('supported_versions_warning_at') supportedVersionsWarningAt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,6 +115,22 @@ 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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
import { appSchema, tableSchema } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
export default appSchema({
|
export default appSchema({
|
||||||
version: 13,
|
version: 14,
|
||||||
tables: [
|
tables: [
|
||||||
tableSchema({
|
tableSchema({
|
||||||
name: 'users',
|
name: 'users',
|
||||||
|
@ -38,7 +38,9 @@ export default appSchema({
|
||||||
{ name: 'biometry', type: 'boolean', isOptional: true }, // deprecated
|
{ name: 'biometry', type: 'boolean', isOptional: true }, // deprecated
|
||||||
{ name: 'unique_id', type: 'string', isOptional: true },
|
{ name: 'unique_id', type: 'string', isOptional: true },
|
||||||
{ name: 'enterprise_modules', 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 }
|
||||||
]
|
]
|
||||||
}),
|
}),
|
||||||
tableSchema({
|
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(), 'days') <= remainingDays);
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
};
|
|
@ -23,6 +23,7 @@ export const SUPPORTED_PERMISSIONS = [
|
||||||
'create-p',
|
'create-p',
|
||||||
'create-d',
|
'create-d',
|
||||||
'start-discussion',
|
'start-discussion',
|
||||||
|
'start-discussion-other-user',
|
||||||
'create-team',
|
'create-team',
|
||||||
'delete-c',
|
'delete-c',
|
||||||
'delete-message',
|
'delete-message',
|
||||||
|
|
|
@ -0,0 +1,109 @@
|
||||||
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
import { settings as RocketChatSettings } from '@rocket.chat/sdk';
|
||||||
|
import { KJUR } from 'jsrsasign';
|
||||||
|
|
||||||
|
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';
|
||||||
|
|
||||||
|
interface IServerInfoFailure {
|
||||||
|
success: false;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IServerInfoSuccess extends IServerInfo {
|
||||||
|
success: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TServerInfoResult = IServerInfoSuccess | IServerInfoFailure;
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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;
|
||||||
|
};
|
|
@ -199,8 +199,7 @@ export function downloadMediaFile({
|
||||||
try {
|
try {
|
||||||
const path = getFilePath({ type, mimeType, urlToCache: downloadUrl });
|
const path = getFilePath({ type, mimeType, urlToCache: downloadUrl });
|
||||||
if (!path) {
|
if (!path) {
|
||||||
reject();
|
return reject();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
downloadKey = mediaDownloadKey(downloadUrl);
|
downloadKey = mediaDownloadKey(downloadUrl);
|
||||||
downloadQueue[downloadKey] = FileSystem.createDownloadResumable(downloadUrl, path);
|
downloadQueue[downloadKey] = FileSystem.createDownloadResumable(downloadUrl, path);
|
||||||
|
@ -208,9 +207,9 @@ export function downloadMediaFile({
|
||||||
if (result?.uri) {
|
if (result?.uri) {
|
||||||
return resolve(result.uri);
|
return resolve(result.uri);
|
||||||
}
|
}
|
||||||
reject();
|
return reject();
|
||||||
} catch {
|
} catch {
|
||||||
reject();
|
return reject();
|
||||||
} finally {
|
} finally {
|
||||||
delete downloadQueue[downloadKey];
|
delete downloadQueue[downloadKey];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback, Options } from 'use-debounce';
|
||||||
|
|
||||||
export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
||||||
let timeout: ReturnType<typeof setTimeout> | null;
|
let timeout: ReturnType<typeof setTimeout> | null;
|
||||||
|
@ -24,6 +24,6 @@ export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
||||||
return _debounce;
|
return _debounce;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDebounce(func: (...args: any) => any, wait?: number): (...args: any[]) => void {
|
export function useDebounce(func: (...args: any) => any, wait?: number, options?: Options): (...args: any[]) => void {
|
||||||
return useDebouncedCallback(func, wait || 1000);
|
return useDebouncedCallback(func, wait || 1000, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export const headers: CustomHeaders = {
|
||||||
};
|
};
|
||||||
|
|
||||||
let _basicAuth;
|
let _basicAuth;
|
||||||
export const setBasicAuth = (basicAuth: string): void => {
|
export const setBasicAuth = (basicAuth: string | null): void => {
|
||||||
_basicAuth = basicAuth;
|
_basicAuth = basicAuth;
|
||||||
if (basicAuth) {
|
if (basicAuth) {
|
||||||
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { URL } from 'react-native-url-polyfill';
|
import { URL } from 'react-native-url-polyfill';
|
||||||
|
|
||||||
import { LOCAL_DOCUMENT_DIRECTORY } from '../handleMediaDownload';
|
import { LOCAL_DOCUMENT_DIRECTORY } from '../handleMediaDownload';
|
||||||
|
import { isImageBase64 } from '../isImageBase64';
|
||||||
|
import { store } from '../../store/auxStore';
|
||||||
|
|
||||||
function setParamInUrl({ url, token, userId }: { url: string; token: string; userId: string }) {
|
function setParamInUrl({ url, token, userId }: { url: string; token: string; userId: string }) {
|
||||||
const urlObj = new URL(url);
|
const urlObj = new URL(url);
|
||||||
|
@ -10,7 +12,10 @@ function setParamInUrl({ url, token, userId }: { url: string; token: string; use
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => {
|
export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: string, token: string, server: string): string => {
|
||||||
if (LOCAL_DOCUMENT_DIRECTORY && attachmentUrl?.startsWith(LOCAL_DOCUMENT_DIRECTORY)) {
|
if (
|
||||||
|
(attachmentUrl && isImageBase64(attachmentUrl)) ||
|
||||||
|
(LOCAL_DOCUMENT_DIRECTORY && attachmentUrl?.startsWith(LOCAL_DOCUMENT_DIRECTORY))
|
||||||
|
) {
|
||||||
return attachmentUrl;
|
return attachmentUrl;
|
||||||
}
|
}
|
||||||
if (attachmentUrl && attachmentUrl.startsWith('http')) {
|
if (attachmentUrl && attachmentUrl.startsWith('http')) {
|
||||||
|
@ -19,5 +24,9 @@ export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: s
|
||||||
}
|
}
|
||||||
return setParamInUrl({ url: attachmentUrl, token, userId });
|
return setParamInUrl({ url: attachmentUrl, token, userId });
|
||||||
}
|
}
|
||||||
|
const cdnPrefix = store?.getState().settings.CDN_PREFIX as string;
|
||||||
|
if (cdnPrefix) {
|
||||||
|
server = cdnPrefix.trim().replace(/\/+$/, '');
|
||||||
|
}
|
||||||
return setParamInUrl({ url: `${server}${attachmentUrl}`, token, userId });
|
return setParamInUrl({ url: `${server}${attachmentUrl}`, token, userId });
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,8 @@ export const getAvatarURL = ({
|
||||||
blockUnauthenticatedAccess,
|
blockUnauthenticatedAccess,
|
||||||
serverVersion,
|
serverVersion,
|
||||||
avatarExternalProviderUrl,
|
avatarExternalProviderUrl,
|
||||||
roomAvatarExternalProviderUrl
|
roomAvatarExternalProviderUrl,
|
||||||
|
cdnPrefix
|
||||||
}: IAvatar): string => {
|
}: IAvatar): string => {
|
||||||
let room;
|
let room;
|
||||||
if (type === SubscriptionType.DIRECT) {
|
if (type === SubscriptionType.DIRECT) {
|
||||||
|
@ -48,6 +49,10 @@ export const getAvatarURL = ({
|
||||||
query += `&etag=${avatarETag}`;
|
query += `&etag=${avatarETag}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cdnPrefix) {
|
||||||
|
server = cdnPrefix.trim().replace(/\/+$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
if (avatar.startsWith('http')) {
|
if (avatar.startsWith('http')) {
|
||||||
return avatar;
|
return avatar;
|
||||||
|
|
|
@ -39,3 +39,6 @@ export * from './subscribeRooms';
|
||||||
export * from './serializeAsciiUrl';
|
export * from './serializeAsciiUrl';
|
||||||
export * from './audioPlayer';
|
export * from './audioPlayer';
|
||||||
export * from './isRoomFederated';
|
export * from './isRoomFederated';
|
||||||
|
export * from './checkSupportedVersions';
|
||||||
|
export * from './getServerInfo';
|
||||||
|
export * from './isImageBase64';
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { isImageBase64 } from './isImageBase64';
|
||||||
|
|
||||||
|
// We aren't testing the content, only the header
|
||||||
|
const base64 =
|
||||||
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKkAAADcCAIAAACEWBYKAAAAA3NCSVQICAjb4U/gAAAgAElEQVR4nO29aZAc13WoeXOtrMza967qHd1ooAGRBLQNTcnUxmHMmPo3CjvGEdZo7PAP/fDzs2fscbw/Hr14EYrwNhMeWZbMkCY0YZJiaB4FkwxRFkiAIgACDZAAutFbVXft+55Vua/z40g9EJaWWN0Al8rvR0d1Va735M177jnnnoPZto0cxhL8/b4Ah/cNR/bjiyP78cWR/fjiyH58cWQ/vjiyH18c2Y8vjuzHF0f244sj+/HFkf344sh+fHFkP76MLntLkg/xOhwePiPK3pKV0vLTpf/+fxy8fp4/e07e2PpN9lJ3c3d+s5P9lcOKkt5q37GN3myZojjadTrsw4iyx90M8z8';
|
||||||
|
|
||||||
|
describe('Test the isImageBase64', () => {
|
||||||
|
it.each([
|
||||||
|
['test', false],
|
||||||
|
['/file-upload/oTQmb2zRCsYF4pdHv/help-image-url.png', false],
|
||||||
|
[base64, true]
|
||||||
|
])('return properly the boolean', (data, res) => {
|
||||||
|
const result = isImageBase64(data);
|
||||||
|
expect(result).toBe(res);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
const imageBase64RegExp = new RegExp(/^data:image\/([a-zA-Z]*);base64,([^\"]*)$/);
|
||||||
|
|
||||||
|
export function isImageBase64(data?: string): boolean {
|
||||||
|
if (!data) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return imageBase64RegExp.test(data);
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||||
import { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import { InteractionManager } from 'react-native';
|
import { InteractionManager } from 'react-native';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
@ -8,7 +7,6 @@ import log from '../methods/helpers/log';
|
||||||
import { setActiveUsers } from '../../actions/activeUsers';
|
import { setActiveUsers } from '../../actions/activeUsers';
|
||||||
import protectedFunction from '../methods/helpers/protectedFunction';
|
import protectedFunction from '../methods/helpers/protectedFunction';
|
||||||
import database from '../database';
|
import database from '../database';
|
||||||
import { selectServerFailure } from '../../actions/server';
|
|
||||||
import { twoFactor } from './twoFactor';
|
import { twoFactor } from './twoFactor';
|
||||||
import { store } from '../store/auxStore';
|
import { store } from '../store/auxStore';
|
||||||
import { loginRequest, setLoginServices, setUser } from '../../actions/login';
|
import { loginRequest, setLoginServices, setUser } from '../../actions/login';
|
||||||
|
@ -19,7 +17,7 @@ import { connectRequest, connectSuccess, disconnect as disconnectAction } from '
|
||||||
import { updatePermission } from '../../actions/permissions';
|
import { updatePermission } from '../../actions/permissions';
|
||||||
import EventEmitter from '../methods/helpers/events';
|
import EventEmitter from '../methods/helpers/events';
|
||||||
import { updateSettings } from '../../actions/settings';
|
import { updateSettings } from '../../actions/settings';
|
||||||
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
import { defaultSettings } from '../constants';
|
||||||
import {
|
import {
|
||||||
getSettings,
|
getSettings,
|
||||||
IActiveUsers,
|
IActiveUsers,
|
||||||
|
@ -49,7 +47,7 @@ let notifyAllListener: any;
|
||||||
let rolesListener: any;
|
let rolesListener: any;
|
||||||
let notifyLoggedListener: 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 => {
|
return new Promise<void>(resolve => {
|
||||||
if (sdk.current?.client?.host === server) {
|
if (sdk.current?.client?.host === server) {
|
||||||
return resolve();
|
return resolve();
|
||||||
|
@ -401,51 +399,11 @@ function disconnect() {
|
||||||
return sdk.disconnect();
|
return sdk.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getServerInfo(server: string) {
|
async function getWebsocketInfo({
|
||||||
try {
|
server
|
||||||
const response = await RNFetchBlob.fetch('GET', `${server}/api/info`, { ...RocketChatSettings.customHeaders });
|
}: {
|
||||||
try {
|
server: string;
|
||||||
// Try to resolve as json
|
}): Promise<{ success: true } | { success: false; message: string }> {
|
||||||
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 }) {
|
|
||||||
const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -530,7 +488,6 @@ export {
|
||||||
abort,
|
abort,
|
||||||
connect,
|
connect,
|
||||||
disconnect,
|
disconnect,
|
||||||
getServerInfo,
|
|
||||||
getWebsocketInfo,
|
getWebsocketInfo,
|
||||||
stopListener,
|
stopListener,
|
||||||
getLoginServices,
|
getLoginServices,
|
||||||
|
|
|
@ -999,3 +999,6 @@ export const notifyUser = (type: string, params: Record<string, any>): Promise<b
|
||||||
sdk.methodCall('stream-notify-user', type, params);
|
sdk.methodCall('stream-notify-user', type, params);
|
||||||
|
|
||||||
export const getUsersRoles = (): Promise<boolean> => sdk.methodCall('getUserRoles');
|
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`);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import permissions from './permissions';
|
||||||
import roles from './roles';
|
import roles from './roles';
|
||||||
import videoConf from './videoConf';
|
import videoConf from './videoConf';
|
||||||
import usersRoles from './usersRoles';
|
import usersRoles from './usersRoles';
|
||||||
|
import supportedVersions from './supportedVersions';
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
settings,
|
settings,
|
||||||
|
@ -47,5 +48,6 @@ export default combineReducers({
|
||||||
permissions,
|
permissions,
|
||||||
roles,
|
roles,
|
||||||
videoConf,
|
videoConf,
|
||||||
usersRoles
|
usersRoles,
|
||||||
|
supportedVersions
|
||||||
});
|
});
|
||||||
|
|
|
@ -41,9 +41,10 @@ describe('test server reducer', () => {
|
||||||
it('should return modified store after selectServerSucess', () => {
|
it('should return modified store after selectServerSucess', () => {
|
||||||
const server = 'https://open.rocket.chat/';
|
const server = 'https://open.rocket.chat/';
|
||||||
const version = '4.1.0';
|
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 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);
|
expect(state).toEqual(manipulated);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ describe('test server reducer', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return modified store after serverRequestFailure', () => {
|
it('should return modified store after serverRequestFailure', () => {
|
||||||
mockedStore.dispatch(serverFailure('error'));
|
mockedStore.dispatch(serverFailure());
|
||||||
const state = mockedStore.getState().server;
|
const state = mockedStore.getState().server;
|
||||||
expect(state.failure).toEqual(true);
|
expect(state.failure).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,6 +7,7 @@ export interface IServer {
|
||||||
failure: boolean;
|
failure: boolean;
|
||||||
server: string;
|
server: string;
|
||||||
version: string | null;
|
version: string | null;
|
||||||
|
name: string | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
previousServer: string | null;
|
previousServer: string | null;
|
||||||
changingServer: boolean;
|
changingServer: boolean;
|
||||||
|
@ -18,6 +19,7 @@ export const initialState: IServer = {
|
||||||
failure: false,
|
failure: false,
|
||||||
server: '',
|
server: '',
|
||||||
version: null,
|
version: null,
|
||||||
|
name: null,
|
||||||
loading: true,
|
loading: true,
|
||||||
previousServer: null,
|
previousServer: null,
|
||||||
changingServer: false
|
changingServer: false
|
||||||
|
@ -53,6 +55,7 @@ export default function server(state = initialState, action: TActionServer): ISe
|
||||||
...state,
|
...state,
|
||||||
server: action.server,
|
server: action.server,
|
||||||
version: action.version,
|
version: action.version,
|
||||||
|
name: action.name,
|
||||||
connecting: false,
|
connecting: false,
|
||||||
connected: true,
|
connected: true,
|
||||||
loading: false,
|
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;
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,7 +1,9 @@
|
||||||
|
import React from 'react';
|
||||||
import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects';
|
import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import { appStart } from '../actions/app';
|
import { appStart } from '../actions/app';
|
||||||
import { selectServerRequest, serverFinishAdd } from '../actions/server';
|
import { selectServerRequest, serverFinishAdd } from '../actions/server';
|
||||||
|
@ -37,12 +39,40 @@ import {
|
||||||
} from '../lib/methods';
|
} from '../lib/methods';
|
||||||
import { Services } from '../lib/services';
|
import { Services } from '../lib/services';
|
||||||
import { setUsersRoles } from '../actions/usersRoles';
|
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 getServer = state => state.server.server;
|
||||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||||
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
|
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
|
||||||
const logoutCall = args => logout(args);
|
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({
|
const handleLoginRequest = function* handleLoginRequest({
|
||||||
credentials,
|
credentials,
|
||||||
logoutOnError = false,
|
logoutOnError = false,
|
||||||
|
@ -209,6 +239,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
||||||
if (inviteLinkToken) {
|
if (inviteLinkToken) {
|
||||||
yield put(inviteLinksRequest(inviteLinkToken));
|
yield put(inviteLinksRequest(inviteLinkToken));
|
||||||
}
|
}
|
||||||
|
yield showSupportedVersionsWarning(server);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(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,261 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
let newRecord;
|
||||||
|
await serversDB.write(async () => {
|
||||||
|
newRecord = await serversCollection.create(r => {
|
||||||
|
r._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||||
|
if (serverInfo.supportedVersions) {
|
||||||
|
r.supportedVersions = serverInfo.supportedVersions;
|
||||||
|
}
|
||||||
|
r.version = serverVersion;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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;
|
|
@ -76,6 +76,7 @@ import {
|
||||||
} from './types';
|
} from './types';
|
||||||
import { isIOS } from '../../lib/methods/helpers';
|
import { isIOS } from '../../lib/methods/helpers';
|
||||||
import { TNavigation } from '../stackType';
|
import { TNavigation } from '../stackType';
|
||||||
|
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||||
|
|
||||||
// ChatsStackNavigator
|
// ChatsStackNavigator
|
||||||
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
|
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
|
||||||
|
@ -184,6 +185,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
||||||
<ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
|
<ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
|
||||||
<ModalStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} />
|
<ModalStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} />
|
||||||
<ModalStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
|
<ModalStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
|
||||||
|
<ModalStack.Screen name='SupportedVersionsWarning' component={SupportedVersionsWarning} />
|
||||||
</ModalStack.Navigator>
|
</ModalStack.Navigator>
|
||||||
</ModalContainer>
|
</ModalContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -196,6 +196,9 @@ export type ModalStackParamList = {
|
||||||
SecurityPrivacyView: undefined;
|
SecurityPrivacyView: undefined;
|
||||||
MediaAutoDownloadView: undefined;
|
MediaAutoDownloadView: undefined;
|
||||||
E2EEncryptionSecurityView: undefined;
|
E2EEncryptionSecurityView: undefined;
|
||||||
|
SupportedVersionsWarning: {
|
||||||
|
showCloseButton?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MasterDetailInsideStackParamList = {
|
export type MasterDetailInsideStackParamList = {
|
||||||
|
|
|
@ -50,11 +50,7 @@ const OutsideStackModal = () => {
|
||||||
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation, presentation: 'transparentModal' }}
|
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation, presentation: 'transparentModal' }}
|
||||||
>
|
>
|
||||||
<OutsideModal.Screen name='OutsideStack' component={OutsideStack} options={{ headerShown: false }} />
|
<OutsideModal.Screen name='OutsideStack' component={OutsideStack} options={{ headerShown: false }} />
|
||||||
<OutsideModal.Screen
|
<OutsideModal.Screen name='AuthenticationWebView' component={AuthenticationWebView} />
|
||||||
name='AuthenticationWebView'
|
|
||||||
component={AuthenticationWebView}
|
|
||||||
options={AuthenticationWebView.navigationOptions}
|
|
||||||
/>
|
|
||||||
</OutsideModal.Navigator>
|
</OutsideModal.Navigator>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { shallowEqual } from 'react-redux';
|
import { shallowEqual } from 'react-redux';
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
|
||||||
|
import { isImageBase64 } from '../lib/methods';
|
||||||
import RCActivityIndicator from '../containers/ActivityIndicator';
|
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||||
import * as HeaderButton from '../containers/HeaderButton';
|
import * as HeaderButton from '../containers/HeaderButton';
|
||||||
import { ImageViewer } from '../containers/ImageViewer';
|
import { ImageViewer } from '../containers/ImageViewer';
|
||||||
|
@ -109,6 +110,7 @@ const AttachmentView = (): React.ReactElement => {
|
||||||
|
|
||||||
const setHeader = () => {
|
const setHeader = () => {
|
||||||
let { title } = attachment;
|
let { title } = attachment;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (title) {
|
if (title) {
|
||||||
title = decodeURI(title);
|
title = decodeURI(title);
|
||||||
|
@ -128,7 +130,7 @@ const AttachmentView = (): React.ReactElement => {
|
||||||
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={colors.previewTintColor} />
|
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={colors.previewTintColor} />
|
||||||
),
|
),
|
||||||
headerRight: () =>
|
headerRight: () =>
|
||||||
Allow_Save_Media_to_Gallery ? (
|
Allow_Save_Media_to_Gallery && !isImageBase64(attachment.image_url) ? (
|
||||||
<HeaderButton.Download testID='save-image' onPress={handleSave} color={colors.previewTintColor} />
|
<HeaderButton.Download testID='save-image' onPress={handleSave} color={colors.previewTintColor} />
|
||||||
) : null,
|
) : null,
|
||||||
headerBackground: () => (
|
headerBackground: () => (
|
||||||
|
|
|
@ -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 { 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 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 * 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 { 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
|
// 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
|
// We'll handle this sending the token to the hash of the window.location
|
||||||
|
@ -40,95 +40,56 @@ window.addEventListener('popstate', function() {
|
||||||
});
|
});
|
||||||
`;
|
`;
|
||||||
|
|
||||||
interface INavigationOption {
|
const AuthenticationWebView = () => {
|
||||||
navigation: StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
const [logging, setLogging] = useState(false);
|
||||||
route: RouteProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
const [loading, setLoading] = useState(false);
|
||||||
}
|
|
||||||
|
|
||||||
interface IAuthenticationWebView extends INavigationOption {
|
const navigation = useNavigation<StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||||
server: string;
|
const {
|
||||||
Accounts_Iframe_api_url: string;
|
params: { authType, url, ssoToken }
|
||||||
Accounts_Iframe_api_method: string;
|
} = useRoute<RouteProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||||
theme: TSupportedThemes;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IState {
|
const { Accounts_Iframe_api_method, Accounts_Iframe_api_url, server } = useAppSelector(state => ({
|
||||||
logging: boolean;
|
server: state.server.server,
|
||||||
loading: boolean;
|
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> {
|
const oauthRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||||
private oauthRedirectRegex: RegExp;
|
const iframeRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(event|loginToken|token))`, 'g');
|
||||||
private iframeRedirectRegex: RegExp;
|
|
||||||
|
|
||||||
static navigationOptions = ({ route, navigation }: INavigationOption) => {
|
// Force 3s delay so the server has time to evaluate the token
|
||||||
const { authType } = route.params;
|
const debouncedLogin = useDebounce((params: ICredentials) => login(params), 3000);
|
||||||
return {
|
|
||||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} />,
|
|
||||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
constructor(props: IAuthenticationWebView) {
|
const login = (params: ICredentials) => {
|
||||||
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;
|
|
||||||
if (logging) {
|
if (logging) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setLogging(true);
|
||||||
this.setState({ logging: true });
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Services.loginOAuthOrSso(params);
|
Services.loginOAuthOrSso(params);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
}
|
}
|
||||||
this.setState({ logging: false });
|
setLogging(false);
|
||||||
this.dismiss();
|
navigation.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Force 3s delay so the server has time to evaluate the token
|
const tryLogin = useDebounce(
|
||||||
debouncedLogin = debounce((params: ICredentials) => this.login(params), 3000);
|
|
||||||
|
|
||||||
tryLogin = debounce(
|
|
||||||
async () => {
|
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 data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json());
|
||||||
const resume = data?.login || data?.loginToken;
|
const resume = data?.login || data?.loginToken;
|
||||||
if (resume) {
|
if (resume) {
|
||||||
this.login({ resume });
|
login({ resume });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
3000,
|
3000,
|
||||||
true
|
{ leading: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
const onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||||
const url = decodeURIComponent(webViewState.url);
|
const url = decodeURIComponent(webViewState.url);
|
||||||
const { route } = this.props;
|
|
||||||
const { authType } = route.params;
|
|
||||||
if (authType === 'saml' || authType === 'cas') {
|
if (authType === 'saml' || authType === 'cas') {
|
||||||
const { ssoToken } = route.params;
|
|
||||||
const parsedUrl = parse(url, true);
|
const parsedUrl = parse(url, true);
|
||||||
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||||
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||||
|
@ -140,28 +101,28 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
||||||
} else {
|
} else {
|
||||||
payload = { cas: { credentialToken: ssoToken } };
|
payload = { cas: { credentialToken: ssoToken } };
|
||||||
}
|
}
|
||||||
this.debouncedLogin(payload);
|
debouncedLogin(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType === 'oauth') {
|
if (authType === 'oauth') {
|
||||||
if (this.oauthRedirectRegex.test(url)) {
|
if (oauthRedirectRegex.test(url)) {
|
||||||
const parts = url.split('#');
|
const parts = url.split('#');
|
||||||
const credentials = JSON.parse(parts[1]);
|
const credentials = JSON.parse(parts[1]);
|
||||||
this.debouncedLogin({ oauth: { ...credentials } });
|
debouncedLogin({ oauth: { ...credentials } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authType === 'iframe') {
|
if (authType === 'iframe') {
|
||||||
if (this.iframeRedirectRegex.test(url)) {
|
if (iframeRedirectRegex.test(url)) {
|
||||||
const parts = url.split('#');
|
const parts = url.split('#');
|
||||||
const credentials = JSON.parse(parts[1]);
|
const credentials = JSON.parse(parts[1]);
|
||||||
switch (credentials.event) {
|
switch (credentials.event) {
|
||||||
case 'try-iframe-login':
|
case 'try-iframe-login':
|
||||||
this.tryLogin();
|
tryLogin();
|
||||||
break;
|
break;
|
||||||
case 'login-with-token':
|
case 'login-with-token':
|
||||||
this.debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
|
@ -170,39 +131,31 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
const isIframe = authType === 'iframe';
|
||||||
const { loading } = this.state;
|
|
||||||
const { route } = this.props;
|
|
||||||
const { url, authType } = route.params;
|
|
||||||
const isIframe = authType === 'iframe';
|
|
||||||
|
|
||||||
return (
|
useLayoutEffect(() => {
|
||||||
<>
|
navigation.setOptions({
|
||||||
<StatusBar />
|
headerLeft: () => <HeaderButton.CloseModal />,
|
||||||
<WebView
|
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||||
source={{ uri: url }}
|
});
|
||||||
userAgent={userAgent}
|
}, [authType, navigation]);
|
||||||
// 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}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = (state: IApplicationState) => ({
|
return (
|
||||||
server: state.server.server,
|
<>
|
||||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
<StatusBar />
|
||||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
<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;
|
||||||
|
|
|
@ -86,6 +86,7 @@ const JitsiMeetView = (): React.ReactElement => {
|
||||||
domStorageEnabled
|
domStorageEnabled
|
||||||
allowsInlineMediaPlayback
|
allowsInlineMediaPlayback
|
||||||
mediaCapturePermissionGrantType={'grant'}
|
mediaCapturePermissionGrantType={'grant'}
|
||||||
|
mediaPlaybackRequiresUserAction={isIOS}
|
||||||
/>
|
/>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,38 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { RefreshControl as RNRefreshControl, RefreshControlProps, StyleSheet } from 'react-native';
|
|
||||||
|
|
||||||
import { useTheme } from '../../../../theme';
|
|
||||||
import { isAndroid } from '../../../../lib/methods/helpers';
|
|
||||||
|
|
||||||
const style = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1
|
|
||||||
},
|
|
||||||
inverted: {
|
|
||||||
scaleY: -1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
interface IRefreshControl extends RefreshControlProps {
|
|
||||||
children: React.ReactElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RefreshControl = ({ children, onRefresh, refreshing }: IRefreshControl): React.ReactElement => {
|
|
||||||
const { colors } = useTheme();
|
|
||||||
if (isAndroid) {
|
|
||||||
return (
|
|
||||||
<RNRefreshControl
|
|
||||||
onRefresh={onRefresh}
|
|
||||||
refreshing={refreshing}
|
|
||||||
tintColor={colors.auxiliaryText}
|
|
||||||
style={[style.container, style.inverted]}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</RNRefreshControl>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const refreshControl = <RNRefreshControl onRefresh={onRefresh} refreshing={refreshing} tintColor={colors.auxiliaryText} />;
|
|
||||||
|
|
||||||
return React.cloneElement(children, { refreshControl });
|
|
||||||
};
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from './NavBottomFAB';
|
export * from './NavBottomFAB';
|
||||||
export * from './RefreshControl';
|
|
||||||
export * from './EmptyRoom';
|
export * from './EmptyRoom';
|
||||||
export * from './List';
|
export * from './List';
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './useMessages';
|
export * from './useMessages';
|
||||||
export * from './useRefresh';
|
|
||||||
export * from './useScroll';
|
export * from './useScroll';
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import moment from 'moment';
|
|
||||||
import { useState } from 'react';
|
|
||||||
|
|
||||||
import log from '../../../../lib/methods/helpers/log';
|
|
||||||
import { loadMissedMessages, loadThreadMessages } from '../../../../lib/methods';
|
|
||||||
|
|
||||||
export const useRefresh = ({ rid, tmid, messagesLength }: { rid: string; tmid?: string; messagesLength: number }) => {
|
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
|
||||||
|
|
||||||
const refresh = async () => {
|
|
||||||
if (messagesLength) {
|
|
||||||
setRefreshing(true);
|
|
||||||
try {
|
|
||||||
if (tmid) {
|
|
||||||
await loadThreadMessages({ tmid, rid });
|
|
||||||
} else {
|
|
||||||
await loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() });
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log(e);
|
|
||||||
}
|
|
||||||
setRefreshing(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [refreshing, refresh] as const;
|
|
||||||
};
|
|
|
@ -1,11 +1,11 @@
|
||||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||||
import { View, Platform, StyleSheet } from 'react-native';
|
import { Platform, StyleSheet, View } from 'react-native';
|
||||||
|
|
||||||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||||
import { useMessages, useRefresh, useScroll } from './hooks';
|
import { isAndroid, useDebounce } from '../../../lib/methods/helpers';
|
||||||
import { useDebounce } from '../../../lib/methods/helpers';
|
import { EmptyRoom, List } from './components';
|
||||||
import { RefreshControl, EmptyRoom, List } from './components';
|
|
||||||
import { IListContainerProps, IListContainerRef, IListProps } from './definitions';
|
import { IListContainerProps, IListContainerRef, IListProps } from './definitions';
|
||||||
|
import { useMessages, useScroll } from './hooks';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
inverted: {
|
inverted: {
|
||||||
|
@ -17,6 +17,9 @@ const styles = StyleSheet.create({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Container = ({ children }: { children: React.ReactElement }) =>
|
||||||
|
isAndroid ? <View style={{ flex: 1, scaleY: -1 }}>{children}</View> : <>{children}</>;
|
||||||
|
|
||||||
const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
||||||
({ rid, tmid, renderRow, showMessageInMainThread, serverVersion, hideSystemMessages, listRef, loading }, ref) => {
|
({ rid, tmid, renderRow, showMessageInMainThread, serverVersion, hideSystemMessages, listRef, loading }, ref) => {
|
||||||
const [messages, messagesIds, fetchMessages] = useMessages({
|
const [messages, messagesIds, fetchMessages] = useMessages({
|
||||||
|
@ -26,7 +29,6 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
||||||
serverVersion,
|
serverVersion,
|
||||||
hideSystemMessages
|
hideSystemMessages
|
||||||
});
|
});
|
||||||
const [refreshing, refresh] = useRefresh({ rid, tmid, messagesLength: messages.length });
|
|
||||||
const {
|
const {
|
||||||
jumpToBottom,
|
jumpToBottom,
|
||||||
jumpToMessage,
|
jumpToMessage,
|
||||||
|
@ -59,7 +61,7 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EmptyRoom rid={rid} length={messages.length} />
|
<EmptyRoom rid={rid} length={messages.length} />
|
||||||
<RefreshControl refreshing={refreshing} onRefresh={refresh}>
|
<Container>
|
||||||
<List
|
<List
|
||||||
listRef={listRef}
|
listRef={listRef}
|
||||||
data={messages}
|
data={messages}
|
||||||
|
@ -71,7 +73,7 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
||||||
jumpToBottom={jumpToBottom}
|
jumpToBottom={jumpToBottom}
|
||||||
isThread={!!tmid}
|
isThread={!!tmid}
|
||||||
/>
|
/>
|
||||||
</RefreshControl>
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import sharedStyles from '../../Styles';
|
||||||
import { CustomIcon } from '../../../containers/CustomIcon';
|
import { CustomIcon } from '../../../containers/CustomIcon';
|
||||||
import { useTheme } from '../../../theme';
|
import { useTheme } from '../../../theme';
|
||||||
import SearchHeader from '../../../containers/SearchHeader';
|
import SearchHeader from '../../../containers/SearchHeader';
|
||||||
|
import { useAppSelector } from '../../../lib/hooks';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -54,13 +55,16 @@ const Header = React.memo(
|
||||||
onSearchChangeText,
|
onSearchChangeText,
|
||||||
onPress
|
onPress
|
||||||
}: IRoomHeader) => {
|
}: IRoomHeader) => {
|
||||||
|
const { status: supportedVersionsStatus } = useAppSelector(state => state.supportedVersions);
|
||||||
const { colors } = useTheme();
|
const { colors } = useTheme();
|
||||||
|
|
||||||
if (showSearchHeader) {
|
if (showSearchHeader) {
|
||||||
return <SearchHeader onSearchChangeText={onSearchChangeText} testID='rooms-list-view-search-input' />;
|
return <SearchHeader onSearchChangeText={onSearchChangeText} testID='rooms-list-view-search-input' />;
|
||||||
}
|
}
|
||||||
let subtitle;
|
let subtitle;
|
||||||
if (connecting) {
|
if (supportedVersionsStatus === 'expired') {
|
||||||
|
subtitle = 'Cannot connect';
|
||||||
|
} else if (connecting) {
|
||||||
subtitle = I18n.t('Connecting');
|
subtitle = I18n.t('Connecting');
|
||||||
} else if (isFetching) {
|
} else if (isFetching) {
|
||||||
subtitle = I18n.t('Updating');
|
subtitle = I18n.t('Updating');
|
||||||
|
|
|
@ -26,7 +26,7 @@ import { goRoom } from '../../lib/methods/helpers/goRoom';
|
||||||
import SafeAreaView from '../../containers/SafeAreaView';
|
import SafeAreaView from '../../containers/SafeAreaView';
|
||||||
import { withDimensions } from '../../dimensions';
|
import { withDimensions } from '../../dimensions';
|
||||||
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
|
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 styles from './styles';
|
||||||
import ServerDropdown from './ServerDropdown';
|
import ServerDropdown from './ServerDropdown';
|
||||||
import ListHeader, { TEncryptionBanner } from './ListHeader';
|
import ListHeader, { TEncryptionBanner } from './ListHeader';
|
||||||
|
@ -44,9 +44,10 @@ import {
|
||||||
isTablet,
|
isTablet,
|
||||||
compareServerVersion
|
compareServerVersion
|
||||||
} from '../../lib/methods/helpers';
|
} 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 { Services } from '../../lib/services';
|
||||||
import audioPlayer from '../../lib/methods/audioPlayer';
|
import audioPlayer from '../../lib/methods/audioPlayer';
|
||||||
|
import { SupportedVersionsExpired } from '../../containers/SupportedVersions';
|
||||||
|
|
||||||
type TNavigation = CompositeNavigationProp<
|
type TNavigation = CompositeNavigationProp<
|
||||||
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
|
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
|
||||||
|
@ -74,6 +75,7 @@ interface IRoomsListViewProps {
|
||||||
useRealName: boolean;
|
useRealName: boolean;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
notificationPresenceCap: boolean;
|
notificationPresenceCap: boolean;
|
||||||
|
supportedVersionsStatus: TSVStatus;
|
||||||
subscribedRoom: string;
|
subscribedRoom: string;
|
||||||
width: number;
|
width: number;
|
||||||
insets: {
|
insets: {
|
||||||
|
@ -144,7 +146,8 @@ const shouldUpdateProps = [
|
||||||
'createDirectMessagePermission',
|
'createDirectMessagePermission',
|
||||||
'createPublicChannelPermission',
|
'createPublicChannelPermission',
|
||||||
'createPrivateChannelPermission',
|
'createPrivateChannelPermission',
|
||||||
'createDiscussionPermission'
|
'createDiscussionPermission',
|
||||||
|
'supportedVersionsStatus'
|
||||||
];
|
];
|
||||||
|
|
||||||
const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread'];
|
const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread'];
|
||||||
|
@ -333,7 +336,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
createDirectMessagePermission,
|
createDirectMessagePermission,
|
||||||
createDiscussionPermission,
|
createDiscussionPermission,
|
||||||
showAvatar,
|
showAvatar,
|
||||||
displayMode
|
displayMode,
|
||||||
|
supportedVersionsStatus
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { item } = this.state;
|
const { item } = this.state;
|
||||||
|
|
||||||
|
@ -356,7 +360,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
if (
|
if (
|
||||||
insets.left !== prevProps.insets.left ||
|
insets.left !== prevProps.insets.left ||
|
||||||
insets.right !== prevProps.insets.right ||
|
insets.right !== prevProps.insets.right ||
|
||||||
notificationPresenceCap !== prevProps.notificationPresenceCap
|
notificationPresenceCap !== prevProps.notificationPresenceCap ||
|
||||||
|
supportedVersionsStatus !== prevProps.supportedVersionsStatus
|
||||||
) {
|
) {
|
||||||
this.setHeader();
|
this.setHeader();
|
||||||
}
|
}
|
||||||
|
@ -409,7 +414,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
|
|
||||||
getHeader = (): StackNavigationOptions => {
|
getHeader = (): StackNavigationOptions => {
|
||||||
const { searching, canCreateRoom } = this.state;
|
const { searching, canCreateRoom } = this.state;
|
||||||
const { navigation, isMasterDetail, notificationPresenceCap } = this.props;
|
const { navigation, isMasterDetail, notificationPresenceCap, supportedVersionsStatus, theme } = this.props;
|
||||||
if (searching) {
|
if (searching) {
|
||||||
return {
|
return {
|
||||||
headerTitleAlign: 'left',
|
headerTitleAlign: 'left',
|
||||||
|
@ -425,6 +430,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 {
|
return {
|
||||||
headerTitleAlign: 'left',
|
headerTitleAlign: 'left',
|
||||||
headerTitleContainerStyle: { flex: 1, marginHorizontal: 4, maxWidth: undefined },
|
headerTitleContainerStyle: { flex: 1, marginHorizontal: 4, maxWidth: undefined },
|
||||||
|
@ -439,17 +454,33 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
: // @ts-ignore
|
: // @ts-ignore
|
||||||
() => navigation.toggleDrawer()
|
() => navigation.toggleDrawer()
|
||||||
}
|
}
|
||||||
badge={() => (notificationPresenceCap ? <HeaderButton.BadgeWarn /> : null)}
|
badge={() => getBadge()}
|
||||||
|
disabled={supportedVersionsStatus === 'expired'}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
headerTitle: () => <RoomsListHeaderView />,
|
headerTitle: () => <RoomsListHeaderView />,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<HeaderButton.Container>
|
<HeaderButton.Container>
|
||||||
{canCreateRoom ? (
|
{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}
|
) : null}
|
||||||
<HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' />
|
<HeaderButton.Item
|
||||||
<HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' />
|
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>
|
</HeaderButton.Container>
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
@ -899,7 +930,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
|
|
||||||
renderScroll = () => {
|
renderScroll = () => {
|
||||||
const { loading, chats, search, searching } = this.state;
|
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;
|
const height = displayMode === DisplayMode.Condensed ? ROW_HEIGHT_CONDENSED : ROW_HEIGHT;
|
||||||
|
|
||||||
|
@ -907,6 +938,10 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
||||||
return <ActivityIndicator />;
|
return <ActivityIndicator />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (supportedVersionsStatus === 'expired') {
|
||||||
|
return <SupportedVersionsExpired />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={this.getScrollRef}
|
ref={this.getScrollRef}
|
||||||
|
@ -952,6 +987,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
user: getUserSelector(state),
|
user: getUserSelector(state),
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||||
|
supportedVersionsStatus: state.supportedVersions.status,
|
||||||
server: state.server.server,
|
server: state.server.server,
|
||||||
changingServer: state.server.changingServer,
|
changingServer: state.server.changingServer,
|
||||||
searchText: state.rooms.searchText,
|
searchText: state.rooms.searchText,
|
||||||
|
|
|
@ -10,13 +10,14 @@ interface SidebarItemProps {
|
||||||
left: JSX.Element;
|
left: JSX.Element;
|
||||||
right?: JSX.Element;
|
right?: JSX.Element;
|
||||||
text: string;
|
text: string;
|
||||||
|
textColor?: string;
|
||||||
current?: boolean;
|
current?: boolean;
|
||||||
onPress(): void;
|
onPress(): void;
|
||||||
testID: string;
|
testID: string;
|
||||||
theme: TSupportedThemes;
|
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
|
<Touch
|
||||||
key={testID}
|
key={testID}
|
||||||
testID={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.itemHorizontal}>{left}</View>
|
||||||
<View style={styles.itemCenter}>
|
<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}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -21,9 +21,11 @@ import Navigation from '../../lib/navigation/appNavigation';
|
||||||
import SidebarItem from './SidebarItem';
|
import SidebarItem from './SidebarItem';
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import { DrawerParamList } from '../../stacks/types';
|
import { DrawerParamList } from '../../stacks/types';
|
||||||
import { IApplicationState, IUser } from '../../definitions';
|
import { IApplicationState, IUser, TSVStatus } from '../../definitions';
|
||||||
import * as List from '../../containers/List';
|
import * as List from '../../containers/List';
|
||||||
|
import { IActionSheetProvider, showActionSheetRef, withActionSheet } from '../../containers/ActionSheet';
|
||||||
import { setNotificationPresenceCap } from '../../actions/app';
|
import { setNotificationPresenceCap } from '../../actions/app';
|
||||||
|
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||||
|
|
||||||
interface ISidebarState {
|
interface ISidebarState {
|
||||||
showStatus: boolean;
|
showStatus: boolean;
|
||||||
|
@ -42,11 +44,13 @@ interface ISidebarProps {
|
||||||
allowStatusMessage: boolean;
|
allowStatusMessage: boolean;
|
||||||
notificationPresenceCap: boolean;
|
notificationPresenceCap: boolean;
|
||||||
Presence_broadcast_disabled: boolean;
|
Presence_broadcast_disabled: boolean;
|
||||||
|
supportedVersionsStatus: TSVStatus;
|
||||||
isMasterDetail: boolean;
|
isMasterDetail: boolean;
|
||||||
viewStatisticsPermission: string[];
|
viewStatisticsPermission: string[];
|
||||||
viewRoomAdministrationPermission: string[];
|
viewRoomAdministrationPermission: string[];
|
||||||
viewUserAdministrationPermission: string[];
|
viewUserAdministrationPermission: string[];
|
||||||
viewPrivilegedSettingPermission: string[];
|
viewPrivilegedSettingPermission: string[];
|
||||||
|
showActionSheet: IActionSheetProvider['showActionSheet'];
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
|
@ -197,6 +201,15 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onPressSupportedVersionsWarning = () => {
|
||||||
|
const { isMasterDetail } = this.props;
|
||||||
|
if (isMasterDetail) {
|
||||||
|
Navigation.navigate('ModalStackNavigator', { screen: 'SupportedVersionsWarning' });
|
||||||
|
} else {
|
||||||
|
showActionSheetRef({ children: <SupportedVersionsWarning /> });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
renderAdmin = () => {
|
renderAdmin = () => {
|
||||||
const { theme, isMasterDetail } = this.props;
|
const { theme, isMasterDetail } = this.props;
|
||||||
if (!this.getIsAdmin()) {
|
if (!this.getIsAdmin()) {
|
||||||
|
@ -286,6 +299,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() {
|
render() {
|
||||||
const { user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme } = this.props;
|
const { user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme } = this.props;
|
||||||
|
|
||||||
|
@ -323,6 +353,9 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
|
||||||
|
<List.Separator />
|
||||||
|
{this.renderSupportedVersionsWarn()}
|
||||||
|
|
||||||
<List.Separator />
|
<List.Separator />
|
||||||
|
|
||||||
{allowStatusMessage ? this.renderCustomStatus() : null}
|
{allowStatusMessage ? this.renderCustomStatus() : null}
|
||||||
|
@ -350,6 +383,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
||||||
Presence_broadcast_disabled: state.settings.Presence_broadcast_disabled as boolean,
|
Presence_broadcast_disabled: state.settings.Presence_broadcast_disabled as boolean,
|
||||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||||
|
supportedVersionsStatus: state.supportedVersions.status,
|
||||||
isMasterDetail: state.app.isMasterDetail,
|
isMasterDetail: state.app.isMasterDetail,
|
||||||
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
||||||
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
||||||
|
@ -357,4 +391,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
||||||
viewPrivilegedSettingPermission: state.permissions['view-privileged-setting'] as string[]
|
viewPrivilegedSettingPermission: state.permissions['view-privileged-setting'] as string[]
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(withTheme(Sidebar));
|
export default connect(mapStateToProps)(withActionSheet(withTheme(Sidebar)));
|
||||||
|
|
|
@ -478,7 +478,7 @@ describe('Room screen', () => {
|
||||||
.toExist()
|
.toExist()
|
||||||
.withTimeout(2000);
|
.withTimeout(2000);
|
||||||
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
await expect(element(by.id('action-sheet-handle'))).toBeVisible();
|
||||||
// open the action-sheet, then swipe the scroll inside the action-sheet
|
// Fix android flaky test. Close the action sheet, then re-open again
|
||||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||||
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
|
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
|
||||||
await sleep(300); // wait for animation
|
await sleep(300); // wait for animation
|
||||||
|
|
|
@ -237,8 +237,9 @@ describe('Threads', () => {
|
||||||
.withTimeout(5000);
|
.withTimeout(5000);
|
||||||
await element(by.id(`message-thread-button-${thread}`)).tap();
|
await element(by.id(`message-thread-button-${thread}`)).tap();
|
||||||
await tryTapping(element(by[textMatcher]('replied')).atIndex(0), 2000, true);
|
await tryTapping(element(by[textMatcher]('replied')).atIndex(0), 2000, true);
|
||||||
// open the action-sheet, then swipe the scroll inside the action-sheet
|
// Fix android flaky test. Close the action sheet, then re-open again
|
||||||
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
await element(by.id('action-sheet-handle')).swipe('up', 'fast', 0.5);
|
||||||
|
await sleep(1000); // wait for animation
|
||||||
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
|
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
|
||||||
await sleep(300); // wait for animation
|
await sleep(300); // wait for animation
|
||||||
await element(by[textMatcher]('Delete')).atIndex(0).tap();
|
await element(by[textMatcher]('Delete')).atIndex(0).tap();
|
||||||
|
|
|
@ -137,9 +137,10 @@ describe('Auto Translate', () => {
|
||||||
|
|
||||||
// verify default language is checked
|
// verify default language is checked
|
||||||
await waitFor(element(by.id(`auto-translate-view-${languages.default}`)))
|
await waitFor(element(by.id(`auto-translate-view-${languages.default}`)))
|
||||||
.toBeVisible()
|
.toExist()
|
||||||
.whileElement(by.id('auto-translate-view'))
|
.whileElement(by.id('auto-translate-view'))
|
||||||
.scroll(750, 'down');
|
.scroll(750, 'down');
|
||||||
|
await element(by.id('auto-translate-view')).swipe('up', 'slow', 0.5);
|
||||||
await waitForVisible(`auto-translate-view-${languages.default}-check`);
|
await waitForVisible(`auto-translate-view-${languages.default}-check`);
|
||||||
|
|
||||||
// enable translated language
|
// enable translated language
|
||||||
|
@ -219,6 +220,7 @@ describe('Auto Translate', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should don't see action to View original when disable auto translate`, async () => {
|
it(`should don't see action to View original when disable auto translate`, async () => {
|
||||||
|
await searchMessage(oldMessage[languages.default] as string, textMatcher); // will scroll the messages list to the last one
|
||||||
await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher);
|
await waitForVisibleTextMatcher(oldMessage[languages.default] as string, textMatcher);
|
||||||
await tryTapping(element(by[textMatcher](oldMessage[languages.default] as string)).atIndex(0), 2000, true);
|
await tryTapping(element(by[textMatcher](oldMessage[languages.default] as string)).atIndex(0), 2000, true);
|
||||||
|
|
||||||
|
|
|
@ -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 mockClipboard from '@react-native-clipboard/clipboard/jest/clipboard-mock.js';
|
||||||
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
|
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-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 = () => {};
|
global.__reanimatedWorkletInit = () => {};
|
||||||
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
|
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
|
||||||
|
|
||||||
|
@ -28,8 +42,6 @@ jest.mock('expo-haptics', () => jest.fn(() => null));
|
||||||
|
|
||||||
jest.mock('./app/lib/database', () => jest.fn(() => null));
|
jest.mock('./app/lib/database', () => jest.fn(() => null));
|
||||||
|
|
||||||
const mockedNavigate = jest.fn();
|
|
||||||
|
|
||||||
jest.mock('@react-navigation/native', () => ({
|
jest.mock('@react-navigation/native', () => ({
|
||||||
...jest.requireActual('@react-navigation/native'),
|
...jest.requireActual('@react-navigation/native'),
|
||||||
useNavigation: () => ({
|
useNavigation: () => ({
|
|
@ -84,6 +84,7 @@
|
||||||
"i18n-js": "3.9.2",
|
"i18n-js": "3.9.2",
|
||||||
"js-base64": "3.6.1",
|
"js-base64": "3.6.1",
|
||||||
"js-sha256": "^0.9.0",
|
"js-sha256": "^0.9.0",
|
||||||
|
"jsrsasign": "^10.8.6",
|
||||||
"lint-staged": "^11.1.0",
|
"lint-staged": "^11.1.0",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"moment": "2.29.4",
|
"moment": "2.29.4",
|
||||||
|
@ -172,13 +173,14 @@
|
||||||
"@storybook/addon-storyshots": "6.3",
|
"@storybook/addon-storyshots": "6.3",
|
||||||
"@storybook/react": "6.3",
|
"@storybook/react": "6.3",
|
||||||
"@storybook/react-native": "^6.0.1-beta.7",
|
"@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-hooks": "^8.0.1",
|
||||||
"@testing-library/react-native": "^9.0.0",
|
"@testing-library/react-native": "^12.1.2",
|
||||||
"@types/bytebuffer": "^5.0.44",
|
"@types/bytebuffer": "^5.0.44",
|
||||||
"@types/ejson": "^2.1.3",
|
"@types/ejson": "^2.1.3",
|
||||||
"@types/i18n-js": "^3.8.3",
|
"@types/i18n-js": "^3.8.3",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
|
"@types/jsrsasign": "^10.5.8",
|
||||||
"@types/lodash": "^4.14.188",
|
"@types/lodash": "^4.14.188",
|
||||||
"@types/react": "^17.0.14",
|
"@types/react": "^17.0.14",
|
||||||
"@types/react-native": "0.68.1",
|
"@types/react-native": "0.68.1",
|
||||||
|
@ -241,8 +243,7 @@
|
||||||
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
|
".+\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/fileMock.js"
|
||||||
},
|
},
|
||||||
"setupFilesAfterEnv": [
|
"setupFilesAfterEnv": [
|
||||||
"@testing-library/jest-native/extend-expect",
|
"./jest.setup.ts"
|
||||||
"./jest.setup.js"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"jest-junit": {
|
"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"
|
190
yarn.lock
190
yarn.lock
|
@ -4564,6 +4564,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sinclair/typebox" "^0.24.1"
|
"@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":
|
"@jest/source-map@^28.1.2":
|
||||||
version "28.1.2"
|
version "28.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24"
|
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24"
|
||||||
|
@ -4645,15 +4652,6 @@
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
write-file-atomic "^4.0.1"
|
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":
|
"@jest/types@^25.5.0":
|
||||||
version "25.5.0"
|
version "25.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||||
|
@ -5677,6 +5675,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794"
|
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794"
|
||||||
integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==
|
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":
|
"@sinonjs/commons@^1.7.0":
|
||||||
version "1.8.0"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||||
|
@ -6282,17 +6285,16 @@
|
||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
store2 "^2.12.0"
|
store2 "^2.12.0"
|
||||||
|
|
||||||
"@testing-library/jest-native@^4.0.4":
|
"@testing-library/jest-native@^5.4.2":
|
||||||
version "4.0.4"
|
version "5.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-4.0.4.tgz#25e2046896118f887683202a6e5fd8a4056131cd"
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.3.tgz#9334c68eaf45db9eb20d0876728cc5d7fc2c3ea2"
|
||||||
integrity sha512-4q5FeTFyFgPCmQH18uMJsZkVnYvBtK24yhSfbd9hQi0SZzCpbjSeQQcsGXIaX+WjWcMeeip8B7NUvZmLhGHiZw==
|
integrity sha512-/sSDGaOuE+PJ1Z9Kp4u7PQScSVVXGud59I/qsBFFJvIbcn4P6yYw6cBnBmbPF+X9aRIsTJRDl6gzw5ZkJNm66w==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^2.4.1"
|
chalk "^4.1.2"
|
||||||
jest-diff "^24.0.0"
|
jest-diff "^29.0.1"
|
||||||
jest-matcher-utils "^24.0.0"
|
jest-matcher-utils "^29.0.1"
|
||||||
pretty-format "^27.3.1"
|
pretty-format "^29.0.3"
|
||||||
ramda "^0.26.1"
|
redent "^3.0.0"
|
||||||
redent "^2.0.0"
|
|
||||||
|
|
||||||
"@testing-library/react-hooks@^8.0.1":
|
"@testing-library/react-hooks@^8.0.1":
|
||||||
version "8.0.1"
|
version "8.0.1"
|
||||||
|
@ -6302,12 +6304,12 @@
|
||||||
"@babel/runtime" "^7.12.5"
|
"@babel/runtime" "^7.12.5"
|
||||||
react-error-boundary "^3.1.0"
|
react-error-boundary "^3.1.0"
|
||||||
|
|
||||||
"@testing-library/react-native@^9.0.0":
|
"@testing-library/react-native@^12.1.2":
|
||||||
version "9.0.0"
|
version "12.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-9.0.0.tgz#e9c63411e93d2e8e70d744b12aeb78c58025c5fc"
|
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-12.2.2.tgz#4b2275d5d1feb689c9b1e5cd9cb03ffe32a43228"
|
||||||
integrity sha512-UE3FWOsDUr+2l3Pg6JTpn2rV5uzYsxIus6ZyN1uMOTmn30bIuBBDDlWQtdWGJx92YcY4xgJA4vViCEKv7wVzJA==
|
integrity sha512-aLr7YQ6pyn8PbLmdbtADG2aKcmarTLI7VhgWNVzJLxQHOtsDxLpJGKMSw10j406BE/GyGHbB0Gln3Of8/2TjnA==
|
||||||
dependencies:
|
dependencies:
|
||||||
pretty-format "^27.0.0"
|
pretty-format "^29.0.0"
|
||||||
|
|
||||||
"@tootallnate/once@1":
|
"@tootallnate/once@1":
|
||||||
version "1.1.2"
|
version "1.1.2"
|
||||||
|
@ -6576,6 +6578,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
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":
|
"@types/lodash@^4.14.175":
|
||||||
version "4.14.182"
|
version "4.14.182"
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||||
|
@ -6880,13 +6887,6 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||||
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
|
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":
|
"@types/yargs@^15.0.0":
|
||||||
version "15.0.5"
|
version "15.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
|
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
|
||||||
|
@ -7430,7 +7430,7 @@ ansi-regex@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||||
|
|
||||||
ansi-regex@^4.0.0, ansi-regex@^4.1.0:
|
ansi-regex@^4.1.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||||
|
@ -10165,11 +10165,6 @@ detox@^20.1.2:
|
||||||
yargs-parser "^20.2.9"
|
yargs-parser "^20.2.9"
|
||||||
yargs-unparser "^2.0.0"
|
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:
|
diff-sequences@^25.2.6:
|
||||||
version "25.2.6"
|
version "25.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
||||||
|
@ -10185,6 +10180,11 @@ diff-sequences@^28.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6"
|
||||||
integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==
|
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:
|
diff@^4.0.1:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||||
|
@ -12908,11 +12908,6 @@ imurmurhash@^0.1.4:
|
||||||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
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:
|
indent-string@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||||
|
@ -13700,16 +13695,6 @@ jest-config@^28.1.3:
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
strip-json-comments "^3.1.1"
|
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:
|
jest-diff@^25.2.1:
|
||||||
version "25.5.0"
|
version "25.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
||||||
|
@ -13740,6 +13725,16 @@ jest-diff@^28.1.3:
|
||||||
jest-get-type "^28.0.2"
|
jest-get-type "^28.0.2"
|
||||||
pretty-format "^28.1.3"
|
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:
|
jest-docblock@^28.1.1:
|
||||||
version "28.1.1"
|
version "28.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8"
|
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8"
|
||||||
|
@ -13785,11 +13780,6 @@ jest-expo@^46.0.1:
|
||||||
lodash "^4.17.19"
|
lodash "^4.17.19"
|
||||||
react-test-renderer "~18.0.0"
|
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:
|
jest-get-type@^25.2.6:
|
||||||
version "25.2.6"
|
version "25.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
|
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
|
||||||
|
@ -13805,6 +13795,11 @@ jest-get-type@^28.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203"
|
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203"
|
||||||
integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==
|
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:
|
jest-haste-map@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa"
|
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa"
|
||||||
|
@ -13883,16 +13878,6 @@ jest-leak-detector@^28.1.3:
|
||||||
jest-get-type "^28.0.2"
|
jest-get-type "^28.0.2"
|
||||||
pretty-format "^28.1.3"
|
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:
|
jest-matcher-utils@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a"
|
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a"
|
||||||
|
@ -13913,6 +13898,16 @@ jest-matcher-utils@^28.1.3:
|
||||||
jest-get-type "^28.0.2"
|
jest-get-type "^28.0.2"
|
||||||
pretty-format "^28.1.3"
|
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:
|
jest-message-util@^26.6.2:
|
||||||
version "26.6.2"
|
version "26.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||||
|
@ -14539,6 +14534,11 @@ jsonfile@^6.0.1:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
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:
|
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb"
|
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb"
|
||||||
|
@ -17051,16 +17051,6 @@ pretty-error@^2.1.1:
|
||||||
renderkid "^2.0.1"
|
renderkid "^2.0.1"
|
||||||
utila "~0.4"
|
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:
|
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||||
version "25.5.0"
|
version "25.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||||
|
@ -17081,16 +17071,7 @@ pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2:
|
||||||
ansi-styles "^4.0.0"
|
ansi-styles "^4.0.0"
|
||||||
react-is "^17.0.1"
|
react-is "^17.0.1"
|
||||||
|
|
||||||
pretty-format@^27.0.0:
|
pretty-format@^27.5.1:
|
||||||
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:
|
|
||||||
version "27.5.1"
|
version "27.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||||
|
@ -17109,6 +17090,15 @@ pretty-format@^28.1.3:
|
||||||
ansi-styles "^5.0.0"
|
ansi-styles "^5.0.0"
|
||||||
react-is "^18.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:
|
pretty-format@^3.8.0:
|
||||||
version "3.8.0"
|
version "3.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||||
|
@ -17430,11 +17420,6 @@ ramda@^0.21.0:
|
||||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
||||||
integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=
|
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:
|
random-bytes@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||||
|
@ -17617,7 +17602,7 @@ react-hook-form@^7.34.2:
|
||||||
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de"
|
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.34.2.tgz#9ac6d1a309a7c4aaa369d1269357a70e9e9bf4de"
|
||||||
integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==
|
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"
|
version "16.13.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
@ -17704,7 +17689,7 @@ react-native-easy-grid@^0.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.15"
|
lodash "^4.17.15"
|
||||||
|
|
||||||
react-native-easy-toast@^1.2.0:
|
react-native-easy-toast@1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-easy-toast/-/react-native-easy-toast-1.2.0.tgz#0f70bcb99e3306cda4800c244bfb4a67d42276ed"
|
resolved "https://registry.yarnpkg.com/react-native-easy-toast/-/react-native-easy-toast-1.2.0.tgz#0f70bcb99e3306cda4800c244bfb4a67d42276ed"
|
||||||
integrity sha512-UtpxnRn1ME+035Uey4VR+9K0P4aVoTcWNOx5QkioWBe3LBKMPb/kZjrQ1LtvWzOyeGP4TeTUTtMX3IOPWv5MtA==
|
integrity sha512-UtpxnRn1ME+035Uey4VR+9K0P4aVoTcWNOx5QkioWBe3LBKMPb/kZjrQ1LtvWzOyeGP4TeTUTtMX3IOPWv5MtA==
|
||||||
|
@ -18269,13 +18254,13 @@ recursive-readdir@2.2.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch "3.0.4"
|
minimatch "3.0.4"
|
||||||
|
|
||||||
redent@^2.0.0:
|
redent@^3.0.0:
|
||||||
version "2.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
|
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||||
integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=
|
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||||
dependencies:
|
dependencies:
|
||||||
indent-string "^3.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^2.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
reduce-flatten@^2.0.0:
|
reduce-flatten@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
|
@ -19788,11 +19773,6 @@ strip-final-newline@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
|
||||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
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:
|
strip-indent@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
||||||
|
|
Loading…
Reference in New Issue