feat: Supported Versions (#5185)

This commit is contained in:
Diego Mello 2023-10-19 10:38:57 -03:00 committed by GitHub
parent 4a6a4a3b9a
commit 18b77ab4d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 2433 additions and 434 deletions

View File

@ -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: |

View File

@ -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

View File

@ -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

104
app-supportedversions.json Normal file
View File

@ -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"
}

View File

@ -96,3 +96,4 @@ export const VIDEO_CONF = createRequestTypes('VIDEO_CONF', [
'ACCEPT_CALL',
'SET_CALLING'
]);
export const SUPPORTED_VERSIONS = createRequestTypes('SUPPORTED_VERSIONS', ['SET']);

View File

@ -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
};
}

View File

@ -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
};
}

View File

@ -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();
};

View File

@ -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();
});

View File

@ -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>
);

View File

@ -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} />

View File

@ -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 }]} />
);

View File

@ -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>
)}

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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();
});
});

View File

@ -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>
);
};

View File

@ -0,0 +1 @@
export const LEARN_MORE_URL = 'https://go.rocket.chat/i/supported-versions';

View File

@ -0,0 +1,2 @@
export * from './SupportedVersionsWarning';
export * from './SupportedVersionsExpired';

View File

@ -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
}
});

View File

@ -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
};
};

View File

@ -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;

View File

@ -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;

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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';

View File

@ -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';

View File

@ -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-----`;

View File

@ -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;
}

View File

@ -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
}
]
})
]
}
]
});

View File

@ -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({

View File

@ -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;
}
};

View File

@ -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;
}
};

View File

@ -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'
});
});
});

View File

@ -0,0 +1,481 @@
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('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'
});
});
});

View File

@ -0,0 +1,90 @@
import moment from 'moment';
import coerce from 'semver/functions/coerce';
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;
} {
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 }) => coerce(version)?.version === serverVersion);
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 }) => coerce(version)?.version === serverVersion);
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
};
};

View File

@ -0,0 +1,104 @@
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();
// 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') })
};
}
export const getCloudInfo = async (): Promise<TCloudInfo | null> => {
const uniqueId = store.getState().settings.uniqueID as string;
const domain = store.getState().server.server;
const response = await getSupportedVersionsCloud(uniqueId, domain);
return response.json() as unknown as TCloudInfo;
};

View File

@ -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}` };

View File

@ -38,4 +38,6 @@ export * from './parseSettings';
export * from './subscribeRooms';
export * from './serializeAsciiUrl';
export * from './isRoomFederated';
export * from './checkSupportedVersions';
export * from './getServerInfo';
export * from './isImageBase64';

View File

@ -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,

View File

@ -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`);

View File

@ -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
});

View File

@ -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);
});

View File

@ -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,

View File

@ -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 });
});
});

View File

@ -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;
}
};

View File

@ -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,41 @@ 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(serverRecord?.supportedVersionsWarningAt).diff(new Date(), '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 +240,7 @@ const handleLoginSuccess = function* handleLoginSuccess({ user }) {
if (inviteLinkToken) {
yield put(inviteLinksRequest(inviteLinkToken));
}
yield showSupportedVersionsWarning(server);
} catch (e) {
log(e);
}

View File

@ -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;

261
app/sagas/selectServer.ts Normal file
View File

@ -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;

View File

@ -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>
);

View File

@ -196,6 +196,9 @@ export type ModalStackParamList = {
SecurityPrivacyView: undefined;
MediaAutoDownloadView: undefined;
E2EEncryptionSecurityView: undefined;
SupportedVersionsWarning: {
showCloseButton?: boolean;
};
};
export type MasterDetailInsideStackParamList = {

View File

@ -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');

View File

@ -26,7 +26,7 @@ import { goRoom } from '../../lib/methods/helpers/goRoom';
import SafeAreaView from '../../containers/SafeAreaView';
import { withDimensions } from '../../dimensions';
import { getInquiryQueueSelector } from '../../ee/omnichannel/selectors/inquiry';
import { IApplicationState, ISubscription, IUser, SubscriptionType, TSubscriptionModel } from '../../definitions';
import { IApplicationState, ISubscription, IUser, TSVStatus, SubscriptionType, TSubscriptionModel } from '../../definitions';
import styles from './styles';
import ServerDropdown from './ServerDropdown';
import ListHeader, { TEncryptionBanner } from './ListHeader';
@ -44,8 +44,9 @@ import {
isTablet,
compareServerVersion
} from '../../lib/methods/helpers';
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes } from '../../lib/constants';
import { E2E_BANNER_TYPE, DisplayMode, SortBy, MAX_SIDEBAR_WIDTH, themes, STATUS_COLORS, colors } from '../../lib/constants';
import { Services } from '../../lib/services';
import { SupportedVersionsExpired } from '../../containers/SupportedVersions';
type TNavigation = CompositeNavigationProp<
StackNavigationProp<ChatsStackParamList, 'RoomsListView'>,
@ -73,6 +74,7 @@ interface IRoomsListViewProps {
useRealName: boolean;
isMasterDetail: boolean;
notificationPresenceCap: boolean;
supportedVersionsStatus: TSVStatus;
subscribedRoom: string;
width: number;
insets: {
@ -143,7 +145,8 @@ const shouldUpdateProps = [
'createDirectMessagePermission',
'createPublicChannelPermission',
'createPrivateChannelPermission',
'createDiscussionPermission'
'createDiscussionPermission',
'supportedVersionsStatus'
];
const sortPreferencesShouldUpdate = ['sortBy', 'groupByType', 'showFavorites', 'showUnread'];
@ -330,7 +333,8 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
createDirectMessagePermission,
createDiscussionPermission,
showAvatar,
displayMode
displayMode,
supportedVersionsStatus
} = this.props;
const { item } = this.state;
@ -353,7 +357,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();
}
@ -406,7 +411,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',
@ -422,6 +427,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 },
@ -436,17 +451,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>
)
};
@ -896,7 +927,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;
@ -904,6 +935,10 @@ class RoomsListView extends React.Component<IRoomsListViewProps, IRoomsListViewS
return <ActivityIndicator />;
}
if (supportedVersionsStatus === 'expired') {
return <SupportedVersionsExpired />;
}
return (
<FlatList
ref={this.getScrollRef}
@ -949,6 +984,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,

View File

@ -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>

View File

@ -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)));

View File

@ -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'));

View File

@ -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": {

View File

@ -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"

188
yarn.lock
View File

@ -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==
@ -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"