Merge branch 'develop' into feat.new-audio-player
This commit is contained in:
commit
b1e0e1211c
|
@ -96,16 +96,23 @@ commands:
|
|||
- ~/.pods
|
||||
- ios/Pods
|
||||
|
||||
fetch-supported-versions:
|
||||
description: "Fetch supported versions from Cloud"
|
||||
steps:
|
||||
- run:
|
||||
name: "Fetch supported versions from Cloud"
|
||||
command: sh ./scripts/fetch-supported-versions.sh
|
||||
- store_artifacts:
|
||||
path: ./app-supportedversions.json
|
||||
|
||||
android-build:
|
||||
description: "Build Android app"
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- restore_cache: *restore-npm-cache-linux
|
||||
|
||||
- run: *install-npm-modules
|
||||
|
||||
- restore_cache: *restore-gradle-cache
|
||||
- fetch-supported-versions
|
||||
|
||||
- run:
|
||||
name: Configure Gradle
|
||||
|
@ -200,6 +207,7 @@ commands:
|
|||
- run: *install-npm-modules
|
||||
- run: *update-fastlane-ios
|
||||
- manage-pods
|
||||
- fetch-supported-versions
|
||||
- run:
|
||||
name: Set Google Services
|
||||
command: |
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Custom Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"purple\\",\\"padding\\":10,\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"yellow\\",\\"fontSize\\":18},[{\\"textAlign\\":\\"left\\"}]]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Disabled Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Disabled Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":0.3}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button Loading Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":true},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"ActivityIndicator\\",\\"props\\":{\\"style\\":[{\\"padding\\":16,\\"flex\\":1},null],\\"color\\":\\"#ffffff\\"},\\"children\\":null}]}"`;
|
||||
|
||||
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Primary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#1d74f5\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#ffffff\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
||||
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Press me!\\"},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
exports[`Storyshots Button Secondary Button 1`] = `"{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Press me!\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"testID\\":\\"testButton\\",\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":12,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#2f343d\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Press me!\\"]}]}"`;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"More options\\"},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null],\\"accessibilityLabel\\":\\"Less options\\"},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
||||
exports[`Storyshots Login Services Separators 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"More options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null]},\\"children\\":[\\"More options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]},{\\"type\\":\\"View\\",\\"props\\":{\\"accessible\\":true,\\"accessibilityLabel\\":\\"Less options\\",\\"accessibilityState\\":{\\"disabled\\":false},\\"focusable\\":true,\\"collapsable\\":false,\\"style\\":{\\"paddingHorizontal\\":14,\\"justifyContent\\":\\"center\\",\\"height\\":48,\\"borderRadius\\":4,\\"marginBottom\\":0,\\"backgroundColor\\":\\"#ffffff\\",\\"opacity\\":1}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"center\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#1d74f5\\",\\"fontSize\\":16},null]},\\"children\\":[\\"Less options\\"]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"marginVertical\\":24}},\\"children\\":[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"fontSize\\":14,\\"marginLeft\\":14,\\"marginRight\\":14,\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"500\\"},{\\"color\\":\\"#9ca2a8\\"}]},\\"children\\":[\\"OR\\"]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":[{\\"height\\":1,\\"flex\\":1},{\\"backgroundColor\\":\\"#e1e5e8\\"}]},\\"children\\":null}]}]"`;
|
||||
|
||||
exports[`Storyshots Login Services Service List 1`] = `"[{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"github\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"gitlab\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"google\\"]}]}]},{\\"type\\":\\"View\\",\\"props\\":{\\"style\\":{\\"borderRadius\\":4,\\"width\\":\\"100%\\",\\"height\\":48,\\"flexDirection\\":\\"row\\",\\"alignItems\\":\\"center\\",\\"justifyContent\\":\\"center\\",\\"paddingHorizontal\\":15}},\\"children\\":[{\\"type\\":\\"Text\\",\\"props\\":{\\"selectable\\":false,\\"allowFontScaling\\":false,\\"style\\":[{\\"fontSize\\":24,\\"color\\":\\"#0d0e12\\"},[{\\"lineHeight\\":24},{\\"position\\":\\"absolute\\",\\"left\\":15,\\"top\\":12,\\"width\\":24,\\"height\\":24}],{\\"fontFamily\\":\\"custom\\",\\"fontWeight\\":\\"normal\\",\\"fontStyle\\":\\"normal\\"},{}]},\\"children\\":[\\"\\"]},{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":[{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"400\\",\\"fontSize\\":16},{\\"color\\":\\"#0d0e12\\"}]},\\"children\\":[\\"Continue with\\",\\" \\",{\\"type\\":\\"Text\\",\\"props\\":{\\"style\\":{\\"textAlign\\":\\"left\\",\\"backgroundColor\\":\\"transparent\\",\\"fontFamily\\":\\"Inter\\",\\"fontWeight\\":\\"600\\"}},\\"children\\":[\\"apple\\"]}]}]}]"`;
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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',
|
||||
'SET_CALLING'
|
||||
]);
|
||||
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Action } from 'redux';
|
|||
|
||||
import { SERVER } from './actionsTypes';
|
||||
|
||||
interface ISelectServer extends Action {
|
||||
export interface ISelectServerAction extends Action {
|
||||
server: string;
|
||||
version?: string;
|
||||
fetchVersion: boolean;
|
||||
|
@ -12,9 +12,10 @@ interface ISelectServer extends Action {
|
|||
interface ISelectServerSuccess extends Action {
|
||||
server: string;
|
||||
version: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface IServer extends Action {
|
||||
export interface IServerRequestAction extends Action {
|
||||
server: string;
|
||||
username: string | null;
|
||||
fromServerHistory: boolean;
|
||||
|
@ -24,13 +25,14 @@ interface IServerInit extends Action {
|
|||
previousServer: string;
|
||||
}
|
||||
|
||||
interface IServerFailure extends Action {
|
||||
err: any;
|
||||
}
|
||||
export type TActionServer = ISelectServerAction & ISelectServerSuccess & IServerRequestAction & IServerInit;
|
||||
|
||||
export type TActionServer = ISelectServer & ISelectServerSuccess & IServer & IServerInit & IServerFailure;
|
||||
|
||||
export function selectServerRequest(server: string, version?: string, fetchVersion = true, changeServer = false): ISelectServer {
|
||||
export function selectServerRequest(
|
||||
server: string,
|
||||
version?: string,
|
||||
fetchVersion = true,
|
||||
changeServer = false
|
||||
): ISelectServerAction {
|
||||
return {
|
||||
type: SERVER.SELECT_REQUEST,
|
||||
server,
|
||||
|
@ -40,11 +42,20 @@ export function selectServerRequest(server: string, version?: string, fetchVersi
|
|||
};
|
||||
}
|
||||
|
||||
export function selectServerSuccess(server: string, version: string): ISelectServerSuccess {
|
||||
export function selectServerSuccess({
|
||||
server,
|
||||
version,
|
||||
name
|
||||
}: {
|
||||
server: string;
|
||||
version: string;
|
||||
name: string;
|
||||
}): ISelectServerSuccess {
|
||||
return {
|
||||
type: SERVER.SELECT_SUCCESS,
|
||||
server,
|
||||
version
|
||||
version,
|
||||
name
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -54,7 +65,7 @@ export function selectServerFailure(): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServer {
|
||||
export function serverRequest(server: string, username: string | null = null, fromServerHistory = false): IServerRequestAction {
|
||||
return {
|
||||
type: SERVER.REQUEST,
|
||||
server,
|
||||
|
@ -69,10 +80,9 @@ export function serverSuccess(): Action {
|
|||
};
|
||||
}
|
||||
|
||||
export function serverFailure(err: any): IServerFailure {
|
||||
export function serverFailure(): Action {
|
||||
return {
|
||||
type: SERVER.FAILURE,
|
||||
err
|
||||
type: SERVER.FAILURE
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { Action } from 'redux';
|
||||
|
||||
import { SUPPORTED_VERSIONS } from './actionsTypes';
|
||||
import { TSVDictionary, TSVMessage, TSVStatus } from '../definitions';
|
||||
|
||||
type TSetSupportedVersions = {
|
||||
status: TSVStatus;
|
||||
message?: TSVMessage;
|
||||
i18n?: TSVDictionary;
|
||||
expiration?: string;
|
||||
};
|
||||
type TSetSupportedVersionsAction = Action & TSetSupportedVersions;
|
||||
|
||||
export type TActionSupportedVersions = TSetSupportedVersionsAction;
|
||||
|
||||
export function setSupportedVersions({ status, message, i18n, expiration }: TSetSupportedVersions): TSetSupportedVersionsAction {
|
||||
return {
|
||||
type: SUPPORTED_VERSIONS.SET,
|
||||
status,
|
||||
message,
|
||||
i18n,
|
||||
expiration
|
||||
};
|
||||
}
|
|
@ -51,8 +51,8 @@ export const withActionSheet = (Component: React.ComponentType<any>): typeof Com
|
|||
const actionSheetRef: React.Ref<IActionSheetProvider> = createRef();
|
||||
|
||||
export const ActionSheetProvider = React.memo(({ children }: { children: React.ReactElement | React.ReactElement[] }) => {
|
||||
const getContext = () => ({
|
||||
showActionSheet: (options: TActionSheetOptions) => {
|
||||
const getContext = (): IActionSheetProvider => ({
|
||||
showActionSheet: options => {
|
||||
actionSheetRef.current?.showActionSheet(options);
|
||||
},
|
||||
hideActionSheet: () => {
|
||||
|
@ -69,6 +69,10 @@ export const ActionSheetProvider = React.memo(({ children }: { children: React.R
|
|||
);
|
||||
});
|
||||
|
||||
export const hideActionSheetRef = (): void => {
|
||||
export const showActionSheetRef: IActionSheetProvider['showActionSheet'] = options => {
|
||||
actionSheetRef?.current?.showActionSheet(options);
|
||||
};
|
||||
|
||||
export const hideActionSheetRef: IActionSheetProvider['hideActionSheet'] = () => {
|
||||
actionSheetRef?.current?.hideActionSheet();
|
||||
};
|
||||
|
|
|
@ -30,7 +30,8 @@ const Avatar = React.memo(
|
|||
borderRadius = 4,
|
||||
type = SubscriptionType.DIRECT,
|
||||
avatarExternalProviderUrl,
|
||||
roomAvatarExternalProviderUrl
|
||||
roomAvatarExternalProviderUrl,
|
||||
cdnPrefix
|
||||
}: IAvatar) => {
|
||||
if ((!text && !avatar && !emoji && !rid) || !server) {
|
||||
return null;
|
||||
|
@ -61,7 +62,8 @@ const Avatar = React.memo(
|
|||
rid,
|
||||
blockUnauthenticatedAccess,
|
||||
avatarExternalProviderUrl,
|
||||
roomAvatarExternalProviderUrl
|
||||
roomAvatarExternalProviderUrl,
|
||||
cdnPrefix
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -32,9 +32,10 @@ const AvatarContainer = ({
|
|||
shallowEqual
|
||||
);
|
||||
|
||||
const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl } = useSelector((state: IApplicationState) => ({
|
||||
const { avatarExternalProviderUrl, roomAvatarExternalProviderUrl, cdnPrefix } = useSelector((state: IApplicationState) => ({
|
||||
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(
|
||||
(state: IApplicationState) =>
|
||||
|
@ -67,6 +68,7 @@ const AvatarContainer = ({
|
|||
roomAvatarExternalProviderUrl={roomAvatarExternalProviderUrl}
|
||||
avatarETag={avatarETag}
|
||||
serverVersion={serverVersion}
|
||||
cdnPrefix={cdnPrefix}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,4 +24,5 @@ export interface IAvatar {
|
|||
serverVersion?: string | null;
|
||||
avatarExternalProviderUrl?: string;
|
||||
roomAvatarExternalProviderUrl?: string;
|
||||
cdnPrefix?: string;
|
||||
}
|
||||
|
|
|
@ -44,8 +44,8 @@ describe('ButtonTests', () => {
|
|||
});
|
||||
|
||||
test('find button using accessibilityLabel', async () => {
|
||||
const { findByA11yLabel } = render(<TestButton />);
|
||||
const Button = await findByA11yLabel(testProps.title);
|
||||
const { getByLabelText } = render(<TestButton />);
|
||||
const Button = await getByLabelText(testProps.title);
|
||||
expect(Button).toBeTruthy();
|
||||
});
|
||||
|
||||
|
|
|
@ -71,9 +71,7 @@ const Button = ({
|
|||
{loading ? (
|
||||
<ActivityIndicator color={textColor} />
|
||||
) : (
|
||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]} accessibilityLabel={title}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text style={[styles.text, { color: textColor, fontSize }, styleText]}>{title}</Text>
|
||||
)}
|
||||
</Touchable>
|
||||
);
|
||||
|
|
|
@ -43,7 +43,18 @@ const styles = StyleSheet.create({
|
|||
const Item = ({ title, iconName, onPress, testID, badge, color, disabled, ...props }: IHeaderButtonItem): React.ReactElement => {
|
||||
const { colors } = useTheme();
|
||||
return (
|
||||
<PlatformPressable onPress={onPress} testID={testID} hitSlop={BUTTON_HIT_SLOP} disabled={disabled} style={styles.container}>
|
||||
<PlatformPressable
|
||||
onPress={onPress}
|
||||
testID={testID}
|
||||
hitSlop={BUTTON_HIT_SLOP}
|
||||
disabled={disabled}
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
opacity: disabled ? 0.5 : 1
|
||||
}
|
||||
]}
|
||||
>
|
||||
<>
|
||||
{iconName ? (
|
||||
<CustomIcon name={iconName} size={24} color={color || colors.headerTintColor} {...props} />
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, View } from 'react-native';
|
||||
|
||||
import { STATUS_COLORS } from '../../lib/constants';
|
||||
import UnreadBadge from '../UnreadBadge';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
@ -18,6 +17,6 @@ const styles = StyleSheet.create({
|
|||
|
||||
export const BadgeUnread = ({ ...props }): React.ReactElement => <UnreadBadge {...props} style={styles.badgeContainer} small />;
|
||||
|
||||
export const BadgeWarn = (): React.ReactElement => (
|
||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: STATUS_COLORS.disabled }]} />
|
||||
export const BadgeWarn = ({ color }: { color: string }): React.ReactElement => (
|
||||
<View style={[styles.badgeContainer, { width: 10, height: 10, backgroundColor: color }]} />
|
||||
);
|
||||
|
|
|
@ -107,7 +107,7 @@ export const Badge = () => (
|
|||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadUser={[1]} />} />
|
||||
<HeaderButton.Item iconName='threads' badge={() => <HeaderButton.BadgeUnread tunread={[1]} tunreadGroup={[1]} />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn color='red' />} />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
/>
|
||||
|
@ -120,7 +120,7 @@ const ThemeStory = ({ theme }: { theme: TSupportedThemes }) => (
|
|||
<HeaderExample
|
||||
left={() => (
|
||||
<HeaderButton.Container left>
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn />} />
|
||||
<HeaderButton.Drawer badge={() => <HeaderButton.BadgeWarn color={colors[theme].dangerColor} />} />
|
||||
<HeaderButton.Item iconName='threads' />
|
||||
</HeaderButton.Container>
|
||||
)}
|
||||
|
|
|
@ -44,6 +44,7 @@ export interface IMessageActionsProps {
|
|||
deleteOwnMessagePermission?: string[];
|
||||
pinMessagePermission?: string[];
|
||||
createDirectMessagePermission?: string[];
|
||||
createDiscussionOtherUserPermission?: string[];
|
||||
}
|
||||
|
||||
export interface IMessageActions {
|
||||
|
@ -76,6 +77,7 @@ const MessageActions = React.memo(
|
|||
deleteOwnMessagePermission,
|
||||
pinMessagePermission,
|
||||
createDirectMessagePermission,
|
||||
createDiscussionOtherUserPermission,
|
||||
serverVersion
|
||||
},
|
||||
ref
|
||||
|
@ -85,7 +87,9 @@ const MessageActions = React.memo(
|
|||
hasDeletePermission: false,
|
||||
hasForceDeletePermission: false,
|
||||
hasPinPermission: false,
|
||||
hasDeleteOwnPermission: false
|
||||
hasDeleteOwnPermission: false,
|
||||
hasCreateDirectMessagePermission: false,
|
||||
hasCreateDiscussionOtherUserPermission: false
|
||||
};
|
||||
const { showActionSheet, hideActionSheet } = useActionSheet();
|
||||
|
||||
|
@ -96,7 +100,9 @@ const MessageActions = React.memo(
|
|||
deleteMessagePermission,
|
||||
forceDeleteMessagePermission,
|
||||
pinMessagePermission,
|
||||
deleteOwnMessagePermission
|
||||
deleteOwnMessagePermission,
|
||||
createDirectMessagePermission,
|
||||
createDiscussionOtherUserPermission
|
||||
];
|
||||
const result = await hasPermission(permission, room.rid);
|
||||
permissions = {
|
||||
|
@ -104,7 +110,9 @@ const MessageActions = React.memo(
|
|||
hasDeletePermission: result[1],
|
||||
hasForceDeletePermission: result[2],
|
||||
hasPinPermission: result[3],
|
||||
hasDeleteOwnPermission: result[4]
|
||||
hasDeleteOwnPermission: result[4],
|
||||
hasCreateDirectMessagePermission: result[5],
|
||||
hasCreateDiscussionOtherUserPermission: result[6]
|
||||
};
|
||||
} catch {
|
||||
// Do nothing
|
||||
|
@ -385,7 +393,7 @@ const MessageActions = React.memo(
|
|||
}
|
||||
|
||||
// Reply in DM
|
||||
if (room.t !== 'd' && room.t !== 'l' && createDirectMessagePermission && !videoConfBlock) {
|
||||
if (room.t !== 'd' && room.t !== 'l' && permissions.hasCreateDirectMessagePermission && !videoConfBlock) {
|
||||
options.push({
|
||||
title: I18n.t('Reply_in_direct_message'),
|
||||
icon: 'arrow-back',
|
||||
|
@ -394,11 +402,13 @@ const MessageActions = React.memo(
|
|||
}
|
||||
|
||||
// Create Discussion
|
||||
options.push({
|
||||
title: I18n.t('Start_a_Discussion'),
|
||||
icon: 'discussions',
|
||||
onPress: () => handleCreateDiscussion(message)
|
||||
});
|
||||
if (permissions.hasCreateDiscussionOtherUserPermission) {
|
||||
options.push({
|
||||
title: I18n.t('Start_a_Discussion'),
|
||||
icon: 'discussions',
|
||||
onPress: () => handleCreateDiscussion(message)
|
||||
});
|
||||
}
|
||||
|
||||
if (compareServerVersion(serverVersion, 'greaterThanOrEqualTo', '6.2.0') && !videoConfBlock) {
|
||||
options.push({
|
||||
|
@ -541,7 +551,8 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
deleteOwnMessagePermission: state.permissions['delete-own-message'],
|
||||
forceDeleteMessagePermission: state.permissions['force-delete-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);
|
||||
|
|
|
@ -112,6 +112,7 @@ export interface IMessageBoxProps extends IBaseScreen<ChatsStackParamList & Mast
|
|||
isActionsEnabled: boolean;
|
||||
usedCannedResponse: string;
|
||||
uploadFilePermission: string[];
|
||||
createDiscussionPermission: string[];
|
||||
goToCannedResponses: () => void | null;
|
||||
serverVersion: string;
|
||||
}
|
||||
|
@ -130,6 +131,7 @@ interface IMessageBoxState {
|
|||
tshow: boolean;
|
||||
mentionLoading: boolean;
|
||||
permissionToUpload: boolean;
|
||||
hasCreateDiscussionPermission: boolean;
|
||||
showEmojiSearchbar: boolean;
|
||||
}
|
||||
|
||||
|
@ -184,7 +186,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
tshow: this.sendThreadToChannel,
|
||||
mentionLoading: false,
|
||||
permissionToUpload: true,
|
||||
showEmojiSearchbar: false
|
||||
showEmojiSearchbar: false,
|
||||
hasCreateDiscussionPermission: false
|
||||
};
|
||||
this.text = '';
|
||||
this.selection = { start: 0, end: 0 };
|
||||
|
@ -325,6 +328,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
mentionLoading,
|
||||
trackingType,
|
||||
permissionToUpload,
|
||||
hasCreateDiscussionPermission,
|
||||
showEmojiSearchbar
|
||||
} = this.state;
|
||||
|
||||
|
@ -337,6 +341,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
theme,
|
||||
usedCannedResponse,
|
||||
uploadFilePermission,
|
||||
createDiscussionPermission,
|
||||
goToCannedResponses
|
||||
} = this.props;
|
||||
if (nextProps.theme !== theme) {
|
||||
|
@ -378,6 +383,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (nextState.permissionToUpload !== permissionToUpload) {
|
||||
return true;
|
||||
}
|
||||
if (nextState.hasCreateDiscussionPermission !== hasCreateDiscussionPermission) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextState.mentions, mentions)) {
|
||||
return true;
|
||||
}
|
||||
|
@ -390,6 +398,9 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
if (!dequal(nextProps.uploadFilePermission, uploadFilePermission)) {
|
||||
return true;
|
||||
}
|
||||
if (!dequal(nextProps.createDiscussionPermission, createDiscussionPermission)) {
|
||||
return true;
|
||||
}
|
||||
if (nextProps.usedCannedResponse !== usedCannedResponse) {
|
||||
return true;
|
||||
}
|
||||
|
@ -400,13 +411,18 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps: IMessageBoxProps) {
|
||||
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled } = this.props;
|
||||
const { uploadFilePermission, goToCannedResponses, replyWithMention, threadsEnabled, createDiscussionPermission } =
|
||||
this.props;
|
||||
if (prevProps.replyWithMention !== replyWithMention) {
|
||||
if (threadsEnabled && replyWithMention) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -441,7 +457,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
}
|
||||
|
||||
setOptions = async () => {
|
||||
const { uploadFilePermission, rid } = this.props;
|
||||
const { uploadFilePermission, rid, createDiscussionPermission } = this.props;
|
||||
|
||||
// Servers older than 4.2
|
||||
if (!uploadFilePermission) {
|
||||
|
@ -449,8 +465,8 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const permissionToUpload = await hasPermission([uploadFilePermission], rid);
|
||||
this.setState({ permissionToUpload: permissionToUpload[0] });
|
||||
const permissions = await hasPermission([uploadFilePermission, createDiscussionPermission], rid);
|
||||
this.setState({ permissionToUpload: permissions[0], hasCreateDiscussionPermission: permissions[1] });
|
||||
};
|
||||
|
||||
onChangeText: any = (text: string): void => {
|
||||
|
@ -877,7 +893,7 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
|
||||
showMessageBoxActions = () => {
|
||||
logEvent(events.ROOM_SHOW_BOX_ACTIONS);
|
||||
const { permissionToUpload } = this.state;
|
||||
const { permissionToUpload, hasCreateDiscussionPermission } = this.state;
|
||||
const { showActionSheet, goToCannedResponses } = this.props;
|
||||
|
||||
const options: TActionSheetOptionsItem[] = [];
|
||||
|
@ -913,11 +929,13 @@ class MessageBox extends Component<IMessageBoxProps, IMessageBoxState> {
|
|||
);
|
||||
}
|
||||
|
||||
options.push({
|
||||
title: I18n.t('Create_Discussion'),
|
||||
icon: 'discussions',
|
||||
onPress: this.createDiscussion
|
||||
});
|
||||
if (hasCreateDiscussionPermission) {
|
||||
options.push({
|
||||
title: I18n.t('Create_Discussion'),
|
||||
icon: 'discussions',
|
||||
onPress: this.createDiscussion
|
||||
});
|
||||
}
|
||||
|
||||
this.closeEmojiAndAction(showActionSheet, { options });
|
||||
};
|
||||
|
@ -1316,6 +1334,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
FileUpload_MaxFileSize: state.settings.FileUpload_MaxFileSize,
|
||||
Message_AudioRecorderEnabled: state.settings.Message_AudioRecorderEnabled,
|
||||
uploadFilePermission: state.permissions['mobile-upload-file'],
|
||||
createDiscussionPermission: state.permissions['start-discussion'],
|
||||
serverVersion: state.server.version
|
||||
});
|
||||
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import React from 'react';
|
||||
// @ts-ignore // TODO: Remove on react-native update
|
||||
import { Pressable, Text, View } from 'react-native';
|
||||
import FastImage from 'react-native-fast-image';
|
||||
|
||||
import { IServerInfo } from '../../definitions';
|
||||
import Check from '../Check';
|
||||
import styles, { ROW_HEIGHT } from './styles';
|
||||
import { themes } from '../../lib/constants';
|
||||
|
@ -13,7 +11,12 @@ import { useTheme } from '../../theme';
|
|||
export { ROW_HEIGHT };
|
||||
|
||||
export interface IServerItem {
|
||||
item: IServerInfo;
|
||||
item: {
|
||||
id: string;
|
||||
iconURL: string;
|
||||
name: string;
|
||||
useRealName?: boolean;
|
||||
};
|
||||
onPress(): void;
|
||||
onLongPress?(): void;
|
||||
hasCheck?: boolean;
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
import { View, Text, Linking } from 'react-native';
|
||||
|
||||
import I18n from '../../i18n';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import Button from '../Button';
|
||||
import { styles } from './styles';
|
||||
import { LEARN_MORE_URL } from './constants';
|
||||
|
||||
export const SupportedVersionsExpired = () => {
|
||||
const { colors } = useTheme();
|
||||
const { name } = useAppSelector(state => state.server);
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { paddingTop: 120, backgroundColor: colors.focusedBackground }]}>
|
||||
<View style={styles.iconContainer}>
|
||||
<CustomIcon name='warning' size={36} color={colors.dangerColor} />
|
||||
</View>
|
||||
<Text style={[styles.title, { color: colors.titleText }]}>
|
||||
{I18n.t('Supported_versions_expired_title', { workspace_name: name })}
|
||||
</Text>
|
||||
<Text style={[styles.description, { color: colors.bodyText }]}>{I18n.t('Supported_versions_expired_description')}</Text>
|
||||
<Button
|
||||
title={I18n.t('Learn_more')}
|
||||
type='secondary'
|
||||
backgroundColor={colors.chatComponentBackground}
|
||||
onPress={() => Linking.openURL(LEARN_MORE_URL)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react-native';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { mockedStore } from '../../reducers/mockedStore';
|
||||
import { SupportedVersionsWarning } from './SupportedVersionsWarning';
|
||||
import { setUser } from '../../actions/login';
|
||||
import { setSupportedVersions } from '../../actions/supportedVersions';
|
||||
import { selectServerSuccess } from '../../actions/server';
|
||||
|
||||
const Render = () => (
|
||||
<Provider store={mockedStore}>
|
||||
<SupportedVersionsWarning />
|
||||
</Provider>
|
||||
);
|
||||
|
||||
const TODAY = '2023-04-01T00:00:00.000Z';
|
||||
jest.useFakeTimers('modern');
|
||||
jest.setSystemTime(new Date(TODAY));
|
||||
|
||||
describe('SupportedVersionsWarning', () => {
|
||||
test('empty', () => {
|
||||
render(<Render />);
|
||||
expect(screen.queryByTestId('sv-warn-title')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-subtitle')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-description')).toBeNull();
|
||||
expect(screen.queryByTestId('sv-warn-button')).toBeNull();
|
||||
});
|
||||
|
||||
test('render properly', () => {
|
||||
mockedStore.dispatch(
|
||||
setUser({ language: 'en', username: 'rocket.cat', emails: [{ address: 'test@test.com', verified: true }] })
|
||||
);
|
||||
mockedStore.dispatch(selectServerSuccess({ server: 'https://example.com', version: '1.0', name: 'Test Server' }));
|
||||
mockedStore.dispatch(
|
||||
setSupportedVersions({
|
||||
status: 'warn',
|
||||
message: {
|
||||
link: 'Docs page',
|
||||
title: 'title_token',
|
||||
subtitle: 'subtitle_token',
|
||||
description: 'description_token',
|
||||
remainingDays: 10,
|
||||
type: 'alert',
|
||||
params: {
|
||||
test_a: 'test A works',
|
||||
test_b: ':)'
|
||||
}
|
||||
},
|
||||
i18n: {
|
||||
en: {
|
||||
title_token: '{{instance_ws_name}} is running an unsupported version of Rocket.Chat',
|
||||
subtitle_token: 'Mobile and desktop app access to {{instance_domain}} will be cut off in {{remaining_days}} days.',
|
||||
description_token:
|
||||
'User: {{instance_username}} Email: {{instance_email}} Version: {{instance_version}} Extra params: {{test_a}} {{test_b}}'
|
||||
},
|
||||
'pt-BR': {
|
||||
title_token: 'Alô título',
|
||||
subtitle_token:
|
||||
'{{instance_ws_name}} {{instance_domain}} {{remaining_days}} {{instance_username}} {{instance_email}} {{instance_version}} {{test_a}} {{test_b}}'
|
||||
}
|
||||
},
|
||||
expiration: '2023-05-01T00:00:00.000Z'
|
||||
})
|
||||
);
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Test Server is running an unsupported version of Rocket.Chat')).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('Mobile and desktop app access to https://example.com will be cut off in 30 days.')
|
||||
).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('User: rocket.cat Email: test@test.com Version: 1.0 Extra params: test A works :)')
|
||||
).toBeOnTheScreen();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
|
||||
test('render another language', () => {
|
||||
mockedStore.dispatch(setUser({ language: 'pt-BR' }));
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Alô título')).toBeOnTheScreen();
|
||||
expect(screen.getByText('Test Server https://example.com 30 rocket.cat test@test.com 1.0 test A works :)')).toBeOnTheScreen();
|
||||
expect(screen.queryByTestId('sv-warn-description')).toBeNull();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
|
||||
test('user on unsupported language and fallback to en', () => {
|
||||
mockedStore.dispatch(setUser({ language: 'it' }));
|
||||
render(<Render />);
|
||||
expect(screen.getByText('Test Server is running an unsupported version of Rocket.Chat')).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('Mobile and desktop app access to https://example.com will be cut off in 30 days.')
|
||||
).toBeOnTheScreen();
|
||||
expect(
|
||||
screen.getByText('User: rocket.cat Email: test@test.com Version: 1.0 Extra params: test A works :)')
|
||||
).toBeOnTheScreen();
|
||||
expect(screen.getByText('Learn more')).toBeOnTheScreen();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
import React, { ReactElement, useLayoutEffect } from 'react';
|
||||
import { View, Text, Linking } from 'react-native';
|
||||
|
||||
import { useTheme } from '../../theme';
|
||||
import { CustomIcon } from '../CustomIcon';
|
||||
import Button from '../Button';
|
||||
import { styles } from './styles';
|
||||
import { useSupportedVersionMessage } from './useSupportedVersionMessage';
|
||||
import * as HeaderButton from '../HeaderButton';
|
||||
import I18n from '../../i18n';
|
||||
import { LEARN_MORE_URL } from './constants';
|
||||
|
||||
export const SupportedVersionsWarning = ({ navigation, route }: { navigation?: any; route?: any }): ReactElement | null => {
|
||||
const { colors } = useTheme();
|
||||
const message = useSupportedVersionMessage();
|
||||
|
||||
useLayoutEffect(() => {
|
||||
navigation?.setOptions({
|
||||
title: I18n.t('Supported_versions_warning_update_required')
|
||||
});
|
||||
|
||||
if (route?.params?.showCloseButton) {
|
||||
navigation?.setOptions({
|
||||
headerLeft: () => <HeaderButton.CloseModal />
|
||||
});
|
||||
}
|
||||
}, [navigation, route]);
|
||||
|
||||
if (!message) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.container, { backgroundColor: colors.focusedBackground }]}>
|
||||
<View style={styles.iconContainer}>
|
||||
<CustomIcon name='warning' size={36} color={colors.dangerColor} />
|
||||
</View>
|
||||
{message.title ? (
|
||||
<Text testID='sv-warn-title' style={[styles.title, { color: colors.titleText }]}>
|
||||
{message.title}
|
||||
</Text>
|
||||
) : null}
|
||||
{message.subtitle ? (
|
||||
<Text testID='sv-warn-subtitle' style={[styles.subtitle, { color: colors.bodyText }]}>
|
||||
{message.subtitle}
|
||||
</Text>
|
||||
) : null}
|
||||
{message.description ? (
|
||||
<Text testID='sv-warn-description' style={[styles.description, { color: colors.bodyText }]}>
|
||||
{message.description}
|
||||
</Text>
|
||||
) : null}
|
||||
<Button
|
||||
testID='sv-warn-button'
|
||||
title='Learn more'
|
||||
type='secondary'
|
||||
backgroundColor={colors.chatComponentBackground}
|
||||
onPress={() => Linking.openURL(message.link || LEARN_MORE_URL)}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export const LEARN_MORE_URL = 'https://go.rocket.chat/i/supported-versions';
|
|
@ -0,0 +1,2 @@
|
|||
export * from './SupportedVersionsWarning';
|
||||
export * from './SupportedVersionsExpired';
|
|
@ -0,0 +1,33 @@
|
|||
import { StyleSheet } from 'react-native';
|
||||
|
||||
import sharedStyles from '../../views/Styles';
|
||||
|
||||
export const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
padding: 16,
|
||||
backgroundColor: '#fff'
|
||||
},
|
||||
iconContainer: {
|
||||
alignItems: 'center',
|
||||
padding: 24
|
||||
},
|
||||
title: {
|
||||
fontSize: 20,
|
||||
lineHeight: 30,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textBold
|
||||
},
|
||||
subtitle: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textBold
|
||||
},
|
||||
description: {
|
||||
fontSize: 16,
|
||||
lineHeight: 24,
|
||||
marginBottom: 24,
|
||||
...sharedStyles.textRegular
|
||||
}
|
||||
});
|
|
@ -0,0 +1,47 @@
|
|||
import moment from 'moment';
|
||||
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
const applyParams = (message: string, params: Record<string, unknown>) => {
|
||||
const keys = Object.keys(params);
|
||||
const regex = new RegExp(`{{(${keys.join('|')})}}`, 'g');
|
||||
return message.replace(regex, (_, p1) => params[p1] as string);
|
||||
};
|
||||
|
||||
const useUser = () => {
|
||||
const { username, name, emails, language } = useAppSelector(state => state.login.user);
|
||||
const useRealName = useAppSelector(state => state.settings.UI_Use_Real_Name);
|
||||
const user = useRealName ? name : username;
|
||||
return { user, email: emails?.[0]?.address, language };
|
||||
};
|
||||
|
||||
export const useSupportedVersionMessage = () => {
|
||||
const { message, i18n, expiration } = useAppSelector(state => state.supportedVersions);
|
||||
const { name, server, version } = useAppSelector(state => state.server);
|
||||
const { language = 'en', user, email } = useUser();
|
||||
|
||||
const params = {
|
||||
instance_username: user,
|
||||
instance_email: email,
|
||||
instance_ws_name: name,
|
||||
instance_domain: server,
|
||||
remaining_days: moment(expiration).diff(new Date(), 'days'),
|
||||
instance_version: version,
|
||||
...message?.params
|
||||
};
|
||||
|
||||
if (!message || !i18n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const i18nLang = i18n[language] ?? i18n.en;
|
||||
|
||||
const getTranslation = (key: string | undefined) => (key && i18nLang[key] ? applyParams(i18nLang[key], params) : undefined);
|
||||
|
||||
return {
|
||||
title: getTranslation(message.title),
|
||||
subtitle: getTranslation(message.subtitle),
|
||||
description: getTranslation(message.description),
|
||||
link: message.link
|
||||
};
|
||||
};
|
|
@ -3,7 +3,7 @@ import { Control, Controller } from 'react-hook-form';
|
|||
|
||||
import { FormTextInput, IRCTextInputProps } from './FormTextInput';
|
||||
|
||||
interface IControlledFormTextInputProps extends IRCTextInputProps {
|
||||
interface IControlledFormTextInputProps extends Omit<IRCTextInputProps, 'inputRef'> {
|
||||
control: Control<any>;
|
||||
name: string;
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ export const ControlledFormTextInput = ({ control, name, ...props }: IControlled
|
|||
<Controller
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field: { onChange, value } }) => <FormTextInput onChangeText={onChange} value={value} {...props} />}
|
||||
render={({ field: { onChange, value, ref } }) => (
|
||||
<FormTextInput onChangeText={onChange} value={value} inputRef={ref} {...props} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,7 @@ import { IAttachment, IUserMessage } from '../../definitions';
|
|||
import { downloadMediaFile, getMediaCache } from '../../lib/methods/handleMediaDownload';
|
||||
import { fetchAutoDownloadEnabled } from '../../lib/methods/autoDownloadPreference';
|
||||
import AudioPlayer from '../AudioPlayer';
|
||||
import { useAppSelector } from '../../lib/hooks';
|
||||
|
||||
interface IMessageAudioProps {
|
||||
file: IAttachment;
|
||||
|
@ -16,6 +17,7 @@ interface IMessageAudioProps {
|
|||
getCustomEmoji: TGetCustomEmoji;
|
||||
author?: IUserMessage;
|
||||
msg?: string;
|
||||
cdnPrefix?: string;
|
||||
}
|
||||
|
||||
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 { cdnPrefix } = useAppSelector(state => ({
|
||||
cdnPrefix: state.settings.CDN_PREFIX as string
|
||||
}));
|
||||
|
||||
const getUrl = () => {
|
||||
let url = file.audio_url;
|
||||
if (url && !url.startsWith('http')) {
|
||||
url = `${baseUrl}${file.audio_url}`;
|
||||
url = `${cdnPrefix || baseUrl}${file.audio_url}`;
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
|
|
@ -13,6 +13,7 @@ import BlurComponent from './Components/BlurComponent';
|
|||
import MessageContext from './Context';
|
||||
import Touchable from './Touchable';
|
||||
import styles from './styles';
|
||||
import { isImageBase64 } from '../../lib/methods';
|
||||
|
||||
interface IMessageButton {
|
||||
children: React.ReactElement;
|
||||
|
@ -110,7 +111,12 @@ const ImageContainer = ({
|
|||
await handleAutoDownload();
|
||||
}
|
||||
};
|
||||
handleCache();
|
||||
if (isImageBase64(imgUrlToCache)) {
|
||||
setLoading(false);
|
||||
setCached(true);
|
||||
} else {
|
||||
handleCache();
|
||||
}
|
||||
}, []);
|
||||
|
||||
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';
|
||||
|
||||
export type TSVStatus = 'supported' | 'expired' | 'warn';
|
||||
|
||||
export type TSVDictionary = {
|
||||
[lng: string]: Record<string, string>;
|
||||
};
|
||||
|
||||
export type TSVMessage = {
|
||||
remainingDays: number;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
description?: string;
|
||||
type: 'info' | 'alert' | 'error';
|
||||
params?: Record<string, unknown>;
|
||||
link: string;
|
||||
};
|
||||
|
||||
export type TSVVersion = {
|
||||
version: string;
|
||||
expiration: string;
|
||||
messages?: TSVMessage[];
|
||||
};
|
||||
|
||||
export interface ISupportedVersionsData {
|
||||
timestamp: string;
|
||||
enforcementStartDate: string;
|
||||
messages?: TSVMessage[];
|
||||
versions: TSVVersion[];
|
||||
exceptions?: {
|
||||
domain: string;
|
||||
uniqueId: string;
|
||||
messages?: TSVMessage[];
|
||||
versions: TSVVersion[];
|
||||
};
|
||||
i18n?: TSVDictionary;
|
||||
}
|
||||
|
||||
export interface ISupportedVersions extends ISupportedVersionsData {
|
||||
signed: string;
|
||||
}
|
||||
|
||||
export interface IApiServerInfo {
|
||||
version: string;
|
||||
success: boolean;
|
||||
supportedVersions?: ISupportedVersions;
|
||||
minimumClientVersions?: {
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IServerInfo {
|
||||
version: string;
|
||||
success: boolean;
|
||||
supportedVersions?: ISupportedVersionsData | null; // no signed
|
||||
minimumClientVersions?: {
|
||||
desktop: string;
|
||||
mobile: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type TCloudInfo = ISupportedVersions;
|
||||
|
||||
export interface IServer {
|
||||
name: string;
|
||||
iconURL: string;
|
||||
|
@ -17,13 +79,8 @@ export interface IServer {
|
|||
uniqueID: string;
|
||||
enterpriseModules: IEnterpriseModules;
|
||||
E2E_Enable: boolean;
|
||||
}
|
||||
|
||||
export interface IServerInfo {
|
||||
id: string;
|
||||
iconURL: string;
|
||||
name: string;
|
||||
useRealName?: boolean;
|
||||
supportedVersions?: ISupportedVersionsData;
|
||||
supportedVersionsWarningAt?: Date;
|
||||
}
|
||||
|
||||
export type TServerModel = IServer & Model;
|
||||
|
|
|
@ -17,6 +17,7 @@ import { TActionUserTyping } from '../../actions/usersTyping';
|
|||
import { TActionPermissions } from '../../actions/permissions';
|
||||
import { TActionEnterpriseModules } from '../../actions/enterpriseModules';
|
||||
import { TActionVideoConf } from '../../actions/videoConf';
|
||||
import { TActionSupportedVersions } from '../../actions/supportedVersions';
|
||||
// REDUCERS
|
||||
import { IActiveUsers } from '../../reducers/activeUsers';
|
||||
import { IApp } from '../../reducers/app';
|
||||
|
@ -38,6 +39,7 @@ import { IEnterpriseModules } from '../../reducers/enterpriseModules';
|
|||
import { IVideoConf } from '../../reducers/videoConf';
|
||||
import { TActionUsersRoles } from '../../actions/usersRoles';
|
||||
import { TUsersRoles } from '../../reducers/usersRoles';
|
||||
import { ISupportedVersionsState } from '../../reducers/supportedVersions';
|
||||
|
||||
export interface IApplicationState {
|
||||
settings: TSettingsState;
|
||||
|
@ -63,6 +65,7 @@ export interface IApplicationState {
|
|||
roles: IRoles;
|
||||
videoConf: IVideoConf;
|
||||
usersRoles: TUsersRoles;
|
||||
supportedVersions: ISupportedVersionsState;
|
||||
}
|
||||
|
||||
export type TApplicationActions = TActionActiveUsers &
|
||||
|
@ -83,4 +86,5 @@ export type TApplicationActions = TActionActiveUsers &
|
|||
TActionPermissions &
|
||||
TActionEnterpriseModules &
|
||||
TActionVideoConf &
|
||||
TActionUsersRoles;
|
||||
TActionUsersRoles &
|
||||
TActionSupportedVersions;
|
||||
|
|
|
@ -692,9 +692,9 @@
|
|||
"Call_ended": "Call ended",
|
||||
"Call_was_not_answered": "Call was not answered",
|
||||
"Call_rejected": "Call rejected",
|
||||
"Call_back": "Call Back",
|
||||
"Call_again": "Call Again",
|
||||
"Call_ongoing": "Call Ongoing",
|
||||
"Call_back": "Call back",
|
||||
"Call_again": "Call again",
|
||||
"Call_ongoing": "Call ongoing",
|
||||
"Call_issue": "Call issue",
|
||||
"Joined": "Joined",
|
||||
"Calling": "Calling",
|
||||
|
@ -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.",
|
||||
"Continue": "Continue",
|
||||
"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",
|
||||
"Place_chat_on_hold": "Colocar conversa em espera",
|
||||
"Would_like_to_place_on_hold": "Gostaria de colocar essa conversa em espera?",
|
||||
"Open_Livechats": "Bate-papos em Andamento",
|
||||
"On_hold_Livechats": "Conversas Em Espera",
|
||||
"Open_Livechats": "Bate-papos em andamento",
|
||||
"On_hold_Livechats": "Conversas em espera",
|
||||
"Chat_is_on_hold": "Esta conversa está em espera devido à inatividade",
|
||||
"Resume": "Continuar",
|
||||
"Omnichannel_placed_chat_on_hold": "Conversa em espera: {{comment}}",
|
||||
|
@ -736,5 +736,8 @@
|
|||
"decline": "Recusar",
|
||||
"accept": "Aceitar",
|
||||
"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: {
|
||||
type: 'valueAsBoolean'
|
||||
},
|
||||
CDN_PREFIX: {
|
||||
type: 'valueAsString'
|
||||
},
|
||||
...deprecatedSettings
|
||||
} as const;
|
||||
|
|
|
@ -10,6 +10,7 @@ export * from './messagesStatus';
|
|||
export * from './messageTypeLoad';
|
||||
export * from './notifications';
|
||||
export * from './defaultSettings';
|
||||
export * from './supportedVersions';
|
||||
export * from './tablet';
|
||||
export * from './mediaAutoDownload';
|
||||
export * from './userAgent';
|
||||
|
|
|
@ -18,7 +18,6 @@ export const E2E_ROOM_TYPES: Record<string, string> = {
|
|||
export const THEME_PREFERENCES_KEY = 'RC_THEME_PREFERENCES_KEY';
|
||||
export const CRASH_REPORT_KEY = 'RC_CRASH_REPORT_KEY';
|
||||
export const ANALYTICS_EVENTS_KEY = 'RC_ANALYTICS_EVENTS_KEY';
|
||||
export const MIN_ROCKETCHAT_VERSION = '0.70.0';
|
||||
export const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||
export const CURRENT_SERVER = 'currentServer';
|
||||
export const CERTIFICATE_KEY = 'RC_CERTIFICATE_KEY';
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
export const SIGNED_SUPPORTED_VERSIONS_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvZ/T/RHOr6+yo/iMLUlf
|
||||
agMiMLFxQR/5Qtc85ykMBvKZqbBGb9zU68VB9n54alrbZG5FdcHkSJXgJIBXF2bk
|
||||
TGTfBi58JmltZirSWzvXoXnT4ieGNZv+BqnP9zzj9HXOVhVncbRmJPEIJOZfL9AQ
|
||||
beix3rPgZx3ZepAaoMQnz11dZKDGzkMN75WkTdf324X3DeFgLVmjsYuAcLl/AJMA
|
||||
uPKSSt0XOQUsfrT7rEqXIrj8rIJcWxIHICMRrwfjw2Qh+3pfIrh7XSzxlW4zCKBN
|
||||
RpavrrCnpOFRfkC5T9eMKLgyapjufOtbjuzu25N3urBsg6oRFNzsGXWp1C7DwUO2
|
||||
kwIDAQAB
|
||||
-----END PUBLIC KEY-----`;
|
|
@ -1,5 +1,6 @@
|
|||
import { Model } from '@nozbe/watermelondb';
|
||||
import { date, field } from '@nozbe/watermelondb/decorators';
|
||||
import { date, field, json } from '@nozbe/watermelondb/decorators';
|
||||
import { sanitizer } from '../../utils';
|
||||
|
||||
export const SERVERS_TABLE = 'servers';
|
||||
|
||||
|
@ -33,4 +34,8 @@ export default class Server extends Model {
|
|||
@field('enterprise_modules') enterpriseModules;
|
||||
|
||||
@field('e2e_enable') E2E_Enable;
|
||||
|
||||
@json('supported_versions', sanitizer) supportedVersions;
|
||||
|
||||
@date('supported_versions_warning_at') supportedVersionsWarningAt;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
export default appSchema({
|
||||
version: 13,
|
||||
version: 14,
|
||||
tables: [
|
||||
tableSchema({
|
||||
name: 'users',
|
||||
|
@ -38,7 +38,9 @@ export default appSchema({
|
|||
{ name: 'biometry', type: 'boolean', isOptional: true }, // deprecated
|
||||
{ name: 'unique_id', type: 'string', isOptional: true },
|
||||
{ name: 'enterprise_modules', type: 'string', isOptional: true },
|
||||
{ name: 'e2e_enable', type: 'boolean', isOptional: true }
|
||||
{ name: 'e2e_enable', type: 'boolean', isOptional: true },
|
||||
{ name: 'supported_versions', type: 'string', isOptional: true },
|
||||
{ name: 'supported_versions_warning_at', type: 'number', isOptional: true }
|
||||
]
|
||||
}),
|
||||
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-d',
|
||||
'start-discussion',
|
||||
'start-discussion-other-user',
|
||||
'create-team',
|
||||
'delete-c',
|
||||
'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 {
|
||||
const path = getFilePath({ type, mimeType, urlToCache: downloadUrl });
|
||||
if (!path) {
|
||||
reject();
|
||||
return;
|
||||
return reject();
|
||||
}
|
||||
downloadKey = mediaDownloadKey(downloadUrl);
|
||||
downloadQueue[downloadKey] = FileSystem.createDownloadResumable(downloadUrl, path);
|
||||
|
@ -208,9 +207,9 @@ export function downloadMediaFile({
|
|||
if (result?.uri) {
|
||||
return resolve(result.uri);
|
||||
}
|
||||
reject();
|
||||
return reject();
|
||||
} catch {
|
||||
reject();
|
||||
return reject();
|
||||
} finally {
|
||||
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) {
|
||||
let timeout: ReturnType<typeof setTimeout> | null;
|
||||
|
@ -24,6 +24,6 @@ export function debounce(func: Function, wait?: number, immediate?: boolean) {
|
|||
return _debounce;
|
||||
}
|
||||
|
||||
export function useDebounce(func: (...args: any) => any, wait?: number): (...args: any[]) => void {
|
||||
return useDebouncedCallback(func, wait || 1000);
|
||||
export function useDebounce(func: (...args: any) => any, wait?: number, options?: Options): (...args: any[]) => void {
|
||||
return useDebouncedCallback(func, wait || 1000, options);
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export const headers: CustomHeaders = {
|
|||
};
|
||||
|
||||
let _basicAuth;
|
||||
export const setBasicAuth = (basicAuth: string): void => {
|
||||
export const setBasicAuth = (basicAuth: string | null): void => {
|
||||
_basicAuth = basicAuth;
|
||||
if (basicAuth) {
|
||||
RocketChatSettings.customHeaders = { ...headers, Authorization: `Basic ${_basicAuth}` };
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { URL } from 'react-native-url-polyfill';
|
||||
|
||||
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 }) {
|
||||
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 => {
|
||||
if (LOCAL_DOCUMENT_DIRECTORY && attachmentUrl?.startsWith(LOCAL_DOCUMENT_DIRECTORY)) {
|
||||
if (
|
||||
(attachmentUrl && isImageBase64(attachmentUrl)) ||
|
||||
(LOCAL_DOCUMENT_DIRECTORY && attachmentUrl?.startsWith(LOCAL_DOCUMENT_DIRECTORY))
|
||||
) {
|
||||
return attachmentUrl;
|
||||
}
|
||||
if (attachmentUrl && attachmentUrl.startsWith('http')) {
|
||||
|
@ -19,5 +24,9 @@ export const formatAttachmentUrl = (attachmentUrl: string | undefined, userId: s
|
|||
}
|
||||
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 });
|
||||
};
|
||||
|
|
|
@ -22,7 +22,8 @@ export const getAvatarURL = ({
|
|||
blockUnauthenticatedAccess,
|
||||
serverVersion,
|
||||
avatarExternalProviderUrl,
|
||||
roomAvatarExternalProviderUrl
|
||||
roomAvatarExternalProviderUrl,
|
||||
cdnPrefix
|
||||
}: IAvatar): string => {
|
||||
let room;
|
||||
if (type === SubscriptionType.DIRECT) {
|
||||
|
@ -48,6 +49,10 @@ export const getAvatarURL = ({
|
|||
query += `&etag=${avatarETag}`;
|
||||
}
|
||||
|
||||
if (cdnPrefix) {
|
||||
server = cdnPrefix.trim().replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
if (avatar) {
|
||||
if (avatar.startsWith('http')) {
|
||||
return avatar;
|
||||
|
|
|
@ -39,3 +39,6 @@ export * from './subscribeRooms';
|
|||
export * from './serializeAsciiUrl';
|
||||
export * from './audioPlayer';
|
||||
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 { settings as RocketChatSettings, Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import { Rocketchat as RocketchatClient } from '@rocket.chat/sdk';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { InteractionManager } from 'react-native';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
@ -8,7 +7,6 @@ import log from '../methods/helpers/log';
|
|||
import { setActiveUsers } from '../../actions/activeUsers';
|
||||
import protectedFunction from '../methods/helpers/protectedFunction';
|
||||
import database from '../database';
|
||||
import { selectServerFailure } from '../../actions/server';
|
||||
import { twoFactor } from './twoFactor';
|
||||
import { store } from '../store/auxStore';
|
||||
import { loginRequest, setLoginServices, setUser } from '../../actions/login';
|
||||
|
@ -19,7 +17,7 @@ import { connectRequest, connectSuccess, disconnect as disconnectAction } from '
|
|||
import { updatePermission } from '../../actions/permissions';
|
||||
import EventEmitter from '../methods/helpers/events';
|
||||
import { updateSettings } from '../../actions/settings';
|
||||
import { defaultSettings, MIN_ROCKETCHAT_VERSION } from '../constants';
|
||||
import { defaultSettings } from '../constants';
|
||||
import {
|
||||
getSettings,
|
||||
IActiveUsers,
|
||||
|
@ -49,7 +47,7 @@ let notifyAllListener: any;
|
|||
let rolesListener: any;
|
||||
let notifyLoggedListener: any;
|
||||
|
||||
function connect({ server, logoutOnError = false }: { server: string; logoutOnError: boolean }): Promise<void> {
|
||||
function connect({ server, logoutOnError = false }: { server: string; logoutOnError?: boolean }): Promise<void> {
|
||||
return new Promise<void>(resolve => {
|
||||
if (sdk.current?.client?.host === server) {
|
||||
return resolve();
|
||||
|
@ -401,51 +399,11 @@ function disconnect() {
|
|||
return sdk.disconnect();
|
||||
}
|
||||
|
||||
async function getServerInfo(server: string) {
|
||||
try {
|
||||
const response = await RNFetchBlob.fetch('GET', `${server}/api/info`, { ...RocketChatSettings.customHeaders });
|
||||
try {
|
||||
// Try to resolve as json
|
||||
const jsonRes: { version?: string; success: boolean } = response.json();
|
||||
if (!jsonRes?.success) {
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
if (compareServerVersion(jsonRes.version, 'lowerThan', MIN_ROCKETCHAT_VERSION)) {
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Invalid_server_version', {
|
||||
currentVersion: jsonRes.version,
|
||||
minVersion: MIN_ROCKETCHAT_VERSION
|
||||
})
|
||||
};
|
||||
}
|
||||
return jsonRes;
|
||||
} catch (error) {
|
||||
// Request is successful, but response isn't a json
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e?.message) {
|
||||
if (e.message === 'Aborted') {
|
||||
store.dispatch(selectServerFailure());
|
||||
throw e;
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
message: e.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
message: I18n.t('Not_RC_Server', { contact: I18n.t('Contact_your_server_admin') })
|
||||
};
|
||||
}
|
||||
|
||||
async function getWebsocketInfo({ server }: { server: string }) {
|
||||
async function getWebsocketInfo({
|
||||
server
|
||||
}: {
|
||||
server: string;
|
||||
}): Promise<{ success: true } | { success: false; message: string }> {
|
||||
const websocketSdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: isSsl(server) });
|
||||
|
||||
try {
|
||||
|
@ -530,7 +488,6 @@ export {
|
|||
abort,
|
||||
connect,
|
||||
disconnect,
|
||||
getServerInfo,
|
||||
getWebsocketInfo,
|
||||
stopListener,
|
||||
getLoginServices,
|
||||
|
|
|
@ -999,3 +999,6 @@ export const notifyUser = (type: string, params: Record<string, any>): Promise<b
|
|||
sdk.methodCall('stream-notify-user', type, params);
|
||||
|
||||
export const getUsersRoles = (): Promise<boolean> => sdk.methodCall('getUserRoles');
|
||||
|
||||
export const getSupportedVersionsCloud = (uniqueId?: string, domain?: string) =>
|
||||
fetch(`https://releases.rocket.chat/v2/server/supportedVersions?uniqueId=${uniqueId}&domain=${domain}&source=mobile`);
|
||||
|
|
|
@ -23,6 +23,7 @@ import permissions from './permissions';
|
|||
import roles from './roles';
|
||||
import videoConf from './videoConf';
|
||||
import usersRoles from './usersRoles';
|
||||
import supportedVersions from './supportedVersions';
|
||||
|
||||
export default combineReducers({
|
||||
settings,
|
||||
|
@ -47,5 +48,6 @@ export default combineReducers({
|
|||
permissions,
|
||||
roles,
|
||||
videoConf,
|
||||
usersRoles
|
||||
usersRoles,
|
||||
supportedVersions
|
||||
});
|
||||
|
|
|
@ -41,9 +41,10 @@ describe('test server reducer', () => {
|
|||
it('should return modified store after selectServerSucess', () => {
|
||||
const server = 'https://open.rocket.chat/';
|
||||
const version = '4.1.0';
|
||||
mockedStore.dispatch(selectServerSuccess(server, version));
|
||||
const name = 'Rocket.Chat';
|
||||
mockedStore.dispatch(selectServerSuccess({ server, version, name: 'Rocket.Chat' }));
|
||||
const state = mockedStore.getState().server;
|
||||
const manipulated = { ...initialState, server, version, connected: true, loading: false };
|
||||
const manipulated = { ...initialState, server, version, connected: true, loading: false, name };
|
||||
expect(state).toEqual(manipulated);
|
||||
});
|
||||
|
||||
|
@ -61,7 +62,7 @@ describe('test server reducer', () => {
|
|||
});
|
||||
|
||||
it('should return modified store after serverRequestFailure', () => {
|
||||
mockedStore.dispatch(serverFailure('error'));
|
||||
mockedStore.dispatch(serverFailure());
|
||||
const state = mockedStore.getState().server;
|
||||
expect(state.failure).toEqual(true);
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@ export interface IServer {
|
|||
failure: boolean;
|
||||
server: string;
|
||||
version: string | null;
|
||||
name: string | null;
|
||||
loading: boolean;
|
||||
previousServer: string | null;
|
||||
changingServer: boolean;
|
||||
|
@ -18,6 +19,7 @@ export const initialState: IServer = {
|
|||
failure: false,
|
||||
server: '',
|
||||
version: null,
|
||||
name: null,
|
||||
loading: true,
|
||||
previousServer: null,
|
||||
changingServer: false
|
||||
|
@ -53,6 +55,7 @@ export default function server(state = initialState, action: TActionServer): ISe
|
|||
...state,
|
||||
server: action.server,
|
||||
version: action.version,
|
||||
name: action.name,
|
||||
connecting: false,
|
||||
connected: true,
|
||||
loading: false,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { TSVMessage } from '../definitions';
|
||||
import { setSupportedVersions } from '../actions/supportedVersions';
|
||||
import { mockedStore } from './mockedStore';
|
||||
import { initialState } from './supportedVersions';
|
||||
|
||||
describe('test supportedVersions reducer', () => {
|
||||
test('initial state', () => {
|
||||
const state = mockedStore.getState().supportedVersions;
|
||||
expect(state).toEqual(initialState);
|
||||
});
|
||||
|
||||
test('set supported versions', () => {
|
||||
const status = 'supported';
|
||||
const message: TSVMessage = {
|
||||
remainingDays: 15,
|
||||
title: 'title',
|
||||
subtitle: 'subtitle',
|
||||
description: 'description',
|
||||
type: 'info',
|
||||
link: 'Docs page'
|
||||
};
|
||||
const i18n = {
|
||||
en: {
|
||||
title: '{{workspace-name}} is running an unsupported version of Rocket.Chat',
|
||||
subtitle: 'Mobile and desktop app access to {{workspace-name}} will be cut off in XX days.',
|
||||
description:
|
||||
'An automatic 30-day warning period has been applied to allow time for a workspace admin to update workspace to a supported software version.'
|
||||
}
|
||||
};
|
||||
mockedStore.dispatch(setSupportedVersions({ status, message, i18n }));
|
||||
const state = mockedStore.getState().supportedVersions;
|
||||
expect(state).toEqual({ status, message, i18n });
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
import { TSVDictionary, TSVMessage, TSVStatus } from '../definitions';
|
||||
import { SUPPORTED_VERSIONS } from '../actions/actionsTypes';
|
||||
import { TActionSupportedVersions } from '../actions/supportedVersions';
|
||||
|
||||
export interface ISupportedVersionsState {
|
||||
status: TSVStatus;
|
||||
message?: TSVMessage;
|
||||
i18n?: TSVDictionary;
|
||||
expiration?: string;
|
||||
}
|
||||
|
||||
export const initialState: ISupportedVersionsState = { status: 'supported' };
|
||||
|
||||
export default (state = initialState, action: TActionSupportedVersions): ISupportedVersionsState => {
|
||||
switch (action.type) {
|
||||
case SUPPORTED_VERSIONS.SET:
|
||||
return {
|
||||
...state,
|
||||
status: action.status,
|
||||
message: action.message,
|
||||
i18n: action.i18n,
|
||||
expiration: action.expiration
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
|
@ -1,7 +1,9 @@
|
|||
import React from 'react';
|
||||
import { call, cancel, delay, fork, put, race, select, take, takeLatest } from 'redux-saga/effects';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
|
||||
import moment from 'moment';
|
||||
import * as types from '../actions/actionsTypes';
|
||||
import { appStart } from '../actions/app';
|
||||
import { selectServerRequest, serverFinishAdd } from '../actions/server';
|
||||
|
@ -37,12 +39,40 @@ import {
|
|||
} from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { setUsersRoles } from '../actions/usersRoles';
|
||||
import { getServerById } from '../lib/database/services/Server';
|
||||
import appNavigation from '../lib/navigation/appNavigation';
|
||||
import { showActionSheetRef } from '../containers/ActionSheet';
|
||||
import { SupportedVersionsWarning } from '../containers/SupportedVersions';
|
||||
|
||||
const getServer = state => state.server.server;
|
||||
const loginWithPasswordCall = args => Services.loginWithPassword(args);
|
||||
const loginCall = (credentials, isFromWebView) => Services.login(credentials, isFromWebView);
|
||||
const logoutCall = args => logout(args);
|
||||
|
||||
const showSupportedVersionsWarning = function* showSupportedVersionsWarning(server) {
|
||||
const { status: supportedVersionsStatus } = yield select(state => state.supportedVersions);
|
||||
if (supportedVersionsStatus !== 'warn') {
|
||||
return;
|
||||
}
|
||||
const serverRecord = yield getServerById(server);
|
||||
const isMasterDetail = yield select(state => state.app.isMasterDetail);
|
||||
if (!serverRecord || moment(new Date()).diff(serverRecord?.supportedVersionsWarningAt, 'hours') <= 12) {
|
||||
return;
|
||||
}
|
||||
|
||||
const serversDB = database.servers;
|
||||
yield serversDB.write(async () => {
|
||||
await serverRecord.update(r => {
|
||||
r.supportedVersionsWarningAt = new Date();
|
||||
});
|
||||
});
|
||||
if (isMasterDetail) {
|
||||
appNavigation.navigate('ModalStackNavigator', { screen: 'SupportedVersionsWarning', params: { showCloseButton: true } });
|
||||
} else {
|
||||
showActionSheetRef({ children: <SupportedVersionsWarning /> });
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoginRequest = function* handleLoginRequest({
|
||||
credentials,
|
||||
logoutOnError = false,
|
||||
|
@ -209,6 +239,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
|
|||
if (inviteLinkToken) {
|
||||
yield put(inviteLinksRequest(inviteLinkToken));
|
||||
}
|
||||
yield showSupportedVersionsWarning(server);
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
|
|
|
@ -1,194 +0,0 @@
|
|||
import { put, takeLatest, select } from 'redux-saga/effects';
|
||||
import { Alert } from 'react-native';
|
||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||
import { Q } from '@nozbe/watermelondb';
|
||||
import valid from 'semver/functions/valid';
|
||||
import coerce from 'semver/functions/coerce';
|
||||
|
||||
import Navigation from '../lib/navigation/appNavigation';
|
||||
import { SERVER } from '../actions/actionsTypes';
|
||||
import { selectServerFailure, selectServerRequest, selectServerSuccess, serverFailure } from '../actions/server';
|
||||
import { clearSettings } from '../actions/settings';
|
||||
import { clearUser, setUser } from '../actions/login';
|
||||
import { clearActiveUsers } from '../actions/activeUsers';
|
||||
import database from '../lib/database';
|
||||
import log, { logServerVersion } from '../lib/methods/helpers/log';
|
||||
import I18n from '../i18n';
|
||||
import { BASIC_AUTH_KEY, setBasicAuth } from '../lib/methods/helpers/fetch';
|
||||
import { appStart } from '../actions/app';
|
||||
import UserPreferences from '../lib/methods/userPreferences';
|
||||
import { encryptionStop } from '../actions/encryption';
|
||||
import SSLPinning from '../lib/methods/helpers/sslPinning';
|
||||
import { inquiryReset } from '../ee/omnichannel/actions/inquiry';
|
||||
import { RootEnum } from '../definitions';
|
||||
import { CERTIFICATE_KEY, CURRENT_SERVER, TOKEN_KEY } from '../lib/constants';
|
||||
import { getLoginSettings, setCustomEmojis, setEnterpriseModules, setPermissions, setRoles, setSettings } from '../lib/methods';
|
||||
import { Services } from '../lib/services';
|
||||
import { connect } from '../lib/services/connect';
|
||||
|
||||
const getServerInfo = function* getServerInfo({ server, raiseError = true }) {
|
||||
try {
|
||||
const serverInfo = yield Services.getServerInfo(server);
|
||||
let websocketInfo = { success: true };
|
||||
if (raiseError) {
|
||||
websocketInfo = yield Services.getWebsocketInfo({ server });
|
||||
}
|
||||
if (!serverInfo.success || !websocketInfo.success) {
|
||||
if (raiseError) {
|
||||
const info = serverInfo.success ? websocketInfo : serverInfo;
|
||||
Alert.alert(I18n.t('Oops'), info.message);
|
||||
}
|
||||
yield put(serverFailure());
|
||||
return;
|
||||
}
|
||||
|
||||
let serverVersion = valid(serverInfo.version);
|
||||
if (!serverVersion) {
|
||||
({ version: serverVersion } = coerce(serverInfo.version));
|
||||
}
|
||||
|
||||
const serversDB = database.servers;
|
||||
const serversCollection = serversDB.get('servers');
|
||||
yield serversDB.action(async () => {
|
||||
try {
|
||||
const serverRecord = await serversCollection.find(server);
|
||||
await serverRecord.update(record => {
|
||||
record.version = serverVersion;
|
||||
});
|
||||
} catch (e) {
|
||||
await serversCollection.create(record => {
|
||||
record._raw = sanitizedRaw({ id: server }, serversCollection.schema);
|
||||
record.version = serverVersion;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return serverInfo;
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectServer = function* handleSelectServer({ server, version, fetchVersion }) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
SSLPinning.setCertificate(certificate, server);
|
||||
yield put(inquiryReset());
|
||||
yield put(encryptionStop());
|
||||
yield put(clearActiveUsers());
|
||||
const serversDB = database.servers;
|
||||
const userId = UserPreferences.getString(`${TOKEN_KEY}-${server}`);
|
||||
const userCollections = serversDB.get('users');
|
||||
let user = null;
|
||||
if (userId) {
|
||||
try {
|
||||
// search credentials on database
|
||||
const userRecord = yield userCollections.find(userId);
|
||||
user = {
|
||||
id: userRecord.id,
|
||||
token: userRecord.token,
|
||||
username: userRecord.username,
|
||||
name: userRecord.name,
|
||||
language: userRecord.language,
|
||||
status: userRecord.status,
|
||||
statusText: userRecord.statusText,
|
||||
roles: userRecord.roles,
|
||||
avatarETag: userRecord.avatarETag,
|
||||
bio: userRecord.bio,
|
||||
nickname: userRecord.nickname
|
||||
};
|
||||
} catch {
|
||||
// search credentials on shared credentials (Experimental/Official)
|
||||
const token = UserPreferences.getString(`${TOKEN_KEY}-${userId}`);
|
||||
if (token) {
|
||||
user = { token };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const basicAuth = UserPreferences.getString(`${BASIC_AUTH_KEY}-${server}`);
|
||||
setBasicAuth(basicAuth);
|
||||
|
||||
if (user) {
|
||||
yield put(clearSettings());
|
||||
yield put(setUser(user));
|
||||
yield connect({ server, logoutOnError: true });
|
||||
yield put(appStart({ root: RootEnum.ROOT_INSIDE }));
|
||||
UserPreferences.setString(CURRENT_SERVER, server); // only set server after have a user
|
||||
} else {
|
||||
yield put(clearUser());
|
||||
yield connect({ server });
|
||||
yield put(appStart({ root: RootEnum.ROOT_OUTSIDE }));
|
||||
}
|
||||
|
||||
// We can't use yield here because fetch of Settings & Custom Emojis is slower
|
||||
// and block the selectServerSuccess raising multiples errors
|
||||
setSettings();
|
||||
setCustomEmojis();
|
||||
setPermissions();
|
||||
setRoles();
|
||||
setEnterpriseModules();
|
||||
|
||||
let serverInfo;
|
||||
if (fetchVersion) {
|
||||
serverInfo = yield getServerInfo({ server, raiseError: false });
|
||||
}
|
||||
|
||||
// Return server version even when offline
|
||||
const serverVersion = (serverInfo && serverInfo.version) || version;
|
||||
|
||||
// we'll set serverVersion as metadata for bugsnag
|
||||
logServerVersion(serverVersion);
|
||||
yield put(selectServerSuccess(server, serverVersion));
|
||||
} catch (e) {
|
||||
yield put(selectServerFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleServerRequest = function* handleServerRequest({ server, username, fromServerHistory }) {
|
||||
try {
|
||||
// SSL Pinning - Read certificate alias and set it to be used by network requests
|
||||
const certificate = UserPreferences.getString(`${CERTIFICATE_KEY}-${server}`);
|
||||
SSLPinning.setCertificate(certificate, server);
|
||||
|
||||
const serverInfo = yield getServerInfo({ server });
|
||||
const serversDB = database.servers;
|
||||
const serversHistoryCollection = serversDB.get('servers_history');
|
||||
|
||||
if (serverInfo) {
|
||||
yield Services.getLoginServices(server);
|
||||
yield getLoginSettings({ server });
|
||||
Navigation.navigate('WorkspaceView');
|
||||
|
||||
const Accounts_iframe_enabled = yield select(state => state.settings.Accounts_iframe_enabled);
|
||||
if (fromServerHistory && !Accounts_iframe_enabled) {
|
||||
Navigation.navigate('LoginView', { username });
|
||||
}
|
||||
|
||||
yield serversDB.action(async () => {
|
||||
try {
|
||||
const serversHistory = await serversHistoryCollection.query(Q.where('url', server)).fetch();
|
||||
if (!serversHistory?.length) {
|
||||
await serversHistoryCollection.create(s => {
|
||||
s.url = server;
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log(e);
|
||||
}
|
||||
});
|
||||
yield put(selectServerRequest(server, serverInfo.version, false));
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(serverFailure());
|
||||
log(e);
|
||||
}
|
||||
};
|
||||
|
||||
const root = function* root() {
|
||||
yield takeLatest(SERVER.REQUEST, handleServerRequest);
|
||||
yield takeLatest(SERVER.SELECT_REQUEST, handleSelectServer);
|
||||
};
|
||||
export default root;
|
|
@ -0,0 +1,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';
|
||||
import { isIOS } from '../../lib/methods/helpers';
|
||||
import { TNavigation } from '../stackType';
|
||||
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||
|
||||
// ChatsStackNavigator
|
||||
const ChatsStack = createStackNavigator<MasterDetailChatsStackParamList>();
|
||||
|
@ -184,6 +185,7 @@ const ModalStackNavigator = React.memo(({ navigation }: INavigation) => {
|
|||
<ModalStack.Screen name='SecurityPrivacyView' component={SecurityPrivacyView} />
|
||||
<ModalStack.Screen name='MediaAutoDownloadView' component={MediaAutoDownloadView} />
|
||||
<ModalStack.Screen name='E2EEncryptionSecurityView' component={E2EEncryptionSecurityView} />
|
||||
<ModalStack.Screen name='SupportedVersionsWarning' component={SupportedVersionsWarning} />
|
||||
</ModalStack.Navigator>
|
||||
</ModalContainer>
|
||||
);
|
||||
|
|
|
@ -196,6 +196,9 @@ export type ModalStackParamList = {
|
|||
SecurityPrivacyView: undefined;
|
||||
MediaAutoDownloadView: undefined;
|
||||
E2EEncryptionSecurityView: undefined;
|
||||
SupportedVersionsWarning: {
|
||||
showCloseButton?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export type MasterDetailInsideStackParamList = {
|
||||
|
|
|
@ -50,11 +50,7 @@ const OutsideStackModal = () => {
|
|||
screenOptions={{ ...defaultHeader, ...themedHeader(theme), ...ModalAnimation, presentation: 'transparentModal' }}
|
||||
>
|
||||
<OutsideModal.Screen name='OutsideStack' component={OutsideStack} options={{ headerShown: false }} />
|
||||
<OutsideModal.Screen
|
||||
name='AuthenticationWebView'
|
||||
component={AuthenticationWebView}
|
||||
options={AuthenticationWebView.navigationOptions}
|
||||
/>
|
||||
<OutsideModal.Screen name='AuthenticationWebView' component={AuthenticationWebView} />
|
||||
</OutsideModal.Navigator>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|||
import { shallowEqual } from 'react-redux';
|
||||
import RNFetchBlob from 'rn-fetch-blob';
|
||||
|
||||
import { isImageBase64 } from '../lib/methods';
|
||||
import RCActivityIndicator from '../containers/ActivityIndicator';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import { ImageViewer } from '../containers/ImageViewer';
|
||||
|
@ -109,6 +110,7 @@ const AttachmentView = (): React.ReactElement => {
|
|||
|
||||
const setHeader = () => {
|
||||
let { title } = attachment;
|
||||
|
||||
try {
|
||||
if (title) {
|
||||
title = decodeURI(title);
|
||||
|
@ -128,7 +130,7 @@ const AttachmentView = (): React.ReactElement => {
|
|||
<HeaderButton.CloseModal testID='close-attachment-view' navigation={navigation} color={colors.previewTintColor} />
|
||||
),
|
||||
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} />
|
||||
) : null,
|
||||
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 { useNavigation, useRoute } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import React, { useLayoutEffect, useState } from 'react';
|
||||
import { WebView, WebViewNavigation } from 'react-native-webview';
|
||||
import { WebViewMessage } from 'react-native-webview/lib/WebViewTypes';
|
||||
import parse from 'url-parse';
|
||||
|
||||
import { OutsideModalParamList } from '../stacks/types';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import ActivityIndicator from '../containers/ActivityIndicator';
|
||||
import { TSupportedThemes, withTheme } from '../theme';
|
||||
import { userAgent } from '../lib/constants';
|
||||
import { debounce } from '../lib/methods/helpers';
|
||||
import * as HeaderButton from '../containers/HeaderButton';
|
||||
import StatusBar from '../containers/StatusBar';
|
||||
import { ICredentials } from '../definitions';
|
||||
import { userAgent } from '../lib/constants';
|
||||
import { useAppSelector } from '../lib/hooks';
|
||||
import { useDebounce } from '../lib/methods/helpers';
|
||||
import { Services } from '../lib/services';
|
||||
import { IApplicationState, ICredentials } from '../definitions';
|
||||
import { OutsideModalParamList } from '../stacks/types';
|
||||
|
||||
// iframe uses a postMessage to send the token to the client
|
||||
// We'll handle this sending the token to the hash of the window.location
|
||||
|
@ -40,95 +40,56 @@ window.addEventListener('popstate', function() {
|
|||
});
|
||||
`;
|
||||
|
||||
interface INavigationOption {
|
||||
navigation: StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
||||
route: RouteProp<OutsideModalParamList, 'AuthenticationWebView'>;
|
||||
}
|
||||
const AuthenticationWebView = () => {
|
||||
const [logging, setLogging] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
interface IAuthenticationWebView extends INavigationOption {
|
||||
server: string;
|
||||
Accounts_Iframe_api_url: string;
|
||||
Accounts_Iframe_api_method: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
const navigation = useNavigation<StackNavigationProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||
const {
|
||||
params: { authType, url, ssoToken }
|
||||
} = useRoute<RouteProp<OutsideModalParamList, 'AuthenticationWebView'>>();
|
||||
|
||||
interface IState {
|
||||
logging: boolean;
|
||||
loading: boolean;
|
||||
}
|
||||
const { Accounts_Iframe_api_method, Accounts_Iframe_api_url, server } = useAppSelector(state => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
||||
}));
|
||||
|
||||
class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView, IState> {
|
||||
private oauthRedirectRegex: RegExp;
|
||||
private iframeRedirectRegex: RegExp;
|
||||
const oauthRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||
const iframeRedirectRegex = new RegExp(`(?=.*(${server}))(?=.*(event|loginToken|token))`, 'g');
|
||||
|
||||
static navigationOptions = ({ route, navigation }: INavigationOption) => {
|
||||
const { authType } = route.params;
|
||||
return {
|
||||
headerLeft: () => <HeaderButton.CloseModal navigation={navigation} />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
};
|
||||
};
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
const debouncedLogin = useDebounce((params: ICredentials) => login(params), 3000);
|
||||
|
||||
constructor(props: IAuthenticationWebView) {
|
||||
super(props);
|
||||
this.state = {
|
||||
logging: false,
|
||||
loading: false
|
||||
};
|
||||
this.oauthRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(credentialToken))(?=.*(credentialSecret))`, 'g');
|
||||
this.iframeRedirectRegex = new RegExp(`(?=.*(${props.server}))(?=.*(event|loginToken|token))`, 'g');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.debouncedLogin && this.debouncedLogin.stop) {
|
||||
this.debouncedLogin.stop();
|
||||
}
|
||||
}
|
||||
|
||||
dismiss = () => {
|
||||
const { navigation } = this.props;
|
||||
navigation.pop();
|
||||
};
|
||||
|
||||
login = (params: ICredentials) => {
|
||||
const { logging } = this.state;
|
||||
const login = (params: ICredentials) => {
|
||||
if (logging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ logging: true });
|
||||
|
||||
setLogging(true);
|
||||
try {
|
||||
Services.loginOAuthOrSso(params);
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
this.setState({ logging: false });
|
||||
this.dismiss();
|
||||
setLogging(false);
|
||||
navigation.pop();
|
||||
};
|
||||
|
||||
// Force 3s delay so the server has time to evaluate the token
|
||||
debouncedLogin = debounce((params: ICredentials) => this.login(params), 3000);
|
||||
|
||||
tryLogin = debounce(
|
||||
const tryLogin = useDebounce(
|
||||
async () => {
|
||||
const { Accounts_Iframe_api_url, Accounts_Iframe_api_method } = this.props;
|
||||
const data = await fetch(Accounts_Iframe_api_url, { method: Accounts_Iframe_api_method }).then(response => response.json());
|
||||
const resume = data?.login || data?.loginToken;
|
||||
if (resume) {
|
||||
this.login({ resume });
|
||||
login({ resume });
|
||||
}
|
||||
},
|
||||
3000,
|
||||
true
|
||||
{ leading: true }
|
||||
);
|
||||
|
||||
onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||
const onNavigationStateChange = (webViewState: WebViewNavigation | WebViewMessage) => {
|
||||
const url = decodeURIComponent(webViewState.url);
|
||||
const { route } = this.props;
|
||||
const { authType } = route.params;
|
||||
if (authType === 'saml' || authType === 'cas') {
|
||||
const { ssoToken } = route.params;
|
||||
const parsedUrl = parse(url, true);
|
||||
// ticket -> cas / validate & saml_idp_credentialToken -> saml
|
||||
if (parsedUrl.pathname?.includes('validate') || parsedUrl.query?.ticket || parsedUrl.query?.saml_idp_credentialToken) {
|
||||
|
@ -140,28 +101,28 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
} else {
|
||||
payload = { cas: { credentialToken: ssoToken } };
|
||||
}
|
||||
this.debouncedLogin(payload);
|
||||
debouncedLogin(payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (authType === 'oauth') {
|
||||
if (this.oauthRedirectRegex.test(url)) {
|
||||
if (oauthRedirectRegex.test(url)) {
|
||||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
this.debouncedLogin({ oauth: { ...credentials } });
|
||||
debouncedLogin({ oauth: { ...credentials } });
|
||||
}
|
||||
}
|
||||
|
||||
if (authType === 'iframe') {
|
||||
if (this.iframeRedirectRegex.test(url)) {
|
||||
if (iframeRedirectRegex.test(url)) {
|
||||
const parts = url.split('#');
|
||||
const credentials = JSON.parse(parts[1]);
|
||||
switch (credentials.event) {
|
||||
case 'try-iframe-login':
|
||||
this.tryLogin();
|
||||
tryLogin();
|
||||
break;
|
||||
case 'login-with-token':
|
||||
this.debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
||||
debouncedLogin({ resume: credentials.token || credentials.loginToken });
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
|
@ -170,39 +131,31 @@ class AuthenticationWebView extends React.PureComponent<IAuthenticationWebView,
|
|||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { loading } = this.state;
|
||||
const { route } = this.props;
|
||||
const { url, authType } = route.params;
|
||||
const isIframe = authType === 'iframe';
|
||||
const isIframe = authType === 'iframe';
|
||||
|
||||
return (
|
||||
<>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
source={{ uri: url }}
|
||||
userAgent={userAgent}
|
||||
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
|
||||
onMessage={({ nativeEvent }) => this.onNavigationStateChange(nativeEvent)}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
injectedJavaScript={isIframe ? injectedJavaScript : undefined}
|
||||
onLoadStart={() => {
|
||||
this.setState({ loading: true });
|
||||
}}
|
||||
onLoadEnd={() => {
|
||||
this.setState({ loading: false });
|
||||
}}
|
||||
/>
|
||||
{loading ? <ActivityIndicator size='large' absolute /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
useLayoutEffect(() => {
|
||||
navigation.setOptions({
|
||||
headerLeft: () => <HeaderButton.CloseModal />,
|
||||
title: ['saml', 'cas', 'iframe'].includes(authType) ? 'SSO' : 'OAuth'
|
||||
});
|
||||
}, [authType, navigation]);
|
||||
|
||||
const mapStateToProps = (state: IApplicationState) => ({
|
||||
server: state.server.server,
|
||||
Accounts_Iframe_api_url: state.settings.Accounts_Iframe_api_url as string,
|
||||
Accounts_Iframe_api_method: state.settings.Accounts_Iframe_api_method as string
|
||||
});
|
||||
return (
|
||||
<>
|
||||
<StatusBar />
|
||||
<WebView
|
||||
source={{ uri: url }}
|
||||
userAgent={userAgent}
|
||||
// https://github.com/react-native-community/react-native-webview/issues/24#issuecomment-540130141
|
||||
onMessage={({ nativeEvent }) => onNavigationStateChange(nativeEvent)}
|
||||
onNavigationStateChange={onNavigationStateChange}
|
||||
injectedJavaScript={isIframe ? injectedJavaScript : undefined}
|
||||
onLoadStart={() => setLoading(true)}
|
||||
onLoadEnd={() => setLoading(false)}
|
||||
/>
|
||||
{loading ? <ActivityIndicator size='large' absolute /> : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(AuthenticationWebView));
|
||||
export default AuthenticationWebView;
|
||||
|
|
|
@ -86,6 +86,7 @@ const JitsiMeetView = (): React.ReactElement => {
|
|||
domStorageEnabled
|
||||
allowsInlineMediaPlayback
|
||||
mediaCapturePermissionGrantType={'grant'}
|
||||
mediaPlaybackRequiresUserAction={isIOS}
|
||||
/>
|
||||
</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 './RefreshControl';
|
||||
export * from './EmptyRoom';
|
||||
export * from './List';
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
export * from './useMessages';
|
||||
export * from './useRefresh';
|
||||
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 { View, Platform, StyleSheet } from 'react-native';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
|
||||
import ActivityIndicator from '../../../containers/ActivityIndicator';
|
||||
import { useMessages, useRefresh, useScroll } from './hooks';
|
||||
import { useDebounce } from '../../../lib/methods/helpers';
|
||||
import { RefreshControl, EmptyRoom, List } from './components';
|
||||
import { isAndroid, useDebounce } from '../../../lib/methods/helpers';
|
||||
import { EmptyRoom, List } from './components';
|
||||
import { IListContainerProps, IListContainerRef, IListProps } from './definitions';
|
||||
import { useMessages, useScroll } from './hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
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>(
|
||||
({ rid, tmid, renderRow, showMessageInMainThread, serverVersion, hideSystemMessages, listRef, loading }, ref) => {
|
||||
const [messages, messagesIds, fetchMessages] = useMessages({
|
||||
|
@ -26,7 +29,6 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
|||
serverVersion,
|
||||
hideSystemMessages
|
||||
});
|
||||
const [refreshing, refresh] = useRefresh({ rid, tmid, messagesLength: messages.length });
|
||||
const {
|
||||
jumpToBottom,
|
||||
jumpToMessage,
|
||||
|
@ -59,7 +61,7 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
|||
return (
|
||||
<>
|
||||
<EmptyRoom rid={rid} length={messages.length} />
|
||||
<RefreshControl refreshing={refreshing} onRefresh={refresh}>
|
||||
<Container>
|
||||
<List
|
||||
listRef={listRef}
|
||||
data={messages}
|
||||
|
@ -71,7 +73,7 @@ const ListContainer = forwardRef<IListContainerRef, IListContainerProps>(
|
|||
jumpToBottom={jumpToBottom}
|
||||
isThread={!!tmid}
|
||||
/>
|
||||
</RefreshControl>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import sharedStyles from '../../Styles';
|
|||
import { CustomIcon } from '../../../containers/CustomIcon';
|
||||
import { useTheme } from '../../../theme';
|
||||
import SearchHeader from '../../../containers/SearchHeader';
|
||||
import { useAppSelector } from '../../../lib/hooks';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
|
@ -54,13 +55,16 @@ const Header = React.memo(
|
|||
onSearchChangeText,
|
||||
onPress
|
||||
}: IRoomHeader) => {
|
||||
const { status: supportedVersionsStatus } = useAppSelector(state => state.supportedVersions);
|
||||
const { colors } = useTheme();
|
||||
|
||||
if (showSearchHeader) {
|
||||
return <SearchHeader onSearchChangeText={onSearchChangeText} testID='rooms-list-view-search-input' />;
|
||||
}
|
||||
let subtitle;
|
||||
if (connecting) {
|
||||
if (supportedVersionsStatus === 'expired') {
|
||||
subtitle = 'Cannot connect';
|
||||
} else if (connecting) {
|
||||
subtitle = I18n.t('Connecting');
|
||||
} else if (isFetching) {
|
||||
subtitle = I18n.t('Updating');
|
||||
|
|
|
@ -26,7 +26,7 @@ import { goRoom } from '../../lib/methods/helpers/goRoom';
|
|||
import SafeAreaView from '../../containers/SafeAreaView';
|
||||
import { withDimensions } from '../../dimensions';
|
||||
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
|
||||
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import { IApplicationState, ISubscription, IUser, TSVStatus, SubscriptionType, TSubscriptionModel } from '../../definitions';
|
||||
import styles from './styles';
|
||||
import ServerDropdown from './ServerDropdown';
|
||||
import ListHeader, { TEncryptionBanner } from './ListHeader';
|
||||
|
@ -44,9 +44,10 @@ import {
|
|||
isTablet,
|
||||
compareServerVersion
|
||||
} from '../../lib/methods/helpers';
|
||||
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants';
|
||||
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes, STATUS_COLORS, colors } from '../../lib/constants';
|
||||
import { Services } from '../../lib/services';
|
||||
import audioPlayer from '../../lib/methods/audioPlayer';
|
||||
import { SupportedVersionsExpired } from '../../containers/SupportedVersions';
|
||||
|
||||
type TNavigation = CompositeNavigationProp<
|
||||
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
|
||||
|
@ -74,6 +75,7 @@ interface IRoomsListViewProps {
|
|||
useRealName: boolean;
|
||||
isMasterDetail: boolean;
|
||||
notificationPresenceCap: boolean;
|
||||
supportedVersionsStatus: TSVStatus;
|
||||
subscribedRoom: string;
|
||||
width: number;
|
||||
insets: {
|
||||
|
@ -144,7 +146,8 @@ const shouldUpdateProps = [
|
|||
'createDirectMessagePermission',
|
||||
'createPublicChannelPermission',
|
||||
'createPrivateChannelPermission',
|
||||
'createDiscussionPermission'
|
||||
'createDiscussionPermission',
|
||||
'supportedVersionsStatus'
|
||||
];
|
||||
|
||||
const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread'];
|
||||
|
@ -333,7 +336,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
createDirectMessagePermission,
|
||||
createDiscussionPermission,
|
||||
showAvatar,
|
||||
displayMode
|
||||
displayMode,
|
||||
supportedVersionsStatus
|
||||
} = this.props;
|
||||
const { item } = this.state;
|
||||
|
||||
|
@ -356,7 +360,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
if (
|
||||
insets.left !== prevProps.insets.left ||
|
||||
insets.right !== prevProps.insets.right ||
|
||||
notificationPresenceCap !== prevProps.notificationPresenceCap
|
||||
notificationPresenceCap !== prevProps.notificationPresenceCap ||
|
||||
supportedVersionsStatus !== prevProps.supportedVersionsStatus
|
||||
) {
|
||||
this.setHeader();
|
||||
}
|
||||
|
@ -409,7 +414,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
|
||||
getHeader = (): StackNavigationOptions => {
|
||||
const { searching, canCreateRoom } = this.state;
|
||||
const { navigation, isMasterDetail, notificationPresenceCap } = this.props;
|
||||
const { navigation, isMasterDetail, notificationPresenceCap, supportedVersionsStatus, theme } = this.props;
|
||||
if (searching) {
|
||||
return {
|
||||
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 {
|
||||
headerTitleAlign: 'left',
|
||||
headerTitleContainerStyle: { flex: 1, marginHorizontal: 4, maxWidth: undefined },
|
||||
|
@ -439,17 +454,33 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
: // @ts-ignore
|
||||
() => navigation.toggleDrawer()
|
||||
}
|
||||
badge={() => (notificationPresenceCap ? <HeaderButton.BadgeWarn /> : null)}
|
||||
badge={() => getBadge()}
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
),
|
||||
headerTitle: () => <RoomsListHeaderView />,
|
||||
headerRight: () => (
|
||||
<HeaderButton.Container>
|
||||
{canCreateRoom ? (
|
||||
<HeaderButton.Item iconName='create' onPress={this.goToNewMessage} testID='rooms-list-view-create-channel' />
|
||||
<HeaderButton.Item
|
||||
iconName='create'
|
||||
onPress={this.goToNewMessage}
|
||||
testID='rooms-list-view-create-channel'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
) : null}
|
||||
<HeaderButton.Item iconName='search' onPress={this.initSearching} testID='rooms-list-view-search' />
|
||||
<HeaderButton.Item iconName='directory' onPress={this.goDirectory} testID='rooms-list-view-directory' />
|
||||
<HeaderButton.Item
|
||||
iconName='search'
|
||||
onPress={this.initSearching}
|
||||
testID='rooms-list-view-search'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
<HeaderButton.Item
|
||||
iconName='directory'
|
||||
onPress={this.goDirectory}
|
||||
testID='rooms-list-view-directory'
|
||||
disabled={supportedVersionsStatus === 'expired'}
|
||||
/>
|
||||
</HeaderButton.Container>
|
||||
)
|
||||
};
|
||||
|
@ -899,7 +930,7 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
|
||||
renderScroll = () => {
|
||||
const { loading, chats, search, searching } = this.state;
|
||||
const { theme, refreshing, displayMode } = this.props;
|
||||
const { theme, refreshing, displayMode, supportedVersionsStatus } = this.props;
|
||||
|
||||
const height = displayMode === DisplayMode.Condensed ? ROW_HEIGHT_CONDENSED : ROW_HEIGHT;
|
||||
|
||||
|
@ -907,6 +938,10 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
|
|||
return <ActivityIndicator />;
|
||||
}
|
||||
|
||||
if (supportedVersionsStatus === 'expired') {
|
||||
return <SupportedVersionsExpired />;
|
||||
}
|
||||
|
||||
return (
|
||||
<FlatList
|
||||
ref={this.getScrollRef}
|
||||
|
@ -952,6 +987,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
user: getUserSelector(state),
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||
supportedVersionsStatus: state.supportedVersions.status,
|
||||
server: state.server.server,
|
||||
changingServer: state.server.changingServer,
|
||||
searchText: state.rooms.searchText,
|
||||
|
|
|
@ -10,13 +10,14 @@ interface SidebarItemProps {
|
|||
left: JSX.Element;
|
||||
right?: JSX.Element;
|
||||
text: string;
|
||||
textColor?: string;
|
||||
current?: boolean;
|
||||
onPress(): void;
|
||||
testID: string;
|
||||
theme: TSupportedThemes;
|
||||
}
|
||||
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme }: SidebarItemProps) => (
|
||||
const Item = React.memo(({ left, right, text, onPress, testID, current, theme, textColor }: SidebarItemProps) => (
|
||||
<Touch
|
||||
key={testID}
|
||||
testID={testID}
|
||||
|
@ -25,7 +26,11 @@ const Item = React.memo(({ left, right, text, onPress, testID, current, theme }:
|
|||
>
|
||||
<View style={styles.itemHorizontal}>{left}</View>
|
||||
<View style={styles.itemCenter}>
|
||||
<Text style={[styles.itemText, { color: themes[theme].titleText }]} numberOfLines={1} accessibilityLabel={text}>
|
||||
<Text
|
||||
style={[styles.itemText, { color: textColor || themes[theme].titleText }]}
|
||||
numberOfLines={1}
|
||||
accessibilityLabel={text}
|
||||
>
|
||||
{text}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
@ -21,9 +21,11 @@ import Navigation from '../../lib/navigation/appNavigation';
|
|||
import SidebarItem from './SidebarItem';
|
||||
import styles from './styles';
|
||||
import { DrawerParamList } from '../../stacks/types';
|
||||
import { IApplicationState, IUser } from '../../definitions';
|
||||
import { IApplicationState, IUser, TSVStatus } from '../../definitions';
|
||||
import * as List from '../../containers/List';
|
||||
import { IActionSheetProvider, showActionSheetRef, withActionSheet } from '../../containers/ActionSheet';
|
||||
import { setNotificationPresenceCap } from '../../actions/app';
|
||||
import { SupportedVersionsWarning } from '../../containers/SupportedVersions';
|
||||
|
||||
interface ISidebarState {
|
||||
showStatus: boolean;
|
||||
|
@ -42,11 +44,13 @@ interface ISidebarProps {
|
|||
allowStatusMessage: boolean;
|
||||
notificationPresenceCap: boolean;
|
||||
Presence_broadcast_disabled: boolean;
|
||||
supportedVersionsStatus: TSVStatus;
|
||||
isMasterDetail: boolean;
|
||||
viewStatisticsPermission: string[];
|
||||
viewRoomAdministrationPermission: string[];
|
||||
viewUserAdministrationPermission: string[];
|
||||
viewPrivilegedSettingPermission: string[];
|
||||
showActionSheet: IActionSheetProvider['showActionSheet'];
|
||||
}
|
||||
|
||||
class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
||||
|
@ -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 = () => {
|
||||
const { theme, isMasterDetail } = this.props;
|
||||
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() {
|
||||
const { user, Site_Name, baseUrl, useRealName, allowStatusMessage, isMasterDetail, theme } = this.props;
|
||||
|
||||
|
@ -323,6 +353,9 @@ class Sidebar extends Component<ISidebarProps, ISidebarState> {
|
|||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
|
||||
<List.Separator />
|
||||
{this.renderSupportedVersionsWarn()}
|
||||
|
||||
<List.Separator />
|
||||
|
||||
{allowStatusMessage ? this.renderCustomStatus() : null}
|
||||
|
@ -350,6 +383,7 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
allowStatusMessage: state.settings.Accounts_AllowUserStatusMessageChange as boolean,
|
||||
Presence_broadcast_disabled: state.settings.Presence_broadcast_disabled as boolean,
|
||||
notificationPresenceCap: state.app.notificationPresenceCap,
|
||||
supportedVersionsStatus: state.supportedVersions.status,
|
||||
isMasterDetail: state.app.isMasterDetail,
|
||||
viewStatisticsPermission: state.permissions['view-statistics'] as string[],
|
||||
viewRoomAdministrationPermission: state.permissions['view-room-administration'] as string[],
|
||||
|
@ -357,4 +391,4 @@ const mapStateToProps = (state: IApplicationState) => ({
|
|||
viewPrivilegedSettingPermission: state.permissions['view-privileged-setting'] as string[]
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(withTheme(Sidebar));
|
||||
export default connect(mapStateToProps)(withActionSheet(withTheme(Sidebar)));
|
||||
|
|
|
@ -478,7 +478,7 @@ describe('Room screen', () => {
|
|||
.toExist()
|
||||
.withTimeout(2000);
|
||||
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')).swipe('up', 'fast', 0.5);
|
||||
await sleep(300); // wait for animation
|
||||
|
|
|
@ -237,8 +237,9 @@ describe('Threads', () => {
|
|||
.withTimeout(5000);
|
||||
await element(by.id(`message-thread-button-${thread}`)).tap();
|
||||
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 sleep(1000); // wait for animation
|
||||
await element(by.id('action-sheet')).swipe('up', 'fast', 0.5);
|
||||
await sleep(300); // wait for animation
|
||||
await element(by[textMatcher]('Delete')).atIndex(0).tap();
|
||||
|
|
|
@ -137,9 +137,10 @@ describe('Auto Translate', () => {
|
|||
|
||||
// verify default language is checked
|
||||
await waitFor(element(by.id(`auto-translate-view-${languages.default}`)))
|
||||
.toBeVisible()
|
||||
.toExist()
|
||||
.whileElement(by.id('auto-translate-view'))
|
||||
.scroll(750, 'down');
|
||||
await element(by.id('auto-translate-view')).swipe('up', 'slow', 0.5);
|
||||
await waitForVisible(`auto-translate-view-${languages.default}-check`);
|
||||
|
||||
// enable translated language
|
||||
|
@ -219,6 +220,7 @@ describe('Auto Translate', () => {
|
|||
});
|
||||
|
||||
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 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 mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock';
|
||||
|
||||
jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
|
||||
|
||||
jest.mock('react-native-safe-area-context', () => {
|
||||
const inset = { top: 0, right: 0, bottom: 0, left: 0 };
|
||||
return {
|
||||
...jest.requireActual('react-native-safe-area-context'),
|
||||
SafeAreaProvider: jest.fn(({ children }) => children),
|
||||
SafeAreaConsumer: jest.fn(({ children }) => children(inset)),
|
||||
useSafeAreaInsets: jest.fn(() => inset),
|
||||
useSafeAreaFrame: jest.fn(() => ({ x: 0, y: 0, width: 390, height: 844 }))
|
||||
};
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
global.__reanimatedWorkletInit = () => {};
|
||||
jest.mock('react-native-reanimated', () => require('react-native-reanimated/mock'));
|
||||
|
||||
|
@ -28,8 +42,6 @@ jest.mock('expo-haptics', () => jest.fn(() => null));
|
|||
|
||||
jest.mock('./app/lib/database', () => jest.fn(() => null));
|
||||
|
||||
const mockedNavigate = jest.fn();
|
||||
|
||||
jest.mock('@react-navigation/native', () => ({
|
||||
...jest.requireActual('@react-navigation/native'),
|
||||
useNavigation: () => ({
|
|
@ -84,6 +84,7 @@
|
|||
"i18n-js": "3.9.2",
|
||||
"js-base64": "3.6.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jsrsasign": "^10.8.6",
|
||||
"lint-staged": "^11.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.29.4",
|
||||
|
@ -172,13 +173,14 @@
|
|||
"@storybook/addon-storyshots": "6.3",
|
||||
"@storybook/react": "6.3",
|
||||
"@storybook/react-native": "^6.0.1-beta.7",
|
||||
"@testing-library/jest-native": "^4.0.4",
|
||||
"@testing-library/jest-native": "^5.4.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@testing-library/react-native": "^9.0.0",
|
||||
"@testing-library/react-native": "^12.1.2",
|
||||
"@types/bytebuffer": "^5.0.44",
|
||||
"@types/ejson": "^2.1.3",
|
||||
"@types/i18n-js": "^3.8.3",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/jsrsasign": "^10.5.8",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-native": "0.68.1",
|
||||
|
@ -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"
|
||||
},
|
||||
"setupFilesAfterEnv": [
|
||||
"@testing-library/jest-native/extend-expect",
|
||||
"./jest.setup.js"
|
||||
"./jest.setup.ts"
|
||||
]
|
||||
},
|
||||
"jest-junit": {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
#!/bin/bash
|
||||
|
||||
# URL to fetch data from
|
||||
URL="https://releases.rocket.chat/v2/server/supportedVersions?source=mobile"
|
||||
|
||||
# Output file name
|
||||
OUTPUT_FILE="app-supportedversions.json"
|
||||
|
||||
# Use curl to fetch data and save it to a temporary file
|
||||
TEMP_FILE=$(mktemp)
|
||||
curl -s "$URL" > "$TEMP_FILE"
|
||||
|
||||
# Check if the curl command was successful (HTTP status code 200)
|
||||
if [ $? -eq 0 ]; then
|
||||
# Use jq to pretty-print the JSON and save it to the output file
|
||||
jq '.' "$TEMP_FILE" > "$OUTPUT_FILE"
|
||||
echo "Data fetched and saved to $OUTPUT_FILE"
|
||||
else
|
||||
echo "Failed to fetch data from $URL"
|
||||
fi
|
||||
|
||||
# Clean up the temporary file
|
||||
rm "$TEMP_FILE"
|
190
yarn.lock
190
yarn.lock
|
@ -4564,6 +4564,13 @@
|
|||
dependencies:
|
||||
"@sinclair/typebox" "^0.24.1"
|
||||
|
||||
"@jest/schemas@^29.6.3":
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
|
||||
integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==
|
||||
dependencies:
|
||||
"@sinclair/typebox" "^0.27.8"
|
||||
|
||||
"@jest/source-map@^28.1.2":
|
||||
version "28.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24"
|
||||
|
@ -4645,15 +4652,6 @@
|
|||
slash "^3.0.0"
|
||||
write-file-atomic "^4.0.1"
|
||||
|
||||
"@jest/types@^24.9.0":
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.9.0.tgz#63cb26cb7500d069e5a389441a7c6ab5e909fc59"
|
||||
integrity sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==
|
||||
dependencies:
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^1.1.1"
|
||||
"@types/yargs" "^13.0.0"
|
||||
|
||||
"@jest/types@^25.5.0":
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
|
||||
|
@ -5677,6 +5675,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794"
|
||||
integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow==
|
||||
|
||||
"@sinclair/typebox@^0.27.8":
|
||||
version "0.27.8"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
|
||||
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
|
||||
|
||||
"@sinonjs/commons@^1.7.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.0.tgz#c8d68821a854c555bba172f3b06959a0039b236d"
|
||||
|
@ -6282,17 +6285,16 @@
|
|||
resolve-from "^5.0.0"
|
||||
store2 "^2.12.0"
|
||||
|
||||
"@testing-library/jest-native@^4.0.4":
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-4.0.4.tgz#25e2046896118f887683202a6e5fd8a4056131cd"
|
||||
integrity sha512-4q5FeTFyFgPCmQH18uMJsZkVnYvBtK24yhSfbd9hQi0SZzCpbjSeQQcsGXIaX+WjWcMeeip8B7NUvZmLhGHiZw==
|
||||
"@testing-library/jest-native@^5.4.2":
|
||||
version "5.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.3.tgz#9334c68eaf45db9eb20d0876728cc5d7fc2c3ea2"
|
||||
integrity sha512-/sSDGaOuE+PJ1Z9Kp4u7PQScSVVXGud59I/qsBFFJvIbcn4P6yYw6cBnBmbPF+X9aRIsTJRDl6gzw5ZkJNm66w==
|
||||
dependencies:
|
||||
chalk "^2.4.1"
|
||||
jest-diff "^24.0.0"
|
||||
jest-matcher-utils "^24.0.0"
|
||||
pretty-format "^27.3.1"
|
||||
ramda "^0.26.1"
|
||||
redent "^2.0.0"
|
||||
chalk "^4.1.2"
|
||||
jest-diff "^29.0.1"
|
||||
jest-matcher-utils "^29.0.1"
|
||||
pretty-format "^29.0.3"
|
||||
redent "^3.0.0"
|
||||
|
||||
"@testing-library/react-hooks@^8.0.1":
|
||||
version "8.0.1"
|
||||
|
@ -6302,12 +6304,12 @@
|
|||
"@babel/runtime" "^7.12.5"
|
||||
react-error-boundary "^3.1.0"
|
||||
|
||||
"@testing-library/react-native@^9.0.0":
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-9.0.0.tgz#e9c63411e93d2e8e70d744b12aeb78c58025c5fc"
|
||||
integrity sha512-UE3FWOsDUr+2l3Pg6JTpn2rV5uzYsxIus6ZyN1uMOTmn30bIuBBDDlWQtdWGJx92YcY4xgJA4vViCEKv7wVzJA==
|
||||
"@testing-library/react-native@^12.1.2":
|
||||
version "12.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-12.2.2.tgz#4b2275d5d1feb689c9b1e5cd9cb03ffe32a43228"
|
||||
integrity sha512-aLr7YQ6pyn8PbLmdbtADG2aKcmarTLI7VhgWNVzJLxQHOtsDxLpJGKMSw10j406BE/GyGHbB0Gln3Of8/2TjnA==
|
||||
dependencies:
|
||||
pretty-format "^27.0.0"
|
||||
pretty-format "^29.0.0"
|
||||
|
||||
"@tootallnate/once@1":
|
||||
version "1.1.2"
|
||||
|
@ -6576,6 +6578,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/jsrsasign@^10.5.8":
|
||||
version "10.5.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.8.tgz#0d6c638505454b5e95c684d6f604d57641417336"
|
||||
integrity sha512-1oZ3TbarAhKtKUpyrCIqXpbx3ZAfoSulleJs6/UzzyYty0ut+kjRX7zHLAaHwVIuw8CBjIymwW4J2LK944HoHQ==
|
||||
|
||||
"@types/lodash@^4.14.175":
|
||||
version "4.14.182"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2"
|
||||
|
@ -6880,13 +6887,6 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
|
||||
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
|
||||
|
||||
"@types/yargs@^13.0.0":
|
||||
version "13.0.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.9.tgz#44028e974343c7afcf3960f1a2b1099c39a7b5e1"
|
||||
integrity sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^15.0.0":
|
||||
version "15.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
|
||||
|
@ -7430,7 +7430,7 @@ ansi-regex@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
|
||||
|
||||
ansi-regex@^4.0.0, ansi-regex@^4.1.0:
|
||||
ansi-regex@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
|
||||
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
|
||||
|
@ -10165,11 +10165,6 @@ detox@^20.1.2:
|
|||
yargs-parser "^20.2.9"
|
||||
yargs-unparser "^2.0.0"
|
||||
|
||||
diff-sequences@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.9.0.tgz#5715d6244e2aa65f48bba0bc972db0b0b11e95b5"
|
||||
integrity sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==
|
||||
|
||||
diff-sequences@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
|
||||
|
@ -10185,6 +10180,11 @@ diff-sequences@^28.1.1:
|
|||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6"
|
||||
integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==
|
||||
|
||||
diff-sequences@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
|
||||
|
||||
diff@^4.0.1:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
|
@ -12908,11 +12908,6 @@ imurmurhash@^0.1.4:
|
|||
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
|
||||
integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
|
||||
|
||||
indent-string@^3.0.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289"
|
||||
integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=
|
||||
|
||||
indent-string@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
|
||||
|
@ -13700,16 +13695,6 @@ jest-config@^28.1.3:
|
|||
slash "^3.0.0"
|
||||
strip-json-comments "^3.1.1"
|
||||
|
||||
jest-diff@^24.0.0, jest-diff@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da"
|
||||
integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
diff-sequences "^24.9.0"
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-diff@^25.2.1:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.5.0.tgz#1dd26ed64f96667c068cef026b677dfa01afcfa9"
|
||||
|
@ -13740,6 +13725,16 @@ jest-diff@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-diff@^29.0.1, jest-diff@^29.6.4:
|
||||
version "29.6.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.6.4.tgz#85aaa6c92a79ae8cd9a54ebae8d5b6d9a513314a"
|
||||
integrity sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
diff-sequences "^29.6.3"
|
||||
jest-get-type "^29.6.3"
|
||||
pretty-format "^29.6.3"
|
||||
|
||||
jest-docblock@^28.1.1:
|
||||
version "28.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8"
|
||||
|
@ -13785,11 +13780,6 @@ jest-expo@^46.0.1:
|
|||
lodash "^4.17.19"
|
||||
react-test-renderer "~18.0.0"
|
||||
|
||||
jest-get-type@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.9.0.tgz#1684a0c8a50f2e4901b6644ae861f579eed2ef0e"
|
||||
integrity sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==
|
||||
|
||||
jest-get-type@^25.2.6:
|
||||
version "25.2.6"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
|
||||
|
@ -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"
|
||||
integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==
|
||||
|
||||
jest-get-type@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.6.3.tgz#36f499fdcea197c1045a127319c0481723908fd1"
|
||||
integrity sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==
|
||||
|
||||
jest-haste-map@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa"
|
||||
|
@ -13883,16 +13878,6 @@ jest-leak-detector@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-matcher-utils@^24.0.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
|
||||
integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==
|
||||
dependencies:
|
||||
chalk "^2.0.1"
|
||||
jest-diff "^24.9.0"
|
||||
jest-get-type "^24.9.0"
|
||||
pretty-format "^24.9.0"
|
||||
|
||||
jest-matcher-utils@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a"
|
||||
|
@ -13913,6 +13898,16 @@ jest-matcher-utils@^28.1.3:
|
|||
jest-get-type "^28.0.2"
|
||||
pretty-format "^28.1.3"
|
||||
|
||||
jest-matcher-utils@^29.0.1:
|
||||
version "29.6.4"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.6.4.tgz#327db7ababea49455df3b23e5d6109fe0c709d24"
|
||||
integrity sha512-KSzwyzGvK4HcfnserYqJHYi7sZVqdREJ9DMPAKVbS98JsIAvumihaNUbjrWw0St7p9IY7A9UskCW5MYlGmBQFQ==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
jest-diff "^29.6.4"
|
||||
jest-get-type "^29.6.3"
|
||||
pretty-format "^29.6.3"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
|
@ -14539,6 +14534,11 @@ jsonfile@^6.0.1:
|
|||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jsrsasign@^10.8.6:
|
||||
version "10.8.6"
|
||||
resolved "https://registry.yarnpkg.com/jsrsasign/-/jsrsasign-10.8.6.tgz#ebf7f3c812c6517af84f0d8a10115e0dbfabe145"
|
||||
integrity sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==
|
||||
|
||||
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.0.tgz#e624f259143b9062c92b6413ff92a164c80d3ccb"
|
||||
|
@ -17051,16 +17051,6 @@ pretty-error@^2.1.1:
|
|||
renderkid "^2.0.1"
|
||||
utila "~0.4"
|
||||
|
||||
pretty-format@^24.9.0:
|
||||
version "24.9.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9"
|
||||
integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==
|
||||
dependencies:
|
||||
"@jest/types" "^24.9.0"
|
||||
ansi-regex "^4.0.0"
|
||||
ansi-styles "^3.2.0"
|
||||
react-is "^16.8.4"
|
||||
|
||||
pretty-format@^25.2.1, pretty-format@^25.5.0:
|
||||
version "25.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
|
||||
|
@ -17081,16 +17071,7 @@ pretty-format@^26.0.0, pretty-format@^26.5.2, pretty-format@^26.6.2:
|
|||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.0.0:
|
||||
version "27.4.6"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7"
|
||||
integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^27.3.1, pretty-format@^27.5.1:
|
||||
pretty-format@^27.5.1:
|
||||
version "27.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
|
||||
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
|
||||
|
@ -17109,6 +17090,15 @@ pretty-format@^28.1.3:
|
|||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
pretty-format@^29.0.0, pretty-format@^29.0.3, pretty-format@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.6.3.tgz#d432bb4f1ca6f9463410c3fb25a0ba88e594ace7"
|
||||
integrity sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.6.3"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
pretty-format@^3.8.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-3.8.0.tgz#bfbed56d5e9a776645f4b1ff7aa1a3ac4fa3c385"
|
||||
|
@ -17430,11 +17420,6 @@ ramda@^0.21.0:
|
|||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35"
|
||||
integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU=
|
||||
|
||||
ramda@^0.26.1:
|
||||
version "0.26.1"
|
||||
resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06"
|
||||
integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==
|
||||
|
||||
random-bytes@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b"
|
||||
|
@ -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"
|
||||
integrity sha512-1lYWbEqr0GW7HHUjMScXMidGvV0BE2RJV3ap2BL7G0EJirkqpccTaawbsvBO8GZaB3JjCeFBEbnEWI1P8ZoLRQ==
|
||||
|
||||
react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
|
||||
react-is@^16.12.0, react-is@^16.13.0, react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||
|
@ -17704,7 +17689,7 @@ react-native-easy-grid@^0.2.2:
|
|||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
react-native-easy-toast@^1.2.0:
|
||||
react-native-easy-toast@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"
|
||||
integrity sha512-UtpxnRn1ME+035Uey4VR+9K0P4aVoTcWNOx5QkioWBe3LBKMPb/kZjrQ1LtvWzOyeGP4TeTUTtMX3IOPWv5MtA==
|
||||
|
@ -18269,13 +18254,13 @@ recursive-readdir@2.2.2:
|
|||
dependencies:
|
||||
minimatch "3.0.4"
|
||||
|
||||
redent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-2.0.0.tgz#c1b2007b42d57eb1389079b3c8333639d5e1ccaa"
|
||||
integrity sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=
|
||||
redent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
|
||||
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
|
||||
dependencies:
|
||||
indent-string "^3.0.0"
|
||||
strip-indent "^2.0.0"
|
||||
indent-string "^4.0.0"
|
||||
strip-indent "^3.0.0"
|
||||
|
||||
reduce-flatten@^2.0.0:
|
||||
version "2.0.0"
|
||||
|
@ -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"
|
||||
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
|
||||
|
||||
strip-indent@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
||||
integrity sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=
|
||||
|
||||
strip-indent@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
|
||||
|
|
Loading…
Reference in New Issue