From 56ae1a673f313103ea9c52623ffd52ab86388199 Mon Sep 17 00:00:00 2001 From: Ewe Tek Min Date: Wed, 19 Jun 2019 04:04:11 +0800 Subject: [PATCH 01/28] Update Github issues link (#2889) * Update Github issues link * Update GH issue to permalink --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 244fadf6e..6afea1902 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ To help with testing app updates before they're released, you can: ### Contribute Code -1. Look in [GitHub issues](https://github.com/mattermost/mattermost-server/issues?q=label%3A"React+Native") for issues marked as [Help Wanted] +1. Look in [GitHub issues](https://mattermost.com/pl/help-wanted-mattermost-mobile) for issues marked as [Help Wanted] 2. Comment to let people know you’re working on it 3. Follow [these instructions](https://developers.mattermost.com/contribute/mobile/developer-setup/) to set up your developer environment 4. Join the [Native Mobile Apps channel](https://pre-release.mattermost.com/core/channels/native-mobile-apps) on our team site to ask questions From 1baf3ee5ba45a6ce5a394145b3e11e1d0b3a2fb3 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 18 Jun 2019 16:07:58 -0400 Subject: [PATCH 02/28] Update gradle to use android-jsc as instructed (#2893) --- android/app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 602bbcaa6..2f54f0454 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,8 +114,8 @@ android { } packagingOptions { - pickFirst 'lib/x86_64/libjsc.so' - pickFirst 'lib/arm64-v8a/libjsc.so' + pickFirst '**/libjsc.so' + pickFirst '**/libc++_shared.so' } defaultConfig { @@ -193,9 +193,6 @@ repositories { configurations.all { resolutionStrategy { eachDependency { DependencyResolveDetails details -> - if (details.requested.name == 'android-jsc') { - details.useTarget group: details.requested.group, name: 'android-jsc-intl', version: 'r241213' - } if (details.requested.name == 'play-services-base') { details.useTarget group: details.requested.group, name: details.requested.name, version: '15.0.1' } @@ -213,6 +210,9 @@ configurations.all { } dependencies { + // Make sure to put android-jsc at the top + implementation "org.webkit:android-jsc:r241213" + implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" implementation 'com.android.support:design:28.0.0' From 5d83eef111cccfe4da82fdd90199c4c661728445 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 18 Jun 2019 16:09:09 -0400 Subject: [PATCH 03/28] Bump app version number to 1.21.0 (#2894) --- android/app/build.gradle | 2 +- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- package.json | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2f54f0454..266a3f48e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -123,7 +123,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 198 - versionName "1.20.0" + versionName "1.21.0" multiDexEnabled = true ndk { abiFilters 'armeabi-v7a','arm64-v8a','x86','x86_64' diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index bc5a783a7..bc39078ab 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.20.0 + 1.21.0 CFBundleSignature ???? CFBundleURLTypes diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 2d5ea8b45..46203cee1 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.20.0 + 1.21.0 CFBundleVersion 198 NSAppTransportSecurity diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index c948006ef..2f34ab2ab 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.20.0 + 1.21.0 CFBundleSignature ???? CFBundleVersion diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 55924d842..9e0c92b39 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.20.0 + 1.21.0 CFBundleVersion 198 NSExtension diff --git a/package.json b/package.json index 637d1f74b..4a839bb1a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mattermost-mobile", - "version": "1.20.0", + "version": "1.21.0", "description": "Mattermost Mobile with React Native", "repository": "git@github.com:mattermost/mattermost-mobile.git", "author": "Mattermost, Inc.", @@ -146,4 +146,4 @@ "node_modules/(?!react-native|jail-monkey)" ] } -} +} \ No newline at end of file From 7f30ebbd3960ae52a25497bd4db1cda57a86b7a1 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Tue, 18 Jun 2019 14:09:52 -0700 Subject: [PATCH 04/28] Return if message is null (#2895) --- .../rnbeta/NotificationReplyBroadcastReceiver.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java index 2d69a3b2d..f5610431f 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java @@ -36,13 +36,17 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + final CharSequence message = getReplyMessage(intent); + if (message == null) { + return; + } + mContext = context; bundle = NotificationIntentAdapter.extractPendingNotificationDataFromIntent(intent); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context); final int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1); - final CharSequence message = getReplyMessage(intent); final KeychainModule keychainModule = new KeychainModule(reactApplicationContext); keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() { From 5f9532d6c3ecf902f488d2fdcbf05bbbfcbf4ab5 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 18 Jun 2019 17:42:48 -0400 Subject: [PATCH 05/28] Bump app build number to 199 (#2896) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 266a3f48e..41e4ffcf7 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -122,7 +122,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 198 + versionCode 199 versionName "1.21.0" multiDexEnabled = true ndk { diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index b27904acc..fa6ad1918 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2762,7 +2762,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 198; + CURRENT_PROJECT_VERSION = 199; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2822,7 +2822,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 198; + CURRENT_PROJECT_VERSION = 199; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index bc39078ab..fa58027fa 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 198 + 199 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 46203cee1..5f3933390 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 198 + 199 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 2f34ab2ab..c26979a00 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 198 + 199 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 9e0c92b39..0fcc481c8 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 198 + 199 NSExtension NSExtensionPointIdentifier From 8cc94f52f0432937eeb3040282daace7a89035c2 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 19 Jun 2019 16:14:50 -0400 Subject: [PATCH 06/28] Fix crash on Android by Updating JSC (#2900) --- package-lock.json | 215 +++++++++++++++++++++++++--------------------- package.json | 4 +- 2 files changed, 121 insertions(+), 98 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64d2df264..eb5e5b5bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "mattermost-mobile", - "version": "1.20.0", + "version": "1.21.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -5238,6 +5238,7 @@ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -5282,7 +5283,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "dev": true, + "optional": true }, "is-glob": { "version": "4.0.1", @@ -7662,24 +7664,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "resolved": "", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "optional": true }, "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": false, + "resolved": "", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { @@ -7689,13 +7692,15 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "resolved": "", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7703,34 +7708,37 @@ }, "chownr": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "resolved": "", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "resolved": "", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + "resolved": "", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { @@ -7739,25 +7747,25 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": false, + "resolved": "", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, + "resolved": "", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, + "resolved": "", "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { @@ -7766,13 +7774,13 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { @@ -7788,7 +7796,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, + "resolved": "", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { @@ -7802,13 +7810,13 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": false, + "resolved": "", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { @@ -7817,7 +7825,7 @@ }, "ignore-walk": { "version": "3.0.1", - "resolved": false, + "resolved": "", "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { @@ -7826,7 +7834,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { @@ -7836,46 +7844,51 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "resolved": "", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "optional": true }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "resolved": "", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "optional": true }, "minipass": { "version": "2.3.5", - "resolved": false, + "resolved": "", "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -7883,7 +7896,7 @@ }, "minizlib": { "version": "1.2.1", - "resolved": false, + "resolved": "", "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { @@ -7892,21 +7905,22 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "resolved": false, + "resolved": "", "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { @@ -7917,7 +7931,7 @@ }, "node-pre-gyp": { "version": "0.12.0", - "resolved": false, + "resolved": "", "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { @@ -7935,7 +7949,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { @@ -7945,13 +7959,13 @@ }, "npm-bundled": { "version": "1.0.6", - "resolved": false, + "resolved": "", "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "resolved": false, + "resolved": "", "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { @@ -7961,7 +7975,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { @@ -7973,38 +7987,40 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "resolved": "", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { @@ -8014,19 +8030,19 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "resolved": false, + "resolved": "", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { @@ -8038,7 +8054,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } @@ -8046,7 +8062,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { @@ -8061,7 +8077,7 @@ }, "rimraf": { "version": "2.6.3", - "resolved": false, + "resolved": "", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { @@ -8070,43 +8086,45 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "resolved": "", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, + "resolved": "", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "resolved": false, + "resolved": "", "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -8115,7 +8133,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { @@ -8124,21 +8142,22 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "optional": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "resolved": false, + "resolved": "", "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { @@ -8153,13 +8172,13 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": false, + "resolved": "", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { @@ -8168,13 +8187,15 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "resolved": "", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "yallist": { "version": "3.0.3", - "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "resolved": "", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "optional": true } } }, @@ -12558,9 +12579,9 @@ "dev": true }, "jsc-android": { - "version": "241213.1.0", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.1.0.tgz", - "integrity": "sha512-AH8NYyMNLNhcUEF97QbMxPNLNW+oiSBlvm1rsMNzgJ1d5TQzdh/AOJGsxeeESp3m9YIWGLCgUvGTVoVLs0p68A==" + "version": "241213.2.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.2.0.tgz", + "integrity": "sha512-nfddejB9jxFSG+Uewf+zwATFi8F2CZEEgoHLoOj13egiBDoC7zMoxK1c5/Ycf3AGmGuwCgjpn3LWe0f4tKYbjw==" }, "jsdom": { "version": "11.12.0", @@ -14160,7 +14181,7 @@ "dependencies": { "ansi-regex": { "version": "3.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, @@ -14172,7 +14193,7 @@ }, "cliui": { "version": "4.1.0", - "resolved": false, + "resolved": "", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { @@ -14201,7 +14222,7 @@ }, "find-up": { "version": "3.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { @@ -14216,13 +14237,13 @@ }, "invert-kv": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "lcid": { "version": "2.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", "dev": true, "requires": { @@ -14231,7 +14252,7 @@ }, "locate-path": { "version": "3.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { @@ -14258,7 +14279,7 @@ }, "os-locale": { "version": "3.1.0", - "resolved": false, + "resolved": "", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", "dev": true, "requires": { @@ -14278,7 +14299,7 @@ }, "p-locate": { "version": "3.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { @@ -14299,7 +14320,7 @@ }, "resolve-from": { "version": "4.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, @@ -14333,7 +14354,7 @@ }, "strip-ansi": { "version": "4.0.0", - "resolved": false, + "resolved": "", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { @@ -14342,13 +14363,13 @@ }, "uuid": { "version": "3.3.2", - "resolved": false, + "resolved": "", "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "y18n": { "version": "4.0.0", - "resolved": false, + "resolved": "", "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, @@ -16010,7 +16031,8 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "dev": true, + "optional": true }, "braces": { "version": "2.3.2", @@ -16273,7 +16295,8 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "dev": true, + "optional": true }, "micromatch": { "version": "3.1.10", diff --git a/package.json b/package.json index 4a839bb1a..7284c6e00 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "fuse.js": "3.4.4", "intl": "1.2.5", "jail-monkey": "2.2.0", - "jsc-android": "241213.1.0", + "jsc-android": "241213.2.0", "mattermost-redux": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e", "mime-db": "1.40.0", "moment-timezone": "0.5.25", @@ -146,4 +146,4 @@ "node_modules/(?!react-native|jail-monkey)" ] } -} \ No newline at end of file +} From 36b235d49f269202802a777118806fb1a22d5412 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 19 Jun 2019 18:25:52 -0400 Subject: [PATCH 07/28] MM-16432 Fix missing posts when using since API (#2901) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index eb5e5b5bd..5a52a1bca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13155,8 +13155,8 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e", - "from": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e", + "version": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", + "from": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", "requires": { "deep-equal": "1.0.1", "eslint-plugin-header": "3.0.0", diff --git a/package.json b/package.json index 7284c6e00..5cbfe0c64 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "intl": "1.2.5", "jail-monkey": "2.2.0", "jsc-android": "241213.2.0", - "mattermost-redux": "github:mattermost/mattermost-redux#40cecab5d74708d651460515b24115fd75fb334e", + "mattermost-redux": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", "mime-db": "1.40.0", "moment-timezone": "0.5.25", "prop-types": "15.7.2", From 3f79a2e07f172ef6a00cb335155fc7bf02662e51 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 19 Jun 2019 18:43:06 -0400 Subject: [PATCH 08/28] Bump app build number to 201 (#2903) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 41e4ffcf7..9d60e7df6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -122,7 +122,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 199 + versionCode 201 versionName "1.21.0" multiDexEnabled = true ndk { diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index fa6ad1918..33de3701c 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2762,7 +2762,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 199; + CURRENT_PROJECT_VERSION = 201; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2822,7 +2822,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 199; + CURRENT_PROJECT_VERSION = 201; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index fa58027fa..2080c88ac 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 199 + 201 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 5f3933390..d317efc1f 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 199 + 201 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index c26979a00..04c126b06 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 199 + 201 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 0fcc481c8..186ab3194 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 199 + 201 NSExtension NSExtensionPointIdentifier From 06b3273fd846eeaa4ba8abe11ce8a833bdce0098 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Fri, 21 Jun 2019 11:29:03 -0400 Subject: [PATCH 09/28] MM-16472 Fix renderItem not returning anything (#2909) --- app/screens/channel_members/channel_members.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/screens/channel_members/channel_members.js b/app/screens/channel_members/channel_members.js index d3149d13c..c56f8b4c7 100644 --- a/app/screens/channel_members/channel_members.js +++ b/app/screens/channel_members/channel_members.js @@ -243,7 +243,7 @@ export default class ChannelMembers extends PureComponent { enabled: props.id !== this.props.currentUserId, }; - this.renderItem(props, selectProps); + return this.renderItem(props, selectProps); } renderUnselectableItem = (props) => { @@ -252,7 +252,7 @@ export default class ChannelMembers extends PureComponent { enabled: false, }; - this.renderItem(props, selectProps); + return this.renderItem(props, selectProps); }; renderLoading = () => { From 3f925df9d9ca0f7b057554ba90a5ecfc52475ffc Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Fri, 21 Jun 2019 14:10:30 -0400 Subject: [PATCH 10/28] MM-16392 Split view support & hide sidebar on tablets (#2898) --- android/app/src/main/AndroidManifest.xml | 3 +- .../rnbeta/MattermostManagedModule.java | 14 ++++++++ app/app.js | 5 +-- app/components/sidebars/main/main_sidebar.js | 25 +++++++++++-- app/mattermost.js | 22 ++++++++---- .../mattermost-managed.android.js | 1 + .../mattermost-managed.ios.js | 1 + app/screens/channel/channel_base.js | 7 ++-- .../channel_nav_bar/channel_nav_bar.js | 29 +++++++++++++-- ios/Mattermost.xcodeproj/project.pbxproj | 6 ++++ ios/Mattermost/Mattermost+RCTUITextView.h | 17 +++++++++ ios/Mattermost/Mattermost+RCTUITextView.m | 36 +++++++++++++++++++ ios/Mattermost/MattermostManaged.m | 33 ++++++----------- 13 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 ios/Mattermost/Mattermost+RCTUITextView.h create mode 100644 ios/Mattermost/Mattermost+RCTUITextView.m diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e3d966fce..4fff02aa5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -44,7 +44,8 @@ android:exported="false" /> + android:configChanges="keyboard|keyboardHidden|orientation|screenSize" + android:resizeableActivity="true"/> { @@ -124,6 +133,15 @@ export default class ChannelSidebar extends Component { return false; }; + handleDimensions = () => { + if (DeviceTypes.IS_TABLET && this.mounted) { + mattermostManaged.isRunningInSplitView().then((result) => { + const isSplitView = Boolean(result.isSplitView); + this.setState({isSplitView}); + }); + } + }; + handleShowDrawerContent = () => { this.setState({show: true}); }; @@ -380,6 +398,7 @@ export default class ChannelSidebar extends Component { render() { const {children, deviceWidth} = this.props; const {openDrawerOffset} = this.state; + const isTablet = DeviceTypes.IS_TABLET && !this.state.isSplitView; const drawerWidth = DeviceTypes.IS_TABLET ? TABLET_WIDTH : (deviceWidth - openDrawerOffset); return ( @@ -390,7 +409,7 @@ export default class ChannelSidebar extends Component { onDrawerOpen={this.handleDrawerOpen} drawerWidth={drawerWidth} useNativeAnimations={true} - isTablet={DeviceTypes.IS_TABLET} + isTablet={isTablet} > {children} diff --git a/app/mattermost.js b/app/mattermost.js index 0d11b7154..5f82d5d70 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -405,7 +405,7 @@ const launchSelectServer = () => { }); }; -const launchChannel = () => { +const launchChannel = (skipMetrics = false) => { Navigation.startSingleScreenApp({ screen: { screen: 'Channel', @@ -415,6 +415,9 @@ const launchChannel = () => { statusBarHideWithNavBar: false, screenBackgroundColor: 'transparent', }, + passProps: { + skipMetrics, + }, }, appStyle: { orientation: 'auto', @@ -513,14 +516,19 @@ const fromPushNotification = Platform.OS === 'android' && Initialization.replyFr if (startedSharedExtension || fromPushNotification) { // Hold on launching Entry screen app.setAppStarted(true); - - // Listen for when the user opens the app - new NativeEventsReceiver().appLaunched(() => { - app.setAppStarted(false); - launchEntry(); - }); } if (!app.appStarted) { launchEntry(); } + +new NativeEventsReceiver().appLaunched(() => { + if (startedSharedExtension || fromPushNotification) { + app.setAppStarted(false); + launchEntry(); + } else if (app.token && app.url) { + launchChannel(true); + } else { + launchSelectServer(); + } +}); diff --git a/app/mattermost_managed/mattermost-managed.android.js b/app/mattermost_managed/mattermost-managed.android.js index da192cb58..3cdd00095 100644 --- a/app/mattermost_managed/mattermost-managed.android.js +++ b/app/mattermost_managed/mattermost-managed.android.js @@ -36,6 +36,7 @@ export default { }, authenticate: LocalAuth.auth, blurAppScreen: MattermostManaged.blurAppScreen, + isRunningInSplitView: MattermostManaged.isRunningInSplitView, getConfig: async () => { try { cachedConfig = await MattermostManaged.getConfig(); diff --git a/app/mattermost_managed/mattermost-managed.ios.js b/app/mattermost_managed/mattermost-managed.ios.js index 7b6d81525..9c2415da2 100644 --- a/app/mattermost_managed/mattermost-managed.ios.js +++ b/app/mattermost_managed/mattermost-managed.ios.js @@ -52,6 +52,7 @@ export default { return cachedConfig; }, hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets, + isRunningInSplitView: MattermostManaged.isRunningInSplitView, isDeviceSecure: async () => { try { return await LocalAuth.isDeviceSecure(); diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index da40eacda..7dd030714 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -48,6 +48,7 @@ export default class ChannelBase extends PureComponent { theme: PropTypes.object.isRequired, showTermsOfService: PropTypes.bool, disableTermsModal: PropTypes.bool, + skipMetrics: PropTypes.bool, }; static contextTypes = { @@ -88,7 +89,7 @@ export default class ChannelBase extends PureComponent { } componentDidMount() { - if (tracker.initialLoad) { + if (tracker.initialLoad && !this.props.skipMetrics) { this.props.actions.recordLoadTime('Start time', 'initialLoad'); } @@ -98,7 +99,9 @@ export default class ChannelBase extends PureComponent { EventEmitter.emit('renderDrawer'); - telemetry.end(['start:channel_screen']); + if (!this.props.skipMetrics) { + telemetry.end(['start:channel_screen']); + } } componentWillReceiveProps(nextProps) { diff --git a/app/screens/channel/channel_nav_bar/channel_nav_bar.js b/app/screens/channel/channel_nav_bar/channel_nav_bar.js index d84a2cd6f..6e0a84f40 100644 --- a/app/screens/channel/channel_nav_bar/channel_nav_bar.js +++ b/app/screens/channel/channel_nav_bar/channel_nav_bar.js @@ -3,9 +3,10 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; -import {Platform, View} from 'react-native'; +import {Dimensions, Platform, View} from 'react-native'; import {DeviceTypes, ViewTypes} from 'app/constants'; +import mattermostManaged from 'app/mattermost_managed'; import {makeStyleSheetFromTheme} from 'app/utils/theme'; import ChannelDrawerButton from './channel_drawer_button'; @@ -31,6 +32,30 @@ export default class ChannelNavBar extends PureComponent { theme: PropTypes.object.isRequired, }; + state = { + isSplitView: false, + }; + + componentDidMount() { + this.mounted = true; + this.handleDimensions(); + Dimensions.addEventListener('change', this.handleDimensions); + } + + componentWillUnmount() { + this.mounted = false; + Dimensions.removeEventListener('change', this.handleDimensions); + } + + handleDimensions = () => { + if (DeviceTypes.IS_TABLET && this.mounted) { + mattermostManaged.isRunningInSplitView().then((result) => { + const isSplitView = Boolean(result.isSplitView); + this.setState({isSplitView}); + }); + } + }; + render() { const {isLandscape, navigator, onPress, theme} = this.props; const {openChannelDrawer, openSettingsDrawer} = this.props; @@ -60,7 +85,7 @@ export default class ChannelNavBar extends PureComponent { } let drawerButtonVisible = false; - if (!DeviceTypes.IS_TABLET) { + if (!DeviceTypes.IS_TABLET || this.state.isSplitView) { drawerButtonVisible = true; } diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 33de3701c..78b742f59 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ 7F581D35221ED5C60099E66B /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F581D34221ED5C60099E66B /* NotificationService.swift */; }; 7F581D39221ED5C60099E66B /* NotificationService.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7F581D32221ED5C60099E66B /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 7F581F78221EEA7C0099E66B /* libUploadAttachments.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FABE04522137F2A00D0F595 /* libUploadAttachments.a */; }; + 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */; }; 7F5CA9A0208FE3B9004F91CE /* libRNDocumentPicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F5CA991208FE38F004F91CE /* libRNDocumentPicker.a */; }; 7F642DF02093533300F3165E /* libRNDeviceInfo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F642DED2093530B00F3165E /* libRNDeviceInfo.a */; }; 7F72F2EE2211220500F98FFF /* GenericPreview.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7F72F2ED2211220500F98FFF /* GenericPreview.xib */; }; @@ -819,6 +820,8 @@ 7F581D34221ED5C60099E66B /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 7F581D36221ED5C60099E66B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7F581F77221EEA5A0099E66B /* NotificationService.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotificationService.entitlements; sourceTree = ""; }; + 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Mattermost+RCTUITextView.h"; path = "Mattermost/Mattermost+RCTUITextView.h"; sourceTree = ""; }; + 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "Mattermost+RCTUITextView.m"; path = "Mattermost/Mattermost+RCTUITextView.m"; sourceTree = ""; }; 7F5CA956208FE38F004F91CE /* RNDocumentPicker.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RNDocumentPicker.xcodeproj; path = "../node_modules/react-native-document-picker/ios/RNDocumentPicker.xcodeproj"; sourceTree = ""; }; 7F63D27B1E6C957C001FAE12 /* RCTPushNotification.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTPushNotification.xcodeproj; path = "../node_modules/react-native/Libraries/PushNotificationIOS/RCTPushNotification.xcodeproj"; sourceTree = ""; }; 7F63D2C21E6DD98A001FAE12 /* Mattermost.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Mattermost.entitlements; path = Mattermost/Mattermost.entitlements; sourceTree = ""; }; @@ -1112,6 +1115,8 @@ 7FEB109C1F61019C0039A015 /* UIImage+ImageEffects.m */, 7F151D40221B069200FAD8F3 /* 0155-keys.png */, 7F292AA51E8ABB1100A450A3 /* splash.png */, + 7F5BA34522B99B7B005B05D3 /* Mattermost+RCTUITextView.h */, + 7F5BA34622B99B7B005B05D3 /* Mattermost+RCTUITextView.m */, ); name = Mattermost; sourceTree = ""; @@ -2618,6 +2623,7 @@ 7FEB10981F6101710039A015 /* BlurAppScreen.m in Sources */, 7FEB109D1F61019C0039A015 /* MattermostManaged.m in Sources */, 7F240ACD220D460300637665 /* MattermostBucketModule.m in Sources */, + 7F5BA34722B99B7B005B05D3 /* Mattermost+RCTUITextView.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/Mattermost/Mattermost+RCTUITextView.h b/ios/Mattermost/Mattermost+RCTUITextView.h new file mode 100644 index 000000000..67057d05e --- /dev/null +++ b/ios/Mattermost/Mattermost+RCTUITextView.h @@ -0,0 +1,17 @@ +// +// Mattermost+RCTUITextView.h +// Mattermost +// +// Created by Elias Nahum on 6/18/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface Mattermost_RCTUITextView : NSObject + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/Mattermost/Mattermost+RCTUITextView.m b/ios/Mattermost/Mattermost+RCTUITextView.m new file mode 100644 index 000000000..505798bbc --- /dev/null +++ b/ios/Mattermost/Mattermost+RCTUITextView.m @@ -0,0 +1,36 @@ +// +// Mattermost+RCTUITextView.m +// Mattermost +// +// Created by Elias Nahum on 6/18/19. +// Copyright © 2019 Facebook. All rights reserved. +// + +#import "Mattermost+RCTUITextView.h" +#import "RCTUITextView.h" + +@implementation Mattermost_RCTUITextView + +@end + +@implementation RCTUITextView (DisableCopyPaste) + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender +{ + NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:@"com.apple.configuration.managed"]; + if(response) { + NSString *copyPasteProtection = response[@"copyAndPasteProtection"]; + BOOL prevent = action == @selector(paste:) || + action == @selector(copy:) || + action == @selector(cut:) || + action == @selector(_share:); + + if ([copyPasteProtection isEqual: @"true"] && prevent) { + return NO; + } + } + + return [super canPerformAction:action withSender:sender]; +} + +@end diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m index 4000f5902..b2f5c5ce1 100644 --- a/ios/Mattermost/MattermostManaged.m +++ b/ios/Mattermost/MattermostManaged.m @@ -6,7 +6,6 @@ // See License.txt for license information. // -#import "RCTUITextView.h" #import "MattermostManaged.h" #import @@ -152,31 +151,19 @@ RCT_EXPORT_METHOD(getConfig:(RCTPromiseResolveBlock)resolve } } +RCT_EXPORT_METHOD(isRunningInSplitView:(RCTPromiseResolveBlock)resolve + rejecter:(RCTPromiseRejectBlock)reject) { + BOOL isRunningInFullScreen = CGRectEqualToRect( + [UIApplication sharedApplication].delegate.window.frame, + [UIApplication sharedApplication].delegate.window.screen.bounds); + resolve(@{ + @"isSplitView": @(!isRunningInFullScreen) + }); +} + RCT_EXPORT_METHOD(quitApp) { exit(0); } @end - -@implementation RCTUITextView (DisableCopyPaste) - -- (BOOL)canPerformAction:(SEL)action withSender:(id)sender -{ - NSDictionary *response = [[NSUserDefaults standardUserDefaults] dictionaryForKey:configurationKey]; - if(response) { - NSString *copyPasteProtection = response[@"copyAndPasteProtection"]; - BOOL prevent = action == @selector(paste:) || - action == @selector(copy:) || - action == @selector(cut:) || - action == @selector(_share:); - - if ([copyPasteProtection isEqual: @"true"] && prevent) { - return NO; - } - } - - return [super canPerformAction:action withSender:sender]; -} - -@end From 4d9780da5629f789ef3d492aec24eaaf6d5401dc Mon Sep 17 00:00:00 2001 From: Chris Duarte Date: Sat, 22 Jun 2019 01:40:16 +0530 Subject: [PATCH 11/28] Ios accessibility fixes (#2842) * UCHAT-4371 // MM-15771 - iOS - Larger Fonts Cut Off Text in menu and settings screens revert padding right * Update snapshot --- .../__snapshots__/channel_item.test.js.snap | 28 +++++-------------- .../channel_item/channel_item.js | 4 +-- .../settings/settings_item/style.ios.js | 2 +- 3 files changed, 9 insertions(+), 25 deletions(-) diff --git a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap index 5940ff56f..8a859004b 100644 --- a/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap +++ b/app/components/sidebars/main/channels_list/channel_item/__snapshots__/channel_item.test.js.snap @@ -81,14 +81,12 @@ exports[`ChannelItem should match snapshot 1`] = ` style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -196,14 +194,12 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -311,14 +307,12 @@ exports[`ChannelItem should match snapshot for current user i.e currentUser (you style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -426,14 +420,12 @@ exports[`ChannelItem should match snapshot for deactivated user and is currentCh style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -530,14 +522,12 @@ exports[`ChannelItem should match snapshot for deactivated user and is searchRes style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -640,14 +630,12 @@ exports[`ChannelItem should match snapshot with draft 1`] = ` style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", @@ -746,14 +734,12 @@ exports[`ChannelItem should match snapshot with mentions and muted 1`] = ` style={ Array [ Object { + "alignSelf": "center", "color": "rgba(255,255,255,0.4)", "flex": 1, "fontSize": 14, "fontWeight": "600", - "height": "100%", - "lineHeight": 44, "paddingRight": 10, - "textAlignVertical": "center", }, Object { "color": "#ffffff", diff --git a/app/components/sidebars/main/channels_list/channel_item/channel_item.js b/app/components/sidebars/main/channels_list/channel_item/channel_item.js index 4e5301985..e1ab8a03a 100644 --- a/app/components/sidebars/main/channels_list/channel_item/channel_item.js +++ b/app/components/sidebars/main/channels_list/channel_item/channel_item.js @@ -242,10 +242,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => { fontSize: 14, fontWeight: '600', paddingRight: 10, - height: '100%', flex: 1, - textAlignVertical: 'center', - lineHeight: 44, + alignSelf: 'center', }, textActive: { color: theme.sidebarTextActiveColor, diff --git a/app/screens/settings/settings_item/style.ios.js b/app/screens/settings/settings_item/style.ios.js index ddf47bf21..bebbcfe96 100644 --- a/app/screens/settings/settings_item/style.ios.js +++ b/app/screens/settings/settings_item/style.ios.js @@ -40,7 +40,7 @@ export default makeStyleSheetFromTheme((theme) => { color: theme.centerChannelColor, flex: 1, fontSize: 17, - lineHeight: 43, + alignSelf: 'center', }, arrowContainer: { justifyContent: 'center', From e5f98b51517c9e06c8b47406bf068f31439a7840 Mon Sep 17 00:00:00 2001 From: amyblais Date: Fri, 21 Jun 2019 19:39:09 -0400 Subject: [PATCH 12/28] 1.20.1 Changelog (#2902) * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 877e7cfb3..da70323d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Mattermost Mobile Apps Changelog +## 1.20.1 Release +- Release Date: June 21, 2019 +- Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device + +### Combatibility + - Mobile App v1.13+ is required for Mattermost Server v5.4+. + - Android operating system 7+ [is required by Google](https://android-developers.googleblog.com/2017/12/improving-app-security-and-performance.html). + - iPhone 5s devices and later with iOS 11+ is required. + +### Bug Fixes + - Fixed an issue where some Android devices were crashing. + - Fixed an issue where messages were missing after reconnecting the network. + ## 1.20.0 Release - Release Date: June 16, 2019 - Server Versions Supported: Server v4.10+ is required, Self-Signed SSL Certificates are not supported unless the user installs the CA certificate on their device From e6fdef34519b639986ccf0310fdf77d42fb77304 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Sat, 22 Jun 2019 14:21:57 -0400 Subject: [PATCH 13/28] MM-16397 save draft when app state changes (#2899) * MM-16397 save draft when app state changes * Unit tests --- .../__snapshots__/post_textbox.test.js.snap | 149 ++++++++++++++++++ .../post_textbox/post_textbox.test.js | 90 +++++++++++ .../post_textbox/post_textbox_base.js | 13 ++ 3 files changed, 252 insertions(+) create mode 100644 app/components/post_textbox/__snapshots__/post_textbox.test.js.snap create mode 100644 app/components/post_textbox/post_textbox.test.js diff --git a/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap new file mode 100644 index 000000000..ac7535189 --- /dev/null +++ b/app/components/post_textbox/__snapshots__/post_textbox.test.js.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`PostTextBox should match, full snapshot 1`] = ` + + + + + + + + + + + + +`; diff --git a/app/components/post_textbox/post_textbox.test.js b/app/components/post_textbox/post_textbox.test.js new file mode 100644 index 000000000..6362f1397 --- /dev/null +++ b/app/components/post_textbox/post_textbox.test.js @@ -0,0 +1,90 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallowWithIntl} from 'test/intl-test-helper'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import PostTextbox from './post_textbox.ios'; + +jest.mock('NativeEventEmitter'); + +describe('PostTextBox', () => { + const baseProps = { + actions: { + addReactionToLatestPost: jest.fn(), + createPost: jest.fn(), + executeCommand: jest.fn(), + handleCommentDraftChanged: jest.fn(), + handlePostDraftChanged: jest.fn(), + handleClearFiles: jest.fn(), + handleClearFailedFiles: jest.fn(), + handleRemoveLastFile: jest.fn(), + initUploadFiles: jest.fn(), + userTyping: jest.fn(), + handleCommentDraftSelectionChanged: jest.fn(), + setStatus: jest.fn(), + selectPenultimateChannel: jest.fn(), + }, + canUploadFiles: true, + channelId: 'channel-id', + channelDisplayName: 'Test Channel', + channelTeamId: 'channel-team-id', + channelIsLoading: false, + channelIsReadOnly: false, + currentUserId: 'current-user-id', + deactivatedChannel: false, + files: [], + maxFileSize: 1024, + maxMessageLength: 4000, + navigator: { + showModal: jest.fn(), + }, + rootId: '', + theme: Preferences.THEMES.default, + uploadFileRequestStatus: 'NOT_STARTED', + value: '', + userIsOutOfOffice: false, + channelIsArchived: false, + onCloseChannel: jest.fn(), + cursorPositionEvent: '', + valueEvent: '', + }; + + test('should match, full snapshot', () => { + const wrapper = shallowWithIntl( + + ); + + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('should emit the event but no text is save to draft', () => { + const wrapper = shallowWithIntl( + + ); + + wrapper.setState({value: 'some text'}); + + const instance = wrapper.instance(); + + instance.changeDraft = jest.fn(); + instance.handleAppStateChange('active'); + expect(instance.changeDraft).not.toBeCalled(); + }); + + test('should emit the event and text is save to draft', () => { + const wrapper = shallowWithIntl( + + ); + + const instance = wrapper.instance(); + const value = 'some text'; + + wrapper.setState({value}); + instance.handleAppStateChange('background'); + expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledWith(baseProps.channelId, value); + expect(baseProps.actions.handlePostDraftChanged).toHaveBeenCalledTimes(1); + }); +}); diff --git a/app/components/post_textbox/post_textbox_base.js b/app/components/post_textbox/post_textbox_base.js index d9d884743..08ae4a1ae 100644 --- a/app/components/post_textbox/post_textbox_base.js +++ b/app/components/post_textbox/post_textbox_base.js @@ -5,6 +5,7 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import { Alert, + AppState, BackHandler, findNodeHandle, Keyboard, @@ -99,7 +100,10 @@ export default class PostTextBoxBase extends PureComponent { componentDidMount() { const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT; + EventEmitter.on(event, this.handleInsertTextToDraft); + AppState.addEventListener('change', this.handleAppStateChange); + if (Platform.OS === 'android') { Keyboard.addListener('keyboardDidHide', this.handleAndroidKeyboard); BackHandler.addEventListener('hardwareBackPress', this.handleAndroidBack); @@ -114,7 +118,10 @@ export default class PostTextBoxBase extends PureComponent { componentWillUnmount() { const event = this.props.rootId ? INSERT_TO_COMMENT : INSERT_TO_DRAFT; + EventEmitter.off(event, this.handleInsertTextToDraft); + AppState.removeEventListener('change', this.handleAppStateChange); + if (Platform.OS === 'android') { Keyboard.removeListener('keyboardDidHide', this.handleAndroidKeyboard); BackHandler.removeEventListener('hardwareBackPress', this.handleAndroidBack); @@ -248,6 +255,12 @@ export default class PostTextBoxBase extends PureComponent { return false; }; + handleAppStateChange = (nextAppState) => { + if (nextAppState !== 'active') { + this.changeDraft(this.state.value); + } + }; + handleEndEditing = (e) => { if (e && e.nativeEvent) { this.changeDraft(e.nativeEvent.text || ''); From 0e84b912575e0bd6bb39030327c82d85d41806ea Mon Sep 17 00:00:00 2001 From: Jesse Hallam Date: Mon, 24 Jun 2019 16:36:24 -0300 Subject: [PATCH 14/28] MM-16442: count actual reactions before falling back to has_reactions (#2910) * MM-16442: leverage post hasReactions utils function This handles backwards compatibility with servers that do not have post metadata, allowing the client to detect the presence of reactions that in turn might need to be loaded when the component is mounted. * temporary mattermost-redux commit for testing * revert usage of hasReactions, and count directly We still fallback to has_reactions if set, in case we're connected to a server without post metadata. * revert mattermost-redux changes --- app/components/post_body/index.js | 5 ++++- app/screens/long_post/index.js | 6 ++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/components/post_body/index.js b/app/components/post_body/index.js index 7830d1eaa..6cbe0507a 100644 --- a/app/components/post_body/index.js +++ b/app/components/post_body/index.js @@ -10,6 +10,7 @@ import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general import {getCurrentUserId, getCurrentUserRoles} from 'mattermost-redux/selectors/entities/users'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCustomEmojisByName} from 'mattermost-redux/selectors/entities/emojis'; +import {makeGetReactionsForPost} from 'mattermost-redux/selectors/entities/posts'; import {memoizeResult} from 'mattermost-redux/utils/helpers'; import { @@ -30,10 +31,12 @@ const POST_TIMEOUT = 20000; function makeMapStateToProps() { const memoizeHasEmojisOnly = memoizeResult((message, customEmojis) => hasEmojisOnly(message, customEmojis)); + const getReactionsForPost = makeGetReactionsForPost(); return (state, ownProps) => { const post = ownProps.post; const channel = getChannel(state, post.channel_id) || {}; + const reactions = getReactionsForPost(state, post.id); let isFailed = post.failed; let isPending = post.id === post.pending_post_id; @@ -83,7 +86,7 @@ function makeMapStateToProps() { fileIds: post.file_ids, hasBeenDeleted: post.state === Posts.POST_DELETED, hasBeenEdited: isEdited(post), - hasReactions: post.has_reactions, + hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions), isFailed, isPending, isPostAddChannelMember, diff --git a/app/screens/long_post/index.js b/app/screens/long_post/index.js index 2040d9a72..1b6f2d954 100644 --- a/app/screens/long_post/index.js +++ b/app/screens/long_post/index.js @@ -4,7 +4,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {selectPost} from 'mattermost-redux/actions/posts'; +import {selectPost, makeGetReactionsForPost} from 'mattermost-redux/actions/posts'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; @@ -15,14 +15,16 @@ import LongPost from './long_post'; function makeMapStateToProps() { const getChannel = makeGetChannel(); + const getReactionsForPost = makeGetReactionsForPost(); return function mapStateToProps(state, ownProps) { const post = getPost(state, ownProps.postId); const channel = post ? getChannel(state, {id: post.channel_id}) : null; + const reactions = getReactionsForPost(state, post.id); return { channelName: channel ? channel.display_name : '', - hasReactions: post ? post.has_reactions : false, + hasReactions: (reactions && Object.keys(reactions).length > 0) || Boolean(post.has_reactions), inThreadView: Boolean(state.entities.posts.selectedPostId), fileIds: post ? post.file_ids : false, theme: getTheme(state), From 02f97002d04d38452ee26438ffe050f93bd6858c Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Mon, 24 Jun 2019 16:22:01 -0400 Subject: [PATCH 15/28] MM-16480 Update mattermost-redux (#2917) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5a52a1bca..b37b97a3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13155,8 +13155,8 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", - "from": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", + "version": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", + "from": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", "requires": { "deep-equal": "1.0.1", "eslint-plugin-header": "3.0.0", diff --git a/package.json b/package.json index 5cbfe0c64..e99f64bb7 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "intl": "1.2.5", "jail-monkey": "2.2.0", "jsc-android": "241213.2.0", - "mattermost-redux": "github:mattermost/mattermost-redux#c2ba4c4409c81a3ee658277de3c20ea996b8a3ac", + "mattermost-redux": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", "mime-db": "1.40.0", "moment-timezone": "0.5.25", "prop-types": "15.7.2", From c121f665261bc83c4facaf528f7a99abd28a8f56 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 26 Jun 2019 04:59:07 -0400 Subject: [PATCH 16/28] MM-16463 Fix replying from push notification to a message in a thread (#2927) --- .../rnbeta/InitializationModule.java | 3 --- .../mattermost/rnbeta/MainApplication.java | 1 - .../NotificationReplyBroadcastReceiver.java | 6 ++++- app/mattermost.js | 5 ++--- .../push_notifications.android.js | 22 +------------------ 5 files changed, 8 insertions(+), 29 deletions(-) diff --git a/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java b/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java index 6ec2e082e..cf8a489dc 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java @@ -57,7 +57,6 @@ public class InitializationModule extends ReactContextBaseJavaModule { * * Miscellaneous: * MattermostManaged.Config - * replyFromPushNotification */ MainApplication app = (MainApplication) mApplication; @@ -138,8 +137,6 @@ public class InitializationModule extends ReactContextBaseJavaModule { } constants.put("managedConfig", config[0]); - constants.put("replyFromPushNotification", app.replyFromPushNotification); - app.replyFromPushNotification = false; return constants; } diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java index 0fabde91c..967d9cd3b 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -60,7 +60,6 @@ import android.util.Log; public class MainApplication extends NavigationApplication implements INotificationsApplication, INotificationsDrawerApplication { public NotificationsLifecycleFacade notificationsLifecycleFacade; public Boolean sharedExtensionIsOpened = false; - public Boolean replyFromPushNotification = false; public long APP_START_TIME; diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java index f5610431f..cc99e0316 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java @@ -79,7 +79,11 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver { protected void replyToMessage(final String serverUrl, final String token, final int notificationId, final CharSequence message) { final String channelId = bundle.getString("channel_id"); - final String rootId = bundle.getString("post_id"); + final String postId = bundle.getString("post_id"); + String rootId = bundle.getString("root_id"); + if (android.text.TextUtils.isEmpty(rootId)) { + rootId = postId; + } if (token == null || serverUrl == null) { onReplyFailed(notificationManager, notificationId, channelId); diff --git a/app/mattermost.js b/app/mattermost.js index 5f82d5d70..93338f7fe 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -511,9 +511,8 @@ const launchEntry = () => { configurePushNotifications(); const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened; -const fromPushNotification = Platform.OS === 'android' && Initialization.replyFromPushNotification; -if (startedSharedExtension || fromPushNotification) { +if (startedSharedExtension) { // Hold on launching Entry screen app.setAppStarted(true); } @@ -523,7 +522,7 @@ if (!app.appStarted) { } new NativeEventsReceiver().appLaunched(() => { - if (startedSharedExtension || fromPushNotification) { + if (startedSharedExtension) { app.setAppStarted(false); launchEntry(); } else if (app.token && app.url) { diff --git a/app/push_notifications/push_notifications.android.js b/app/push_notifications/push_notifications.android.js index 9e30212f1..2e3943a32 100644 --- a/app/push_notifications/push_notifications.android.js +++ b/app/push_notifications/push_notifications.android.js @@ -1,11 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {AppRegistry, AppState, NativeModules} from 'react-native'; +import {AppState, NativeModules} from 'react-native'; import {NotificationsAndroid, PendingNotifications} from 'react-native-notifications'; -import Notification from 'react-native-notifications/notification.android'; - -import {emptyFunction} from 'app/utils/general'; const {NotificationPreferences} = NativeModules; @@ -37,23 +34,6 @@ class PushNotification { this.handleNotification(data, true); } }); - - AppRegistry.registerHeadlessTask('notificationReplied', () => async (deviceNotification) => { - const notification = new Notification(deviceNotification); - const data = notification.getData(); - const completed = emptyFunction; - - if (this.onReply) { - this.onReply(data, data.text, parseInt(data.badge, 10) - parseInt(data.msg_count, 10), completed); - } else { - this.deviceNotification = { - data, - text: data.text, - badge: parseInt(data.badge, 10) - parseInt(data.msg_count, 10), - completed, // used to identify that the notification belongs to a reply - }; - } - }); } handleNotification = (data, userInteraction) => { From fdb6cc6b2a669b31a5901d1468b8c9f81c5fb793 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 26 Jun 2019 02:01:44 -0700 Subject: [PATCH 17/28] [MM-16456] Ensure Flatlist is mounted prior to calling scrollToOffset (#2925) * Ensure Flatlist is mounted prior to calling scrollToOffset * Make linter happy --- app/components/post_list/post_list.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index 3cc733f04..ddbae2381 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -281,7 +281,9 @@ export default class PostList extends PureComponent { scrollToBottom = () => { setTimeout(() => { - this.flatListRef.current.scrollToOffset({offset: 0, animated: true}); + if (this.flatListRef && this.flatListRef.current) { + this.flatListRef.current.scrollToOffset({offset: 0, animated: true}); + } }, 250); }; From bef593c3cf404d3478dfbe05258f868f6360a52e Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 26 Jun 2019 08:33:04 -0400 Subject: [PATCH 18/28] MM-16467 Fix iOS layout when playing a youtube video in landscape (#2926) --- .../post_body_additional_content.js | 11 +++++++++++ app/components/safe_area_view/safe_area_view.ios.js | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js index 4adcdc806..9d957b074 100644 --- a/app/components/post_body_additional_content/post_body_additional_content.js +++ b/app/components/post_body_additional_content/post_body_additional_content.js @@ -10,10 +10,13 @@ import { Platform, StyleSheet, TouchableOpacity, + StatusBar, } from 'react-native'; import {YouTubeStandaloneAndroid, YouTubeStandaloneIOS} from 'react-native-youtube'; import {intlShape} from 'react-intl'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + import {TABLET_WIDTH} from 'app/components/sidebars/drawer_layout'; import PostAttachmentImage from 'app/components/post_attachment_image'; import ProgressiveImage from 'app/components/progressive_image'; @@ -442,6 +445,7 @@ export default class PostBodyAdditionalContent extends PureComponent { if (Platform.OS === 'ios') { YouTubeStandaloneIOS. playVideo(videoId, startTime). + then(this.playYouTubeVideoEnded). catch(this.playYouTubeVideoError); } else { const {googleDeveloperKey} = this.props; @@ -459,6 +463,13 @@ export default class PostBodyAdditionalContent extends PureComponent { } }; + playYouTubeVideoEnded = () => { + if (Platform.OS === 'ios') { + StatusBar.setHidden(false); + EventEmitter.emit('update_safe_area_view'); + } + }; + playYouTubeVideoError = (errorMessage) => { const {formatMessage} = this.context.intl; diff --git a/app/components/safe_area_view/safe_area_view.ios.js b/app/components/safe_area_view/safe_area_view.ios.js index 8477a78b7..73f79a00f 100644 --- a/app/components/safe_area_view/safe_area_view.ios.js +++ b/app/components/safe_area_view/safe_area_view.ios.js @@ -6,6 +6,8 @@ import PropTypes from 'prop-types'; import {Dimensions, Keyboard, NativeModules, View} from 'react-native'; import SafeArea from 'react-native-safe-area'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + import {DeviceTypes} from 'app/constants'; import mattermostManaged from 'app/mattermost_managed'; @@ -57,6 +59,7 @@ export default class SafeAreaIos extends PureComponent { componentDidMount() { Dimensions.addEventListener('change', this.getSafeAreaInsets); + EventEmitter.on('update_safe_area_view', this.getSafeAreaInsets); this.keyboardDidShowListener = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow); this.keyboardDidHideListener = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide); this.getStatusBarHeight(); @@ -65,6 +68,7 @@ export default class SafeAreaIos extends PureComponent { componentWillUnmount() { this.mounted = false; Dimensions.removeEventListener('change', this.getSafeAreaInsets); + EventEmitter.off('update_safe_area_view', this.getSafeAreaInsets); this.keyboardDidShowListener.remove(); this.keyboardDidHideListener.remove(); this.mounted = false; From ce4b48b6956baf7b13870663a92f8d73ded66229 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 26 Jun 2019 11:50:44 -0700 Subject: [PATCH 19/28] Check if entities is nil (#2930) --- .../UploadAttachments/MattermostBucket.m | 7 ++- .../UploadAttachments/UploadSession.swift | 44 ++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m index a9b99de04..8be32f6a3 100644 --- a/ios/UploadAttachments/UploadAttachments/MattermostBucket.m +++ b/ios/UploadAttachments/UploadAttachments/MattermostBucket.m @@ -32,8 +32,13 @@ if(![fileManager fileExistsAtPath:filePath]) { return nil; } + NSData *data = [NSData dataWithContentsOfFile:filePath]; - return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; + if (data != nil) { + return [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil]; + } + + return nil; } -(void)removeFile:(NSString *)fileName { diff --git a/ios/UploadAttachments/UploadAttachments/UploadSession.swift b/ios/UploadAttachments/UploadAttachments/UploadSession.swift index de1b51726..8a9c5b64f 100644 --- a/ios/UploadAttachments/UploadAttachments/UploadSession.swift +++ b/ios/UploadAttachments/UploadAttachments/UploadSession.swift @@ -126,28 +126,30 @@ import os.log public func notificationReceipt(notificationId: Any?, receivedAt: Int, type: Any?) { let store = StoreManager.shared() as StoreManager - let _ = store.getEntities(true) - let serverURL = store.getServerUrl() - let sessionToken = store.getToken() - let urlString = "\(serverURL!)/api/v4/notifications/ack" - - if (notificationId != nil) { - let jsonObject: [String: Any] = [ - "id": notificationId as Any, - "received_at": receivedAt, - "platform": "ios", - "type": type as Any - ] + let entities = store.getEntities(true) + if (entities != nil) { + let serverURL = store.getServerUrl() + let sessionToken = store.getToken() + let urlString = "\(serverURL!)/api/v4/notifications/ack" - if !JSONSerialization.isValidJSONObject(jsonObject) {return} - - guard let url = URL(string: urlString) else {return} - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization") - request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") - request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) - URLSession(configuration: .ephemeral).dataTask(with: request).resume() + if (notificationId != nil) { + let jsonObject: [String: Any] = [ + "id": notificationId as Any, + "received_at": receivedAt, + "platform": "ios", + "type": type as Any + ] + + if !JSONSerialization.isValidJSONObject(jsonObject) {return} + + guard let url = URL(string: urlString) else {return} + var request = URLRequest(url: url) + request.httpMethod = "POST" + request.setValue("Bearer \(sessionToken!)", forHTTPHeaderField: "Authorization") + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.httpBody = try? JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted) + URLSession(configuration: .ephemeral).dataTask(with: request).resume() + } } } } From 89b96d51db1c20cecd31049c2357351488e4db92 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Wed, 26 Jun 2019 15:21:20 -0400 Subject: [PATCH 20/28] MM-16430 refactor app entry point (#2924) * MM-16430 refactor app entry point * iOS Extensions to use session token from KeyChain * Remove token from general credentials entity * Fix mattermost-managed.ios to return the cachedConfig * Migrate server based keychain for android push notifications * remove unneeded async * Remove unneeded android InitializationModule --- .../rnbeta/InitializationModule.java | 143 ----- .../rnbeta/InitializationPackage.java | 36 -- .../mattermost/rnbeta/MainApplication.java | 1 - .../rnbeta/MattermostCredentialsHelper.java | 41 ++ .../NotificationReplyBroadcastReceiver.java | 17 +- .../mattermost/rnbeta/ReceiptDelivery.java | 19 +- app/actions/views/channel.js | 12 +- app/actions/views/login.js | 5 +- app/actions/views/login.test.js | 6 +- app/app.js | 316 ----------- app/init/credentials.js | 128 +++++ app/init/emm_provider.js | 165 ++++++ app/{fetch_preconfig.js => init/fetch.js} | 3 + app/init/global_event_handler.js | 233 ++++++++ app/mattermost.js | 516 ++---------------- .../mattermost-managed.android.js | 2 + .../mattermost-managed.ios.js | 1 + app/screens/channel/channel_base.js | 7 +- app/screens/entry/entry.js | 321 ----------- app/screens/entry/index.js | 39 -- app/screens/index.js | 2 - app/screens/select_server/select_server.js | 22 +- app/store/ephemeral_store.js | 12 + app/store/index.js | 1 - app/utils/error_handling.js | 85 +-- app/utils/push_notifications.js | 297 +++++----- app/utils/url.js | 3 +- index.js | 44 +- ios/Mattermost/MattermostManaged.m | 1 + .../project.pbxproj | 28 +- .../UploadAttachments/MMKeychainManager.h | 3 + .../UploadAttachments/MMKeychainManager.m | 73 +++ .../UploadAttachments/StoreManager.h | 2 + .../UploadAttachments/StoreManager.m | 13 +- .../UploadAttachments-Bridging-Header.h | 1 + packager/moduleNames.js | 181 +----- packager/modulePaths.js | 165 +----- .../android/extension_post/extension_post.js | 36 +- 38 files changed, 1094 insertions(+), 1886 deletions(-) delete mode 100644 android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java delete mode 100644 android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java create mode 100644 android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java delete mode 100644 app/app.js create mode 100644 app/init/credentials.js create mode 100644 app/init/emm_provider.js rename app/{fetch_preconfig.js => init/fetch.js} (97%) create mode 100644 app/init/global_event_handler.js delete mode 100644 app/screens/entry/entry.js delete mode 100644 app/screens/entry/index.js create mode 100644 app/store/ephemeral_store.js create mode 100644 ios/UploadAttachments/UploadAttachments/MMKeychainManager.h create mode 100644 ios/UploadAttachments/UploadAttachments/MMKeychainManager.m diff --git a/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java b/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java deleted file mode 100644 index cf8a489dc..000000000 --- a/android/app/src/main/java/com/mattermost/rnbeta/InitializationModule.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.mattermost.rnbeta; - -import android.app.Application; -import android.support.annotation.Nullable; - -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContextBaseJavaModule; -import com.facebook.react.bridge.WritableMap; -import com.facebook.react.bridge.WritableNativeMap; -import com.mattermost.react_native_interface.AsyncStorageHelper; -import com.mattermost.react_native_interface.KeysReadableArray; -import com.mattermost.react_native_interface.ResolvePromise; -import com.oblador.keychain.KeychainModule; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -public class InitializationModule extends ReactContextBaseJavaModule { - - static final String TOOLBAR_BACKGROUND = "TOOLBAR_BACKGROUND"; - static final String TOOLBAR_TEXT_COLOR = "TOOLBAR_TEXT_COLOR"; - static final String APP_BACKGROUND = "APP_BACKGROUND"; - - private final Application mApplication; - - public InitializationModule(Application application, ReactApplicationContext reactContext) { - super(reactContext); - mApplication = application; - } - - @Override - public String getName() { - return "Initialization"; - } - - @Nullable - @Override - public Map getConstants() { - Map constants = new HashMap<>(); - - /** - * Package all native module variables in constants - * in order to avoid the native bridge - * - * KeyStore: - * credentialsExist - * deviceToken - * currentUserId - * token - * url - * - * AsyncStorage: - * toolbarBackground - * toolbarTextColor - * appBackground - * - * Miscellaneous: - * MattermostManaged.Config - */ - - MainApplication app = (MainApplication) mApplication; - final Boolean[] credentialsExist = {false}; - final WritableMap[] credentials = {null}; - final Object[] config = {null}; - - // Get KeyStore credentials - KeychainModule module = new KeychainModule(this.getReactApplicationContext()); - module.getGenericPasswordForOptions(null, new ResolvePromise() { - @Override - public void resolve(@Nullable Object value) { - if (value instanceof Boolean && !(Boolean)value) { - credentialsExist[0] = false; - return; - } - - WritableMap map = (WritableMap) value; - if (map != null) { - credentialsExist[0] = true; - credentials[0] = map; - } - } - }); - - // Get managedConfig from MattermostManagedModule - MattermostManagedModule.getInstance().getConfig(new ResolvePromise() { - @Override - public void resolve(@Nullable Object value) { - WritableNativeMap nativeMap = (WritableNativeMap) value; - config[0] = value; - } - }); - - - // Get AsyncStorage key/values - final ArrayList keys = new ArrayList(5); - keys.add(TOOLBAR_BACKGROUND); - keys.add(TOOLBAR_TEXT_COLOR); - keys.add(APP_BACKGROUND); - KeysReadableArray asyncStorageKeys = new KeysReadableArray() { - @Override - public int size() { - return keys.size(); - } - - @Override - public String getString(int index) { - return keys.get(index); - } - }; - - AsyncStorageHelper asyncStorage = new AsyncStorageHelper(this.getReactApplicationContext()); - HashMap asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys); - - String toolbarBackground = asyncStorageResults.get(TOOLBAR_BACKGROUND); - String toolbarTextColor = asyncStorageResults.get(TOOLBAR_TEXT_COLOR); - String appBackground = asyncStorageResults.get(APP_BACKGROUND); - - if (toolbarBackground != null - && toolbarTextColor != null - && appBackground != null) { - - constants.put("themesExist", true); - constants.put("toolbarBackground", toolbarBackground); - constants.put("toolbarTextColor", toolbarTextColor); - constants.put("appBackground", appBackground); - } else { - constants.put("themesExist", false); - } - - - if (credentialsExist[0]) { - constants.put("credentialsExist", true); - constants.put("credentials", credentials[0]); - } else { - constants.put("credentialsExist", false); - } - - constants.put("managedConfig", config[0]); - - return constants; - } -} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java b/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java deleted file mode 100644 index 79e7e12e7..000000000 --- a/android/app/src/main/java/com/mattermost/rnbeta/InitializationPackage.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.mattermost.rnbeta; - -import android.app.Application; - -import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; -import com.facebook.react.bridge.NativeModule; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class InitializationPackage implements ReactPackage { - - private final Application mApplication; - - public InitializationPackage(Application application) { - mApplication = application; - } - - @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Arrays.asList(new InitializationModule(mApplication, reactContext)); - } - - public List> createJSModules() { - return Collections.emptyList(); - } - - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); - } -} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java index 967d9cd3b..4ac35a02e 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.java @@ -100,7 +100,6 @@ public class MainApplication extends NavigationApplication implements INotificat new ReactNativeDocumentPicker(), new SharePackage(this), new KeychainPackage(), - new InitializationPackage(this), new AsyncStoragePackage(), new NetInfoPackage(), new RNCWebViewPackage(), diff --git a/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java b/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java new file mode 100644 index 000000000..1c1d331ee --- /dev/null +++ b/android/app/src/main/java/com/mattermost/rnbeta/MattermostCredentialsHelper.java @@ -0,0 +1,41 @@ +package com.mattermost.rnbeta; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.HashMap; + +import com.facebook.react.bridge.ReactApplicationContext; + +import com.oblador.keychain.KeychainModule; + +import com.mattermost.react_native_interface.ResolvePromise; +import com.mattermost.react_native_interface.AsyncStorageHelper; +import com.mattermost.react_native_interface.KeysReadableArray; + +public class MattermostCredentialsHelper { + static final String CURRENT_SERVER_URL = "@currentServerUrl"; + + public static void getCredentialsForCurrentServer(ReactApplicationContext context, ResolvePromise promise) { + final KeychainModule keychainModule = new KeychainModule(context); + final AsyncStorageHelper asyncStorage = new AsyncStorageHelper(context); + final ArrayList keys = new ArrayList(1); + keys.add(CURRENT_SERVER_URL); + KeysReadableArray asyncStorageKeys = new KeysReadableArray() { + @Override + public int size() { + return keys.size(); + } + + @Override + public String getString(int index) { + return keys.get(index); + } + }; + + HashMap asyncStorageResults = asyncStorage.multiGet(asyncStorageKeys); + String serverUrl = asyncStorageResults.get(CURRENT_SERVER_URL); + + keychainModule.getGenericPasswordForOptions(serverUrl, promise); + } +} diff --git a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java index cc99e0316..dd02b935e 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/NotificationReplyBroadcastReceiver.java @@ -24,7 +24,6 @@ import okhttp3.Response; import com.mattermost.react_native_interface.ResolvePromise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; -import com.oblador.keychain.KeychainModule; import com.wix.reactnativenotifications.core.NotificationIntentAdapter; @@ -47,27 +46,19 @@ public class NotificationReplyBroadcastReceiver extends BroadcastReceiver { final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context); final int notificationId = intent.getIntExtra(CustomPushNotification.NOTIFICATION_ID, -1); - final KeychainModule keychainModule = new KeychainModule(reactApplicationContext); - keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() { + + MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() { @Override public void resolve(@Nullable Object value) { if (value instanceof Boolean && !(Boolean)value) { - String channelId = bundle.getString("channel_id"); - onReplyFailed(notificationManager, notificationId, channelId); return; } WritableMap map = (WritableMap) value; if (map != null) { - String[] credentials = map.getString("password").split(",[ ]*"); - String token = null; - String serverUrl = null; - if (credentials.length == 2) { - token = credentials[0]; - serverUrl = credentials[1]; - - } + String token = map.getString("password"); + String serverUrl = map.getString("service"); Log.i("ReactNative", String.format("URL=%s TOKEN=%s", serverUrl, token)); replyToMessage(serverUrl, token, notificationId, message); diff --git a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java index 64ec0d96f..a276fec0a 100644 --- a/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java +++ b/android/app/src/main/java/com/mattermost/rnbeta/ReceiptDelivery.java @@ -15,17 +15,18 @@ import okhttp3.Response; import org.json.JSONObject; import org.json.JSONException; -import com.mattermost.react_native_interface.ResolvePromise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; -import com.oblador.keychain.KeychainModule; + +import com.mattermost.react_native_interface.ResolvePromise; public class ReceiptDelivery { + static final String CURRENT_SERVER_URL = "@currentServerUrl"; + public static void send (Context context, final String ackId, final String type) { final ReactApplicationContext reactApplicationContext = new ReactApplicationContext(context); - final KeychainModule keychainModule = new KeychainModule(reactApplicationContext); - keychainModule.getGenericPasswordForOptions(null, new ResolvePromise() { + MattermostCredentialsHelper.getCredentialsForCurrentServer(reactApplicationContext, new ResolvePromise() { @Override public void resolve(@Nullable Object value) { if (value instanceof Boolean && !(Boolean)value) { @@ -34,14 +35,8 @@ public class ReceiptDelivery { WritableMap map = (WritableMap) value; if (map != null) { - String[] credentials = map.getString("password").split(",[ ]*"); - String token = null; - String serverUrl = null; - if (credentials.length == 2) { - token = credentials[0]; - serverUrl = credentials[1]; - - } + String token = map.getString("password"); + String serverUrl = map.getString("service"); Log.i("ReactNative", String.format("Send receipt delivery ACK=%s TYPE=%s to URL=%s with TOKEN=%s", ackId, type, serverUrl, token)); execute(serverUrl, token, ackId, type); diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 511f5a8b6..32418fd2e 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -21,7 +21,7 @@ import { } from 'mattermost-redux/actions/posts'; import {getFilesForPost} from 'mattermost-redux/actions/files'; import {savePreferences} from 'mattermost-redux/actions/preferences'; -import {getTeamMembersByIds} from 'mattermost-redux/actions/teams'; +import {getTeamMembersByIds, selectTeam} from 'mattermost-redux/actions/teams'; import {getProfilesInChannel} from 'mattermost-redux/actions/users'; import {General, Preferences} from 'mattermost-redux/constants'; import {getPostIdsInChannel} from 'mattermost-redux/selectors/entities/posts'; @@ -54,8 +54,8 @@ import {isDirectChannelVisible, isGroupChannelVisible} from 'app/utils/channels' const MAX_POST_TRIES = 3; export function loadChannelsIfNecessary(teamId) { - return async (dispatch, getState) => { - await fetchMyChannelsAndMembers(teamId)(dispatch, getState); + return async (dispatch) => { + await dispatch(fetchMyChannelsAndMembers(teamId)); }; } @@ -416,6 +416,12 @@ export function handleSelectChannelByName(channelName, teamName) { const currentTeamName = currentTeam?.name; const {data: channel} = await dispatch(getChannelByNameAndTeamName(teamName || currentTeamName, channelName)); const currentChannelId = getCurrentChannelId(state); + + if (teamName && teamName !== currentTeamName) { + const team = getTeamByName(state, teamName); + dispatch(selectTeam(team)); + } + if (channel && currentChannelId !== channel.id) { dispatch(handleSelectChannel(channel.id)); } diff --git a/app/actions/views/login.js b/app/actions/views/login.js index 740659e4d..f84d97b9d 100644 --- a/app/actions/views/login.js +++ b/app/actions/views/login.js @@ -11,7 +11,7 @@ import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users'; import {ViewTypes} from 'app/constants'; -import {app} from 'app/mattermost'; +import {setAppCredentials} from 'app/init/credentials'; import PushNotifications from 'app/push_notifications'; import {getDeviceTimezone} from 'app/utils/timezone'; import {setCSRFFromCookie} from 'app/utils/security'; @@ -45,7 +45,7 @@ export function handleSuccessfulLogin() { const currentUserId = getCurrentUserId(state); await setCSRFFromCookie(url); - app.setAppCredentials(deviceToken, currentUserId, token, url); + setAppCredentials(deviceToken, currentUserId, token, url); const enableTimezone = isTimezoneEnabled(state); if (enableTimezone) { @@ -56,7 +56,6 @@ export function handleSuccessfulLogin() { type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: { url, - token, }, }); diff --git a/app/actions/views/login.test.js b/app/actions/views/login.test.js index 66020d79c..f75dee2f2 100644 --- a/app/actions/views/login.test.js +++ b/app/actions/views/login.test.js @@ -11,10 +11,8 @@ import { handlePasswordChanged, } from 'app/actions/views/login'; -jest.mock('app/mattermost', () => ({ - app: { - setAppCredentials: () => jest.fn(), - }, +jest.mock('app/init/credentials', () => ({ + setAppCredentials: () => jest.fn(), })); jest.mock('react-native-cookies', () => ({ diff --git a/app/app.js b/app/app.js deleted file mode 100644 index 1011b164d..000000000 --- a/app/app.js +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -/* eslint-disable global-require*/ -import {Linking, NativeModules, Platform, Text} from 'react-native'; -import AsyncStorage from '@react-native-community/async-storage'; -import {setGenericPassword, getGenericPassword, resetGenericPassword} from 'react-native-keychain'; - -import {loadMe} from 'mattermost-redux/actions/users'; -import {Client4} from 'mattermost-redux/client'; -import EventEmitter from 'mattermost-redux/utils/event_emitter'; - -import {setDeepLinkURL} from 'app/actions/views/root'; -import {ViewTypes} from 'app/constants'; -import tracker from 'app/utils/time_tracker'; -import {getCurrentLocale} from 'app/selectors/i18n'; - -import {getTranslations as getLocalTranslations} from 'app/i18n'; -import {store, handleManagedConfig} from 'app/mattermost'; -import avoidNativeBridge from 'app/utils/avoid_native_bridge'; -import {setCSRFFromCookie} from 'app/utils/security'; - -const {Initialization} = NativeModules; - -const TOOLBAR_BACKGROUND = 'TOOLBAR_BACKGROUND'; -const TOOLBAR_TEXT_COLOR = 'TOOLBAR_TEXT_COLOR'; -const APP_BACKGROUND = 'APP_BACKGROUND'; - -export default class App { - constructor() { - // Usage: app.js - this.shouldRelaunchWhenActive = false; - this.inBackgroundSince = null; - this.previousAppState = null; - - // Usage: screen/entry.js - this.startAppFromPushNotification = false; - this.isNotificationsConfigured = false; - this.allowOtherServers = true; - this.appStarted = false; - this.emmEnabled = false; - this.performingEMMAuthentication = false; - this.translations = null; - this.toolbarBackground = null; - this.toolbarTextColor = null; - this.appBackground = null; - - // Usage utils/push_notifications.js - this.replyNotificationData = null; - this.deviceToken = null; - - // Usage credentials - this.currentUserId = null; - this.token = null; - this.url = null; - - // Usage deeplinking - Linking.addEventListener('url', this.handleDeepLink); - - this.setFontFamily(); - this.getStartupThemes(); - this.getAppCredentials(); - } - - setFontFamily = () => { - // Set a global font for Android - if (Platform.OS === 'android') { - const defaultFontFamily = { - style: { - fontFamily: 'Roboto', - }, - }; - const TextRender = Text.render; - const initialDefaultProps = Text.defaultProps; - Text.defaultProps = { - ...initialDefaultProps, - ...defaultFontFamily, - }; - Text.render = function render(props, ...args) { - const oldProps = props; - let newProps = {...props, style: [defaultFontFamily.style, props.style]}; - try { - return Reflect.apply(TextRender, this, [newProps, ...args]); - } finally { - newProps = oldProps; - } - }; - } - }; - - getTranslations = () => { - if (this.translations) { - return this.translations; - } - - const state = store.getState(); - const locale = getCurrentLocale(state); - - this.translations = getLocalTranslations(locale); - return this.translations; - }; - - getAppCredentials = async () => { - try { - const credentials = await avoidNativeBridge( - () => { - return Initialization.credentialsExist; - }, - () => { - return Initialization.credentials; - }, - () => { - this.waitForRehydration = true; - return getGenericPassword(); - } - ); - - if (credentials) { - const usernameParsed = credentials.username.split(','); - const passwordParsed = credentials.password.split(','); - - // username == deviceToken, currentUserId - // password == token, url - if (usernameParsed.length === 2 && passwordParsed.length === 2) { - const [deviceToken, currentUserId] = usernameParsed; - const [token, url] = passwordParsed; - - // if for any case the url and the token aren't valid proceed with re-hydration - if (url && url !== 'undefined' && token && token !== 'undefined') { - this.deviceToken = deviceToken; - this.currentUserId = currentUserId; - this.token = token; - this.url = url; - Client4.setUrl(url); - Client4.setToken(token); - await setCSRFFromCookie(url); - } else { - this.waitForRehydration = true; - } - } - } else { - this.waitForRehydration = false; - } - } catch (error) { - return null; - } - - return null; - }; - - getStartupThemes = async () => { - try { - const [ - toolbarBackground, - toolbarTextColor, - appBackground, - ] = await avoidNativeBridge( - () => { - return Initialization.themesExist; - }, - () => { - return [ - Initialization.toolbarBackground, - Initialization.toolbarTextColor, - Initialization.appBackground, - ]; - }, - () => { - return Promise.all([ - AsyncStorage.getItem(TOOLBAR_BACKGROUND), - AsyncStorage.getItem(TOOLBAR_TEXT_COLOR), - AsyncStorage.getItem(APP_BACKGROUND), - ]); - } - ); - - if (toolbarBackground) { - this.toolbarBackground = toolbarBackground; - this.toolbarTextColor = toolbarTextColor; - this.appBackground = appBackground; - } - } catch (error) { - return null; - } - - return null; - }; - - setPerformingEMMAuthentication = (authenticating) => { - this.performingEMMAuthentication = authenticating; - }; - - setAppCredentials = (deviceToken, currentUserId, token, url) => { - if (!currentUserId) { - return; - } - - const username = `${deviceToken}, ${currentUserId}`; - const password = `${token},${url}`; - - this.token = token; - this.url = url; - - if (this.waitForRehydration) { - this.waitForRehydration = false; - } - - // Only save to keychain if the url and token are set - if (url && token) { - try { - setGenericPassword(username, password); - } catch (e) { - console.warn('could not set credentials', e); //eslint-disable-line no-console - } - } - }; - - setStartupThemes = (toolbarBackground, toolbarTextColor, appBackground) => { - AsyncStorage.setItem(TOOLBAR_BACKGROUND, toolbarBackground); - AsyncStorage.setItem(TOOLBAR_TEXT_COLOR, toolbarTextColor); - AsyncStorage.setItem(APP_BACKGROUND, appBackground); - }; - - setStartAppFromPushNotification = (startAppFromPushNotification) => { - this.startAppFromPushNotification = startAppFromPushNotification; - }; - - setIsNotificationsConfigured = (isNotificationsConfigured) => { - this.isNotificationsConfigured = isNotificationsConfigured; - }; - - setAllowOtherServers = (allowOtherServers) => { - this.allowOtherServers = allowOtherServers; - }; - - setAppStarted = (appStarted) => { - this.appStarted = appStarted; - }; - - setEMMEnabled = (emmEnabled) => { - this.emmEnabled = emmEnabled; - }; - - setDeviceToken = (deviceToken) => { - this.deviceToken = deviceToken; - }; - - setReplyNotificationData = (replyNotificationData) => { - this.replyNotificationData = replyNotificationData; - }; - - setInBackgroundSince = (inBackgroundSince) => { - this.inBackgroundSince = inBackgroundSince; - }; - - setShouldRelaunchWhenActive = (shouldRelaunchWhenActive) => { - this.shouldRelaunchWhenActive = shouldRelaunchWhenActive; - }; - - clearNativeCache = () => { - resetGenericPassword(); - AsyncStorage.multiRemove([ - TOOLBAR_BACKGROUND, - TOOLBAR_TEXT_COLOR, - APP_BACKGROUND, - ]); - }; - - handleDeepLink = (event) => { - const {url} = event; - store.dispatch(setDeepLinkURL(url)); - } - - launchApp = async () => { - const shouldStart = await handleManagedConfig(); - if (shouldStart) { - this.startApp(); - } - }; - - startApp = () => { - if (this.appStarted || this.waitForRehydration) { - return; - } - - const {dispatch} = store; - - Linking.getInitialURL().then((url) => { - dispatch(setDeepLinkURL(url)); - }); - - let screen = 'SelectServer'; - if (this.token && this.url) { - screen = 'Channel'; - tracker.initialLoad = Date.now(); - - try { - dispatch(loadMe()); - } catch (e) { - // Fall through since we should have a previous version of the current user because we have a token - console.warn('Failed to load current user when starting on Channel screen', e); // eslint-disable-line no-console - } - } - - switch (screen) { - case 'SelectServer': - EventEmitter.emit(ViewTypes.LAUNCH_LOGIN, true); - break; - case 'Channel': - EventEmitter.emit(ViewTypes.LAUNCH_CHANNEL, true); - break; - } - - this.setAppStarted(true); - } -} diff --git a/app/init/credentials.js b/app/init/credentials.js new file mode 100644 index 000000000..0f4ee7753 --- /dev/null +++ b/app/init/credentials.js @@ -0,0 +1,128 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import AsyncStorage from '@react-native-community/async-storage'; +import * as KeyChain from 'react-native-keychain'; + +import {Client4} from 'mattermost-redux/client'; + +import mattermostManaged from 'app/mattermost_managed'; +import ephemeralStore from 'app/store/ephemeral_store'; +import {setCSRFFromCookie} from 'app/utils/security'; + +const CURRENT_SERVER = '@currentServerUrl'; + +export const getCurrentServerUrl = async () => { + return AsyncStorage.getItem(CURRENT_SERVER); +}; + +export const setAppCredentials = (deviceToken, currentUserId, token, url) => { + if (!currentUserId) { + return; + } + + // Only save to keychain if the url and token are set + if (url && token) { + try { + const username = `${deviceToken}, ${currentUserId}`; + + ephemeralStore.deviceToken = deviceToken; + AsyncStorage.setItem(CURRENT_SERVER, url); + KeyChain.setInternetCredentials(url, username, token, {accessGroup: mattermostManaged.appGroupIdentifier}); + } catch (e) { + console.warn('could not set credentials', e); //eslint-disable-line no-console + } + } +}; + +export const getAppCredentials = async () => { + const serverUrl = await AsyncStorage.getItem(CURRENT_SERVER); + + if (serverUrl) { + return getInternetCredentials(serverUrl); + } + + return getCredentialsFromGenericKeyChain(); +}; + +export const removeAppCredentials = async () => { + const url = await getCurrentServerUrl(); + + Client4.setCSRF(null); + Client4.serverVersion = ''; + Client4.setUserId(''); + Client4.setToken(''); + Client4.setUrl(''); + + if (url) { + KeyChain.resetInternetCredentials(url); + } + + KeyChain.resetGenericPassword(); + AsyncStorage.removeItem(CURRENT_SERVER); +}; + +async function getCredentialsFromGenericKeyChain() { + try { + const credentials = await KeyChain.getGenericPassword(); + + if (credentials) { + const usernameParsed = credentials.username.split(','); + const passwordParsed = credentials.password.split(','); + + // username == deviceToken, currentUserId + // password == token, url + if (usernameParsed.length === 2 && passwordParsed.length === 2) { + const [deviceToken, currentUserId] = usernameParsed; + const [token, url] = passwordParsed; + + // if for any case the url and the token aren't valid proceed with re-hydration + if (url && url !== 'undefined' && token && token !== 'undefined') { + Client4.setUserId(currentUserId); + Client4.setUrl(url); + Client4.setToken(token); + await setCSRFFromCookie(url); + + // Migration: remove the generic credentials and add a server specific one + setAppCredentials(deviceToken, currentUserId, token, url); + KeyChain.resetGenericPassword(); + + return { + username: usernameParsed, + password: token, + }; + } + } + } + + return null; + } catch (e) { + return null; + } +} + +async function getInternetCredentials(url) { + try { + const credentials = await KeyChain.getInternetCredentials(url); + + if (credentials) { + const usernameParsed = credentials.username.split(','); + const token = credentials.password; + const [deviceToken, currentUserId] = usernameParsed; + + if (token && token !== 'undefined') { + ephemeralStore.deviceToken = deviceToken; + Client4.setUserId(currentUserId); + Client4.setUrl(url); + Client4.setToken(token); + await setCSRFFromCookie(url); + + return credentials; + } + } + + return null; + } catch (e) { + return null; + } +} diff --git a/app/init/emm_provider.js b/app/init/emm_provider.js new file mode 100644 index 000000000..77dfad013 --- /dev/null +++ b/app/init/emm_provider.js @@ -0,0 +1,165 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Alert, Platform} from 'react-native'; + +import {handleLoginIdChanged} from 'app/actions/views/login'; +import {handleServerUrlChanged} from 'app/actions/views/select_server'; +import {getTranslations} from 'app/i18n'; +import mattermostBucket from 'app/mattermost_bucket'; +import mattermostManaged from 'app/mattermost_managed'; +import {getCurrentLocale} from 'app/selectors/i18n'; +import {t} from 'app/utils/i18n'; + +import {getAppCredentials} from './credentials'; + +import LocalConfig from 'assets/config'; + +class EMMProvider { + constructor() { + this.enabled = false; + this.performingAuthentication = false; + this.previousAppState = null; + + this.inAppPinCode = false; + this.blurApplicationScreen = false; + this.jailbreakProtection = false; + this.vendor = null; + + this.allowOtherServers = true; + this.emmServerUrl = null; + this.emmUsername = null; + } + + checkIfDeviceIsTrusted = (store) => { + const isTrusted = mattermostManaged.isTrustedDevice(); + + if (!isTrusted) { + const state = store.getState(); + const locale = getCurrentLocale(state); + const translations = getTranslations(locale); + Alert.alert( + translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor), + translations[t('mobile.managed.jailbreak')].replace('{vendor}', this.vendor), + [{ + text: translations[t('mobile.managed.exit')], + style: 'destructive', + onPress: () => { + mattermostManaged.quitApp(); + }, + }], + {cancelable: false} + ); + } + }; + + handleAuthentication = async (store, prompt = true) => { + this.performingAuthentication = true; + const isSecured = await mattermostManaged.isDeviceSecure(); + const state = store.getState(); + const locale = getCurrentLocale(state); + const translations = getTranslations(locale); + + if (isSecured) { + try { + mattermostBucket.setPreference('emm', this.vendor); + if (prompt) { + await mattermostManaged.authenticate({ + reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', this.vendor), + fallbackToPasscode: true, + suppressEnterPassword: true, + }); + } + } catch (err) { + mattermostManaged.quitApp(); + return false; + } + } else { + await this.showNotSecuredAlert(translations); + + mattermostManaged.quitApp(); + return false; + } + + this.setPerformingAuthentication(false); + return true; + }; + + handleManagedConfig = async (store) => { + if (this.performingAuthentication) { + return true; + } + + const {dispatch} = store; + + if (LocalConfig.AutoSelectServerUrl) { + dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl)); + this.allowOtherServers = false; + } + + const managedConfig = await mattermostManaged.getConfig(); + + if (managedConfig && Object.keys(managedConfig).length) { + this.enabled = true; + this.inAppPinCode = managedConfig.inAppPinCode === 'true'; + + this.blurApplicationScreen = managedConfig.blurApplicationScreen === 'true'; + this.jailbreakProtection = managedConfig.jailbreakProtection === 'true'; + this.vendor = managedConfig.vendor || 'Mattermost'; + + const credentials = await getAppCredentials(); + if (!credentials) { + this.emmServerUrl = managedConfig.serverUrl; + this.emmUsername = managedConfig.username; + + if (managedConfig.allowOtherServers && managedConfig.allowOtherServers === 'false') { + this.allowOtherServers = false; + } + } + + if (this.blurApplicationScreen) { + mattermostManaged.blurAppScreen(true); + } + + if (this.emmServerUrl) { + dispatch(handleServerUrlChanged(this.emmServerUrl)); + } + + if (this.emmUsername) { + dispatch(handleLoginIdChanged(this.emmUsername)); + } + } + + return true; + }; + + showNotSecuredAlert = (translations) => { + return new Promise((resolve) => { + const options = []; + + if (Platform.OS === 'android') { + options.push({ + text: translations[t('mobile.managed.settings')], + onPress: () => { + mattermostManaged.goToSecuritySettings(); + }, + }); + } + + options.push({ + text: translations[t('mobile.managed.exit')], + onPress: resolve, + style: 'cancel', + }); + + Alert.alert( + translations[t('mobile.managed.blocked_by')].replace('{vendor}', this.vendor), + Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')], + options, + {cancelable: false, onDismiss: resolve}, + ); + }); + } +} + +export default new EMMProvider(); diff --git a/app/fetch_preconfig.js b/app/init/fetch.js similarity index 97% rename from app/fetch_preconfig.js rename to app/init/fetch.js index f5f74050f..57cd167a4 100644 --- a/app/fetch_preconfig.js +++ b/app/init/fetch.js @@ -2,6 +2,7 @@ // See LICENSE.txt for license information. import {Platform} from 'react-native'; +import DeviceInfo from 'react-native-device-info'; import RNFetchBlob from 'rn-fetch-blob'; import urlParse from 'url-parse'; @@ -146,6 +147,8 @@ const initFetchConfig = async () => { // no managed config } + Client4.setUserAgent(DeviceInfo.getUserAgent()); + if (Platform.OS === 'ios') { const certificate = await mattermostBucket.getPreference('cert'); fetchConfig = { diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js new file mode 100644 index 000000000..73bc3d04c --- /dev/null +++ b/app/init/global_event_handler.js @@ -0,0 +1,233 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Alert, AppState, Dimensions, Linking, NativeModules, Platform} from 'react-native'; +import DeviceInfo from 'react-native-device-info'; +import semver from 'semver'; + +import {setAppState, setServerVersion} from 'mattermost-redux/actions/general'; +import {loadMe, logout} from 'mattermost-redux/actions/users'; +import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; +import {Client4} from 'mattermost-redux/client'; +import {General} from 'mattermost-redux/constants'; +import EventEmitter from 'mattermost-redux/utils/event_emitter'; + +import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device'; +import {selectDefaultChannel} from 'app/actions/views/channel'; +import {loadConfigAndLicense, setDeepLinkURL, startDataCleanup} from 'app/actions/views/root'; +import {NavigationTypes} from 'app/constants'; +import {getTranslations} from 'app/i18n'; +import mattermostManaged from 'app/mattermost_managed'; +import PushNotifications from 'app/push_notifications'; +import {getCurrentLocale} from 'app/selectors/i18n'; +import {t} from 'app/utils/i18n'; +import {deleteFileCache} from 'app/utils/file'; + +import LocalConfig from 'assets/config'; + +import {getAppCredentials, removeAppCredentials} from './credentials'; +import emmProvider from './emm_provider'; + +const {StatusBarManager} = NativeModules; +const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 1000; + +class GlobalEventHandler { + constructor() { + EventEmitter.on(NavigationTypes.NAVIGATION_RESET, this.onLogout); + EventEmitter.on(NavigationTypes.RESTART_APP, this.onRestartApp); + EventEmitter.on(General.SERVER_VERSION_CHANGED, this.onServerVersionChanged); + EventEmitter.on(General.CONFIG_CHANGED, this.onServerConfigChanged); + EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, this.onSwitchToDefaultChannel); + Dimensions.addEventListener('change', this.onOrientationChange); + AppState.addEventListener('change', this.onAppStateChange); + Linking.addEventListener('url', this.onDeepLink); + } + + appActive = async () => { + // if the app is being controlled by an EMM provider + if (emmProvider.enabled && emmProvider.inAppPinCode) { + const authExpired = (Date.now() - emmProvider.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER; + + // Once the app becomes active we check if the device needs to have a passcode set + const prompt = emmProvider.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode + await emmProvider.handleAuthentication(this.store, prompt); + } + + emmProvider.inBackgroundSince = null; + }; + + appInactive = () => { + const {dispatch} = this.store; + + // When the app is sent to the background we set the time when that happens + // and perform a data clean up to improve on performance + emmProvider.inBackgroundSince = Date.now(); + + dispatch(startDataCleanup()); + }; + + configure = (opts) => { + this.store = opts.store; + this.launchEntry = opts.launchEntry; + + const window = Dimensions.get('window'); + this.onOrientationChange({window}); + + this.StatusBarSizeIOS = require('react-native-status-bar-size'); + if (Platform.OS === 'ios') { + this.StatusBarSizeIOS.addEventListener('willChange', this.onStatusBarHeightChange); + + StatusBarManager.getHeight( + (data) => { + this.onStatusBarHeightChange(data.height); + } + ); + } + + this.JavascriptAndNativeErrorHandler = require('app/utils/error_handling').default; + this.JavascriptAndNativeErrorHandler.initializeErrorHandling(this.store); + + mattermostManaged.addEventListener('managedConfigDidChange', this.onManagedConfigurationChange); + }; + + configureAnalytics = (config) => { + const initAnalytics = require('app/utils/segment').init; + + if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) { + initAnalytics(config); + } else { + global.analytics = null; + } + }; + + onAppStateChange = (appState) => { + const isActive = appState === 'active'; + const isBackground = appState === 'background'; + + if (this.store) { + this.store.dispatch(setAppState(isActive)); + } + + if (isActive && emmProvider.previousAppState === 'background') { + this.appActive(); + } else if (isBackground) { + this.appInactive(); + } + + emmProvider.previousAppState = appState; + }; + + onDeepLink = (event) => { + const {url} = event; + this.store.dispatch(setDeepLinkURL(url)); + }; + + onManagedConfigurationChange = () => { + emmProvider.handleManagedConfig(this.store, true); + }; + + onServerConfigChanged = (config) => { + this.configureAnalytics(config); + }; + + onLogout = async () => { + this.store.dispatch(closeWebSocket(false)); + this.store.dispatch(setServerVersion('')); + deleteFileCache(); + removeAppCredentials(); + + PushNotifications.setApplicationIconBadgeNumber(0); + PushNotifications.cancelAllLocalNotifications(); // TODO: Only cancel the notification that belongs to this server + + if (this.launchEntry) { + this.launchEntry(); + } + }; + + onOrientationChange = (dimensions) => { + if (this.store) { + const {dispatch} = this.store; + if (DeviceInfo.isTablet()) { + dispatch(setDeviceAsTablet()); + } + + const {height, width} = dimensions.window; + const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE'; + + dispatch(setDeviceOrientation(orientation)); + dispatch(setDeviceDimensions(height, width)); + } + }; + + onRestartApp = async () => { + await this.store.dispatch(loadConfigAndLicense()); + await this.store.dispatch(loadMe()); + + const window = Dimensions.get('window'); + this.onOrientationChange({window}); + + if (Platform.OS === 'ios') { + StatusBarManager.getHeight( + (data) => { + this.onStatusBarHeightChange(data.height); + } + ); + } + + if (this.launchEntry) { + const credentials = await getAppCredentials(); + this.launchEntry(credentials); + } + }; + + onServerVersionChanged = async (serverVersion) => { + const {dispatch, getState} = this.store; + const state = getState(); + const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0]; + const locale = getCurrentLocale(state); + const translations = getTranslations(locale); + + if (serverVersion) { + if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) { + Alert.alert( + translations[t('mobile.server_upgrade.title')], + translations[t('mobile.server_upgrade.description')], + [{ + text: translations[t('mobile.server_upgrade.button')], + onPress: this.serverUpgradeNeeded, + }], + {cancelable: false} + ); + } else if (state.entities.users && state.entities.users.currentUserId) { + dispatch(setServerVersion(serverVersion)); + const data = await dispatch(loadConfigAndLicense()); + this.configureAnalytics(data.config); + } + } + }; + + onStatusBarHeightChange = (nextStatusBarHeight) => { + this.store.dispatch(setStatusBarHeight(nextStatusBarHeight)); + }; + + onSwitchToDefaultChannel = (teamId) => { + this.store.dispatch(selectDefaultChannel(teamId)); + }; + + serverUpgradeNeeded = async () => { + const {dispatch} = this.store; + + dispatch(setServerVersion('')); + Client4.serverVersion = ''; + PushNotifications.setApplicationIconBadgeNumber(0); + PushNotifications.cancelAllLocalNotifications(); // TODO: Only cancel the notification that belongs to this server + + const credentials = await getAppCredentials(); + + if (credentials) { + dispatch(logout()); + } + }; +} + +export default new GlobalEventHandler(); diff --git a/app/mattermost.js b/app/mattermost.js index 93338f7fe..9ad9e2190 100644 --- a/app/mattermost.js +++ b/app/mattermost.js @@ -1,389 +1,47 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable global-require*/ -import { - Alert, - AppState, - Dimensions, - InteractionManager, - NativeModules, - Platform, - YellowBox, -} from 'react-native'; -const {StatusBarManager, MattermostShare, Initialization} = NativeModules; - -import DeviceInfo from 'react-native-device-info'; +import {Linking, NativeModules, Platform} from 'react-native'; import {Navigation, NativeEventsReceiver} from 'react-native-navigation'; import {Provider} from 'react-redux'; -import semver from 'semver'; -import {Client4} from 'mattermost-redux/client'; -import {setAppState, setServerVersion} from 'mattermost-redux/actions/general'; -import {loadMe, logout} from 'mattermost-redux/actions/users'; -import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; -import {General} from 'mattermost-redux/constants'; -import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; - -import {selectDefaultChannel} from 'app/actions/views/channel'; -import {setDeviceDimensions, setDeviceOrientation, setDeviceAsTablet, setStatusBarHeight} from 'app/actions/device'; -import {handleLoginIdChanged} from 'app/actions/views/login'; -import {handleServerUrlChanged} from 'app/actions/views/select_server'; -import {loadConfigAndLicense, startDataCleanup} from 'app/actions/views/root'; +import {loadMe} from 'mattermost-redux/actions/users'; +import {setDeepLinkURL} from 'app/actions/views/root'; import initialState from 'app/initial_state'; -import configureStore from 'app/store'; -import {NavigationTypes} from 'app/constants'; -import mattermostBucket from 'app/mattermost_bucket'; -import mattermostManaged from 'app/mattermost_managed'; -import {configurePushNotifications} from 'app/utils/push_notifications'; -import PushNotifications from 'app/push_notifications'; +import {getAppCredentials} from 'app/init/credentials'; +import emmProvider from 'app/init/emm_provider'; +import 'app/init/fetch'; +import globalEventHandler from 'app/init/global_event_handler'; import {registerScreens} from 'app/screens'; -import {deleteFileCache} from 'app/utils/file'; -import avoidNativeBridge from 'app/utils/avoid_native_bridge'; -import {t} from 'app/utils/i18n'; -import LocalConfig from 'assets/config'; +import configureStore from 'app/store'; +import ephemeralStore from 'app/store/ephemeral_store'; import telemetry from 'app/telemetry'; +import pushNotificationsUtils from 'app/utils/push_notifications'; -import App from './app'; -import './fetch_preconfig'; - -const PROMPT_IN_APP_PIN_CODE_AFTER = 5 * 60 * 1000; - -// Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841) -YellowBox.ignoreWarnings(['Require cycle: node_modules/react-native/Libraries/Network/fetch.js']); - -export const app = new App(); +const {MattermostShare} = NativeModules; +const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened; export const store = configureStore(initialState); -registerScreens(store, Provider); -const lazyLoadExternalModules = () => { - const StatusBarSizeIOS = require('react-native-status-bar-size'); - const initializeErrorHandling = require('app/utils/error_handling').initializeErrorHandling; - return { - StatusBarSizeIOS, - initializeErrorHandling, - }; -}; - -const lazyLoadAnalytics = () => { - const initAnalytics = require('app/utils/segment').init; - - return { - initAnalytics, - }; -}; - -const initializeModules = () => { - const { - StatusBarSizeIOS, - initializeErrorHandling, - } = lazyLoadExternalModules(); - const { - config, - } = store.getState().entities.general; - const window = Dimensions.get('window'); - - initializeErrorHandling(); - - EventEmitter.on(NavigationTypes.NAVIGATION_RESET, handleLogout); - EventEmitter.on(NavigationTypes.RESTART_APP, restartApp); - EventEmitter.on(General.SERVER_VERSION_CHANGED, handleServerVersionChanged); - EventEmitter.on(General.CONFIG_CHANGED, handleConfigChanged); - EventEmitter.on(General.SWITCH_TO_DEFAULT_CHANNEL, handleSwitchToDefaultChannel); - Dimensions.addEventListener('change', handleOrientationChange); - mattermostManaged.addEventListener('managedConfigDidChange', () => { - handleManagedConfig(true); +const init = async () => { + const credentials = await getAppCredentials(); + pushNotificationsUtils.configure(store); + globalEventHandler.configure({ + store, + launchEntry, }); - if (config) { - configureAnalytics(config); + registerScreens(store, Provider); + + if (startedSharedExtension) { + ephemeralStore.appStarted = true; } - handleOrientationChange({window}); - - if (Platform.OS === 'ios') { - StatusBarSizeIOS.addEventListener('willChange', handleStatusBarHeightChange); - - StatusBarManager.getHeight( - (data) => { - handleStatusBarHeightChange(data.height); - } - ); + if (!ephemeralStore.appStarted) { + launchEntryAndAuthenticateIfNeeded(credentials); } }; -const configureAnalytics = (config) => { - const { - initAnalytics, - } = lazyLoadAnalytics(); - if (!__DEV__ && config && config.DiagnosticsEnabled === 'true' && config.DiagnosticId && LocalConfig.SegmentApiKey) { - initAnalytics(config); - } else { - global.analytics = null; - } -}; - -const resetBadgeAndVersion = () => { - Client4.serverVersion = ''; - Client4.setUserId(''); - PushNotifications.setApplicationIconBadgeNumber(0); - PushNotifications.cancelAllLocalNotifications(); - store.dispatch(setServerVersion('')); -}; - -const handleLogout = () => { - Client4.setCSRF(null); - store.dispatch(closeWebSocket(false)); - - app.setAppStarted(true); - app.clearNativeCache(); - deleteFileCache(); - resetBadgeAndVersion(); - launchSelectServer(); -}; - -const restartApp = async () => { - Navigation.dismissModal({animationType: 'none'}); - - try { - const window = Dimensions.get('window'); - - handleOrientationChange({window}); - await store.dispatch(loadConfigAndLicense()); - await store.dispatch(loadMe()); - - if (Platform.OS === 'ios') { - StatusBarManager.getHeight( - (data) => { - handleStatusBarHeightChange(data.height); - } - ); - } - } catch (e) { - console.warn('Failed to load initial data while restarting', e); // eslint-disable-line no-console - } - - launchChannel(); -}; - -const handleServerVersionChanged = async (serverVersion) => { - const {dispatch, getState} = store; - const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0]; - const translations = app.getTranslations(); - const state = getState(); - - if (serverVersion) { - if (semver.valid(version) && semver.lt(version, LocalConfig.MinServerVersion)) { - Alert.alert( - translations[t('mobile.server_upgrade.title')], - translations[t('mobile.server_upgrade.description')], - [{ - text: translations[t('mobile.server_upgrade.button')], - onPress: handleServerVersionUpgradeNeeded, - }], - {cancelable: false} - ); - } else if (state.entities.users && state.entities.users.currentUserId) { - dispatch(setServerVersion(serverVersion)); - const data = await dispatch(loadConfigAndLicense()); - configureAnalytics(data.config); - } - } -}; - -const handleConfigChanged = (config) => { - configureAnalytics(config); -}; - -const handleServerVersionUpgradeNeeded = async () => { - const {dispatch, getState} = store; - - resetBadgeAndVersion(); - - if (getState().entities.general.credentials.token) { - InteractionManager.runAfterInteractions(() => { - dispatch(logout()); - }); - } -}; - -const handleStatusBarHeightChange = (nextStatusBarHeight) => { - store.dispatch(setStatusBarHeight(nextStatusBarHeight)); -}; - -const handleOrientationChange = (dimensions) => { - const {dispatch} = store; - if (DeviceInfo.isTablet()) { - dispatch(setDeviceAsTablet()); - } - - const {height, width} = dimensions.window; - const orientation = height > width ? 'PORTRAIT' : 'LANDSCAPE'; - - dispatch(setDeviceOrientation(orientation)); - dispatch(setDeviceDimensions(height, width)); -}; - -export const handleManagedConfig = async (eventFromEmmServer = false) => { - if (app.performingEMMAuthentication) { - return true; - } - - const {dispatch, getState} = store; - const state = getState(); - - let authNeeded = false; - let blurApplicationScreen = false; - let jailbreakProtection = false; - let vendor = null; - let serverUrl = null; - let username = null; - - if (LocalConfig.AutoSelectServerUrl) { - dispatch(handleServerUrlChanged(LocalConfig.DefaultServerUrl)); - app.setAllowOtherServers(false); - } - - try { - const config = await avoidNativeBridge( - () => { - return true; - }, - () => { - return Initialization.managedConfig; - }, - () => { - return mattermostManaged.getConfig(); - } - ); - - if (config && Object.keys(config).length) { - app.setEMMEnabled(true); - authNeeded = config.inAppPinCode === 'true'; - blurApplicationScreen = config.blurApplicationScreen === 'true'; - jailbreakProtection = config.jailbreakProtection === 'true'; - vendor = config.vendor || 'Mattermost'; - - if (!state.entities.general.credentials.token) { - serverUrl = config.serverUrl; - username = config.username; - - if (config.allowOtherServers && config.allowOtherServers === 'false') { - app.setAllowOtherServers(false); - } - } - - if (jailbreakProtection) { - const isTrusted = mattermostManaged.isTrustedDevice(); - - if (!isTrusted) { - const translations = app.getTranslations(); - Alert.alert( - translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor), - translations[t('mobile.managed.jailbreak')].replace('{vendor}', vendor), - [{ - text: translations[t('mobile.managed.exit')], - style: 'destructive', - onPress: () => { - mattermostManaged.quitApp(); - }, - }], - {cancelable: false} - ); - return false; - } - } - - if (authNeeded && !eventFromEmmServer) { - const authenticated = await handleAuthentication(vendor); - if (!authenticated) { - return false; - } - } - - if (blurApplicationScreen) { - mattermostManaged.blurAppScreen(true); - } - - if (serverUrl) { - dispatch(handleServerUrlChanged(serverUrl)); - } - - if (username) { - dispatch(handleLoginIdChanged(username)); - } - } - } catch (error) { - return true; - } - - return true; -}; - -const handleAuthentication = async (vendor, prompt = true) => { - app.setPerformingEMMAuthentication(true); - const isSecured = await mattermostManaged.isDeviceSecure(); - const translations = app.getTranslations(); - - if (isSecured) { - try { - mattermostBucket.setPreference('emm', vendor); - if (prompt) { - await mattermostManaged.authenticate({ - reason: translations[t('mobile.managed.secured_by')].replace('{vendor}', vendor), - fallbackToPasscode: true, - suppressEnterPassword: true, - }); - } - } catch (err) { - mattermostManaged.quitApp(); - return false; - } - } else { - await showNotSecuredAlert(vendor, translations); - - mattermostManaged.quitApp(); - return false; - } - - app.setPerformingEMMAuthentication(false); - return true; -}; - -function showNotSecuredAlert(vendor, translations) { - return new Promise((resolve) => { - const options = []; - - if (Platform.OS === 'android') { - options.push({ - text: translations[t('mobile.managed.settings')], - onPress: () => { - mattermostManaged.goToSecuritySettings(); - }, - }); - } - - options.push({ - text: translations[t('mobile.managed.exit')], - onPress: resolve, - style: 'cancel', - }); - - Alert.alert( - translations[t('mobile.managed.blocked_by')].replace('{vendor}', vendor), - Platform.OS === 'ios' ? translations[t('mobile.managed.not_secured.ios')] : translations[t('mobile.managed.not_secured.android')], - options, - {cancelable: false, onDismiss: resolve}, - ); - }); -} - -const handleSwitchToDefaultChannel = (teamId) => { - store.dispatch(selectDefaultChannel(teamId)); -}; - const launchSelectServer = () => { Navigation.startSingleScreenApp({ screen: { @@ -396,7 +54,7 @@ const launchSelectServer = () => { }, }, passProps: { - allowOtherServers: app.allowOtherServers, + allowOtherServers: emmProvider.allowOtherServers, }, appStyle: { orientation: 'auto', @@ -415,9 +73,9 @@ const launchChannel = (skipMetrics = false) => { statusBarHideWithNavBar: false, screenBackgroundColor: 'transparent', }, - passProps: { - skipMetrics, - }, + }, + passProps: { + skipMetrics, }, appStyle: { orientation: 'auto', @@ -426,108 +84,52 @@ const launchChannel = (skipMetrics = false) => { }); }; -const handleAppStateChange = (appState) => { - const isActive = appState === 'active'; - const isBackground = appState === 'background'; - - store.dispatch(setAppState(isActive)); - - if (isActive && app.previousAppState === 'background') { - handleAppActive(); - } else if (isBackground) { - handleAppInActive(); - } - - app.previousAppState = appState; -}; - -const handleAppActive = async () => { - // This handles when the app was started in the background - // cause of an iOS push notification reply - if (Platform.OS === 'ios' && app.shouldRelaunchWhenActive) { - app.launchApp(); - app.setShouldRelaunchWhenActive(false); - } - - // if the app is being controlled by an EMM provider - if (app.emmEnabled) { - const config = await mattermostManaged.getConfig(); - const authNeeded = config.inAppPinCode === 'true'; - const authExpired = (Date.now() - app.inBackgroundSince) >= PROMPT_IN_APP_PIN_CODE_AFTER; - - // Once the app becomes active we check if the device needs to have a passcode set - if (authNeeded) { - const prompt = app.inBackgroundSince && authExpired; // if more than 5 minutes have passed prompt for passcode - await handleAuthentication(config.vendor, prompt); - } - } - - app.setInBackgroundSince(null); -}; - -const handleAppInActive = () => { - const {dispatch, getState} = store; - const theme = getTheme(getState()); - - // When the app is sent to the background we set the time when that happens - // and perform a data clean up to improve on performance - app.setInBackgroundSince(Date.now()); - app.setStartupThemes( - theme.sidebarHeaderBg, - theme.sidebarHeaderTextColor, - theme.centerChannelBg, - ); - dispatch(startDataCleanup()); -}; - -AppState.addEventListener('change', handleAppStateChange); - -const launchEntry = () => { +const launchEntry = (credentials) => { telemetry.start([ 'start:select_server_screen', 'start:channel_screen', ]); - Navigation.startSingleScreenApp({ - screen: { - screen: 'Entry', - navigatorStyle: { - navBarHidden: true, - statusBarHidden: false, - statusBarHideWithNavBar: false, - }, - }, - passProps: { - initializeModules, - }, - appStyle: { - orientation: 'auto', - }, - animationType: 'fade', - }); + if (credentials) { + store.dispatch(loadMe()); + launchChannel(); + } else { + launchSelectServer(); + } telemetry.startSinceLaunch(['start:splash_screen']); + ephemeralStore.appStarted = true; }; -configurePushNotifications(); -const startedSharedExtension = Platform.OS === 'android' && MattermostShare.isOpened; +const launchEntryAndAuthenticateIfNeeded = async (credentials) => { + await emmProvider.handleManagedConfig(store); + launchEntry(credentials); -if (startedSharedExtension) { - // Hold on launching Entry screen - app.setAppStarted(true); -} + if (emmProvider.enabled) { + if (emmProvider.jailbreakProtection) { + emmProvider.checkIfDeviceIsTrusted(); + } -if (!app.appStarted) { - launchEntry(); -} + if (emmProvider.inAppPinCode) { + await emmProvider.handleAuthentication(store); + } + } -new NativeEventsReceiver().appLaunched(() => { + Linking.getInitialURL().then((url) => { + store.dispatch(setDeepLinkURL(url)); + }); +}; + +new NativeEventsReceiver().appLaunched(async () => { + const credentials = await getAppCredentials(); if (startedSharedExtension) { - app.setAppStarted(false); - launchEntry(); - } else if (app.token && app.url) { + ephemeralStore.appStarted = true; + await launchEntryAndAuthenticateIfNeeded(credentials); + } else if (credentials) { launchChannel(true); } else { launchSelectServer(); } }); + +init(); diff --git a/app/mattermost_managed/mattermost-managed.android.js b/app/mattermost_managed/mattermost-managed.android.js index 3cdd00095..778396a9f 100644 --- a/app/mattermost_managed/mattermost-managed.android.js +++ b/app/mattermost_managed/mattermost-managed.android.js @@ -36,6 +36,8 @@ export default { }, authenticate: LocalAuth.auth, blurAppScreen: MattermostManaged.blurAppScreen, + appGroupIdentifier: null, + hasSafeAreaInsets: null, isRunningInSplitView: MattermostManaged.isRunningInSplitView, getConfig: async () => { try { diff --git a/app/mattermost_managed/mattermost-managed.ios.js b/app/mattermost_managed/mattermost-managed.ios.js index 9c2415da2..8073f9a26 100644 --- a/app/mattermost_managed/mattermost-managed.ios.js +++ b/app/mattermost_managed/mattermost-managed.ios.js @@ -51,6 +51,7 @@ export default { getCachedConfig: () => { return cachedConfig; }, + appGroupIdentifier: MattermostManaged.appGroupIdentifier, hasSafeAreaInsets: MattermostManaged.hasSafeAreaInsets, isRunningInSplitView: MattermostManaged.isRunningInSplitView, isDeviceSecure: async () => { diff --git a/app/screens/channel/channel_base.js b/app/screens/channel/channel_base.js index 7dd030714..f370f09f7 100644 --- a/app/screens/channel/channel_base.js +++ b/app/screens/channel/channel_base.js @@ -14,8 +14,6 @@ import { import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; -import {app} from 'app/mattermost'; - import EmptyToolbar from 'app/components/start/empty_toolbar'; import InteractiveDialogController from 'app/components/interactive_dialog_controller'; import MainSidebar from 'app/components/sidebars/main'; @@ -24,6 +22,7 @@ import SettingsSidebar from 'app/components/sidebars/settings'; import {preventDoubleTap} from 'app/utils/tap'; import PushNotifications from 'app/push_notifications'; +import ephemeralStore from 'app/store/ephemeral_store'; import tracker from 'app/utils/time_tracker'; import telemetry from 'app/telemetry'; @@ -232,8 +231,8 @@ export default class ChannelBase extends PureComponent { loadChannelsIfNecessary(teamId).then(() => { loadProfilesAndTeamMembersForDMSidebar(teamId); - if (app.startAppFromPushNotification) { - app.setStartAppFromPushNotification(false); + if (ephemeralStore.appStartedFromPushNotification) { + ephemeralStore.appStartedFromPushNotification = false; } else { selectInitialChannel(teamId); } diff --git a/app/screens/entry/entry.js b/app/screens/entry/entry.js deleted file mode 100644 index 500023a61..000000000 --- a/app/screens/entry/entry.js +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import React, {PureComponent} from 'react'; -import PropTypes from 'prop-types'; -import { - View, - AppState, - Platform, -} from 'react-native'; - -import DeviceInfo from 'react-native-device-info'; - -import {setSystemEmojis} from 'mattermost-redux/actions/emojis'; -import {Client4} from 'mattermost-redux/client'; -import EventEmitter from 'mattermost-redux/utils/event_emitter'; - -import { - app, - store, -} from 'app/mattermost'; -import {ViewTypes} from 'app/constants'; -import PushNotifications from 'app/push_notifications'; -import {stripTrailingSlashes} from 'app/utils/url'; -import {wrapWithContextProvider} from 'app/utils/wrap_context_provider'; - -import ChannelLoader from 'app/components/channel_loader'; -import EmptyToolbar from 'app/components/start/empty_toolbar'; -import Loading from 'app/components/loading'; -import SafeAreaView from 'app/components/safe_area_view'; -import StatusBar from 'app/components/status_bar'; - -const lazyLoadSelectServer = () => { - return require('app/screens/select_server').default; -}; - -const lazyLoadChannel = () => { - return require('app/screens/channel').default; -}; - -const lazyLoadPushNotifications = () => { - return require('app/utils/push_notifications').configurePushNotifications; -}; - -const lazyLoadReplyPushNotifications = () => { - return require('app/utils/push_notifications').onPushNotificationReply; -}; - -/** - * Entry Component: - * With very little overhead navigate to - * - Login or - * - Channel Component - * - * The idea is to render something to the user as soon as possible - */ -export default class Entry extends PureComponent { - static propTypes = { - theme: PropTypes.object, - navigator: PropTypes.object, - isLandscape: PropTypes.bool, - enableTimezone: PropTypes.bool, - deviceTimezone: PropTypes.string, - initializeModules: PropTypes.func, - actions: PropTypes.shape({ - autoUpdateTimezone: PropTypes.func.isRequired, - setDeviceToken: PropTypes.func.isRequired, - }).isRequired, - }; - - constructor(props) { - super(props); - - this.state = { - launchLogin: false, - launchChannel: false, - }; - - this.unsubscribeFromStore = null; - } - - componentDidMount() { - Client4.setUserAgent(DeviceInfo.getUserAgent()); - - if (store.getState().views.root.hydrationComplete) { - this.handleHydrationComplete(); - } else { - this.unsubscribeFromStore = store.subscribe(this.listenForHydration); - } - - EventEmitter.on(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin); - EventEmitter.on(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel); - } - - componentWillUnmount() { - EventEmitter.off(ViewTypes.LAUNCH_LOGIN, this.handleLaunchLogin); - EventEmitter.off(ViewTypes.LAUNCH_CHANNEL, this.handleLaunchChannel); - } - - handleLaunchLogin = (initializeModules) => { - this.setState({launchLogin: true}); - - if (initializeModules) { - this.props.initializeModules(); - } - }; - - handleLaunchChannel = (initializeModules) => { - this.setState({launchChannel: true}); - - if (initializeModules) { - this.props.initializeModules(); - } - }; - - listenForHydration = () => { - const {getState} = store; - const state = getState(); - - if (!app.isNotificationsConfigured) { - this.configurePushNotifications(); - } - - if (state.views.root.hydrationComplete) { - this.handleHydrationComplete(); - } - }; - - handleHydrationComplete = () => { - if (this.unsubscribeFromStore) { - this.unsubscribeFromStore(); - this.unsubscribeFromStore = null; - } - - this.autoUpdateTimezone(); - this.setAppCredentials(); - this.setStartupThemes(); - this.handleNotification(); - this.loadSystemEmojis(); - - if (Platform.OS === 'android') { - this.launchForAndroid(); - } else { - this.launchForiOS(); - } - }; - - autoUpdateTimezone = () => { - const { - actions: { - autoUpdateTimezone, - }, - enableTimezone, - deviceTimezone, - } = this.props; - - if (enableTimezone) { - autoUpdateTimezone(deviceTimezone); - } - }; - - configurePushNotifications = () => { - const configureNotifications = lazyLoadPushNotifications(); - configureNotifications(); - }; - - setAppCredentials = () => { - const { - actions: { - setDeviceToken, - }, - } = this.props; - const {getState} = store; - const state = getState(); - - const {credentials} = state.entities.general; - const {currentUserId} = state.entities.users; - - if (app.deviceToken) { - setDeviceToken(app.deviceToken); - } - - if (credentials.token && credentials.url) { - Client4.setToken(credentials.token); - Client4.setUrl(stripTrailingSlashes(credentials.url)); - } else if (app.waitForRehydration) { - app.waitForRehydration = false; - } - - if (currentUserId) { - Client4.setUserId(currentUserId); - } - - app.setAppCredentials(app.deviceToken, currentUserId, credentials.token, credentials.url); - }; - - setStartupThemes = () => { - const {theme} = this.props; - if (app.toolbarBackground === theme.sidebarHeaderBg) { - return; - } - - app.setStartupThemes( - theme.sidebarHeaderBg, - theme.sidebarHeaderTextColor, - theme.centerChannelBg - ); - }; - - handleNotification = async () => { - const notification = PushNotifications.getNotification(); - - // If notification exists, it means that the app was started through a reply - // and the app was not sitting in the background nor opened - if (notification || app.replyNotificationData) { - const onPushNotificationReply = lazyLoadReplyPushNotifications(); - const notificationData = notification || app.replyNotificationData; - const {data, text, badge, completed} = notificationData; - - // if the notification has a completed property it means that we are replying to a notification - if (completed) { - onPushNotificationReply(data, text, badge, completed); - } - PushNotifications.resetNotification(); - } - }; - - launchForAndroid = () => { - app.launchApp(); - }; - - launchForiOS = () => { - const appNotActive = AppState.currentState !== 'active'; - - if (appNotActive) { - // for iOS replying from push notification starts the app in the background - app.setShouldRelaunchWhenActive(true); - } else { - app.launchApp(); - } - }; - - loadSystemEmojis = () => { - const EmojiIndicesByAlias = require('app/utils/emojis').EmojiIndicesByAlias; - setSystemEmojis(EmojiIndicesByAlias); - }; - - renderLogin = () => { - const SelectServer = lazyLoadSelectServer(); - const props = { - allowOtherServers: app.allowOtherServers, - navigator: this.props.navigator, - }; - - return wrapWithContextProvider(SelectServer)(props); - }; - - renderChannel = () => { - const ChannelScreen = lazyLoadChannel(); - const props = { - navigator: this.props.navigator, - }; - - return wrapWithContextProvider(ChannelScreen, false)(props); - }; - - render() { - const { - navigator, - isLandscape, - } = this.props; - - if (this.state.launchLogin) { - return this.renderLogin(); - } - - if (this.state.launchChannel) { - return this.renderChannel(); - } - - let toolbar = null; - let loading = null; - const backgroundColor = app.appBackground ? app.appBackground : '#ffff'; - if (app.token && app.toolbarBackground) { - const toolbarTheme = { - sidebarHeaderBg: app.toolbarBackground, - sidebarHeaderTextColor: app.toolbarTextColor, - }; - - toolbar = ( - - - - - ); - - loading = ( - - ); - } else { - loading = ; - } - - return ( - - {toolbar} - {loading} - - ); - } -} diff --git a/app/screens/entry/index.js b/app/screens/entry/index.js deleted file mode 100644 index 22bebdae8..000000000 --- a/app/screens/entry/index.js +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. -import {bindActionCreators} from 'redux'; -import {connect} from 'react-redux'; - -import {setDeviceToken} from 'mattermost-redux/actions/general'; -import {autoUpdateTimezone} from 'mattermost-redux/actions/timezone'; -import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; -import {isTimezoneEnabled} from 'mattermost-redux/selectors/entities/timezone'; - -import {isLandscape} from 'app/selectors/device'; -import {getDeviceTimezone} from 'app/utils/timezone'; - -const lazyLoadEntry = () => { - return require('./entry').default; -}; - -function mapStateToProps(state) { - const enableTimezone = isTimezoneEnabled(state); - const deviceTimezone = getDeviceTimezone(); - - return { - theme: getTheme(state), - isLandscape: isLandscape(state), - enableTimezone, - deviceTimezone, - }; -} - -function mapDispatchToProps(dispatch) { - return { - actions: bindActionCreators({ - autoUpdateTimezone, - setDeviceToken, - }, dispatch), - }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(lazyLoadEntry()); diff --git a/app/screens/index.js b/app/screens/index.js index b2cfa0072..fa83753fb 100644 --- a/app/screens/index.js +++ b/app/screens/index.js @@ -5,7 +5,6 @@ import {Navigation} from 'react-native-navigation'; import {gestureHandlerRootHOC} from 'react-native-gesture-handler'; import Channel from 'app/screens/channel'; -import Entry from 'app/screens/entry'; import SelectServer from 'app/screens/select_server'; import {wrapWithContextProvider} from 'app/utils/wrap_context_provider'; @@ -26,7 +25,6 @@ export function registerScreens(store, Provider) { Navigation.registerComponent('EditChannel', () => wrapWithContextProvider(require('app/screens/edit_channel').default), store, Provider); Navigation.registerComponent('EditPost', () => wrapWithContextProvider(require('app/screens/edit_post').default), store, Provider); Navigation.registerComponent('EditProfile', () => wrapWithContextProvider(require('app/screens/edit_profile').default), store, Provider); - Navigation.registerComponent('Entry', () => Entry, store, Provider); Navigation.registerComponent('ExpandedAnnouncementBanner', () => wrapWithContextProvider(require('app/screens/expanded_announcement_banner').default), store, Provider); Navigation.registerComponent('FlaggedPosts', () => wrapWithContextProvider(require('app/screens/flagged_posts').default), store, Provider); Navigation.registerComponent('ForgotPassword', () => wrapWithContextProvider(require('app/screens/forgot_password').default), store, Provider); diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index e10e1b031..79d291ac3 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -25,7 +25,7 @@ import {Client4} from 'mattermost-redux/client'; import ErrorText from 'app/components/error_text'; import FormattedText from 'app/components/formatted_text'; -import fetchConfig from 'app/fetch_preconfig'; +import fetchConfig from 'app/init/fetch'; import mattermostBucket from 'app/mattermost_bucket'; import {GlobalStyles} from 'app/styles'; import {checkUpgradeType, isUpgradeAvailable} from 'app/utils/client_upgrade'; @@ -67,6 +67,16 @@ export default class SelectServer extends PureComponent { intl: intlShape.isRequired, }; + static getDerivedStateFromProps(props, state) { + if (props.serverUrl && !state.url) { + return { + url: props.serverUrl, + }; + } + + return null; + } + constructor(props) { super(props); @@ -99,19 +109,19 @@ export default class SelectServer extends PureComponent { telemetry.save(); } - componentWillUpdate(nextProps, nextState) { - if (nextState.connected && nextProps.hasConfigAndLicense && !(this.state.connected && this.props.hasConfigAndLicense)) { + componentDidUpdate(prevProps, prevState) { + if (this.state.connected && this.props.hasConfigAndLicense && !(prevState.connected && prevProps.hasConfigAndLicense)) { if (LocalConfig.EnableMobileClientUpgrade) { this.props.actions.setLastUpgradeCheck(); - const {currentVersion, minVersion, latestVersion} = nextProps; + const {currentVersion, minVersion, latestVersion} = prevProps; const upgradeType = checkUpgradeType(currentVersion, minVersion, latestVersion); if (isUpgradeAvailable(upgradeType)) { this.handleShowClientUpgrade(upgradeType); } else { - this.handleLoginOptions(nextProps); + this.handleLoginOptions(prevProps); } } else { - this.handleLoginOptions(nextProps); + this.handleLoginOptions(prevProps); } } } diff --git a/app/store/ephemeral_store.js b/app/store/ephemeral_store.js new file mode 100644 index 000000000..dad1dac11 --- /dev/null +++ b/app/store/ephemeral_store.js @@ -0,0 +1,12 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +class EphemeralStore { + constructor() { + this.appStarted = false; + this.appStartedFromPushNotification = false; + this.deviceToken = null; + } +} + +export default new EphemeralStore(); diff --git a/app/store/index.js b/app/store/index.js index 46743abd2..be1155865 100644 --- a/app/store/index.js +++ b/app/store/index.js @@ -248,7 +248,6 @@ export default function configureAppStore(initialState) { type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: { url: state.entities.general.credentials.url, - token: state.entities.general.credentials.token, }, }, { diff --git a/app/utils/error_handling.js b/app/utils/error_handling.js index f6597f935..64fa5a59d 100644 --- a/app/utils/error_handling.js +++ b/app/utils/error_handling.js @@ -14,6 +14,8 @@ import {logError} from 'mattermost-redux/actions/errors'; import {close as closeWebSocket} from 'mattermost-redux/actions/websocket'; import {purgeOfflineStore} from 'app/actions/views/root'; +import {DEFAULT_LOCALE, getTranslations} from 'app/i18n'; +import {t} from 'app/utils/i18n'; import { captureException, captureJSException, @@ -21,54 +23,55 @@ import { LOGGER_NATIVE, } from 'app/utils/sentry'; -import {app, store} from 'app/mattermost'; - -import {t} from 'app/utils/i18n'; - -const errorHandler = (e, isFatal) => { - if (__DEV__ && !e && !isFatal) { - // react-native-exception-handler redirects console.error to call this, and React calls - // console.error without an exception when prop type validation fails, so this ends up - // being called with no arguments when the error handler is enabled in dev mode. - return; +class JavascriptAndNativeErrorHandler { + initializeErrorHandling = (store) => { + this.store = store; + initializeSentry(); + setJSExceptionHandler(this.errorHandler, false); + setNativeExceptionHandler(this.nativeErrorHandler, false); } - console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console - captureJSException(e, isFatal, store); + nativeErrorHandler = (e) => { + console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console + captureException(e, LOGGER_NATIVE, this.store); + }; - const {dispatch} = store; + errorHandler = (e, isFatal) => { + if (__DEV__ && !e && !isFatal) { + // react-native-exception-handler redirects console.error to call this, and React calls + // console.error without an exception when prop type validation fails, so this ends up + // being called with no arguments when the error handler is enabled in dev mode. + return; + } - dispatch(closeWebSocket()); + console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console + captureJSException(e, isFatal, this.store); - if (Client4.getUrl()) { - dispatch(logError(e)); - } + const {dispatch} = this.store; - if (isFatal && e instanceof Error) { - const translations = app.getTranslations(); + dispatch(closeWebSocket()); - Alert.alert( - translations[t('mobile.error_handler.title')], - translations[t('mobile.error_handler.description')], - [{ - text: translations[t('mobile.error_handler.button')], - onPress: () => { - // purge the store - dispatch(purgeOfflineStore()); - }, - }], - {cancelable: false} - ); - } -}; + if (Client4.getUrl()) { + dispatch(logError(e)); + } -const nativeErrorHandler = (e) => { - console.warn('Handling native error ' + JSON.stringify(e)); // eslint-disable-line no-console - captureException(e, LOGGER_NATIVE, store); -}; + if (isFatal && e instanceof Error) { + const translations = getTranslations(DEFAULT_LOCALE); -export function initializeErrorHandling() { - initializeSentry(); - setJSExceptionHandler(errorHandler, false); - setNativeExceptionHandler(nativeErrorHandler, false); + Alert.alert( + translations[t('mobile.error_handler.title')], + translations[t('mobile.error_handler.description')], + [{ + text: translations[t('mobile.error_handler.button')], + onPress: () => { + // purge the store + dispatch(purgeOfflineStore()); + }, + }], + {cancelable: false} + ); + } + }; } + +export default new JavascriptAndNativeErrorHandler(); diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js index a26781e90..7418f1046 100644 --- a/app/utils/push_notifications.js +++ b/app/utils/push_notifications.js @@ -8,7 +8,6 @@ import {setDeviceToken} from 'mattermost-redux/actions/general'; import {getPosts} from 'mattermost-redux/actions/posts'; import {Client4} from 'mattermost-redux/client'; import {General} from 'mattermost-redux/constants'; -import {getCurrentUser} from 'mattermost-redux/selectors/entities/users'; import EventEmitter from 'mattermost-redux/utils/event_emitter'; import {markChannelViewedAndRead, retryGetPostsAction} from 'app/actions/views/channel'; @@ -17,169 +16,179 @@ import { loadFromPushNotification, } from 'app/actions/views/root'; import {ViewTypes} from 'app/constants'; -import {DEFAULT_LOCALE, getLocalizedMessage} from 'app/i18n'; -import {t} from 'app/utils/i18n'; -import { - app, - store, -} from 'app/mattermost'; +import {getLocalizedMessage} from 'app/i18n'; +import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials'; import PushNotifications from 'app/push_notifications'; +import {getCurrentLocale} from 'app/selectors/i18n'; +import ephemeralStore from 'app/store/ephemeral_store'; +import {t} from 'app/utils/i18n'; -const onRegisterDevice = (data) => { - app.setIsNotificationsConfigured(true); - const state = store.getState(); - - let prefix; - if (Platform.OS === 'ios') { - prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE; - if (DeviceInfo.getBundleId().includes('rnbeta')) { - prefix = `${prefix}beta`; - } - } else { - prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE; +class PushNotificationUtils { + constructor() { + this.configured = false; + this.replyNotificationData = null; } - const token = `${prefix}:${data.token}`; - if (state.views.root.hydrationComplete) { - app.setDeviceToken(token); - store.dispatch(setDeviceToken(token)); - } else { - app.setDeviceToken(token); - } -}; + configure = (store) => { + this.store = store; -const loadFromNotification = async (notification) => { - await store.dispatch(loadFromPushNotification(notification, true)); - if (!app.startAppFromPushNotification) { - EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED); - PushNotifications.resetNotification(); - } -}; - -const onPushNotification = async (deviceNotification) => { - const {dispatch, getState} = store; - let unsubscribeFromStore = null; - let stopLoadingNotification = false; - - // mark the app as started as soon as possible - if (!app.appStarted) { - app.setStartAppFromPushNotification(true); - } - - const {data, foreground, message, userInfo, userInteraction} = deviceNotification; - const notification = { - data, - message, + PushNotifications.configure({ + onRegister: this.onRegisterDevice, + onNotification: this.onPushNotification, + onReply: this.onPushNotificationReply, + popInitialNotification: true, + requestPermissions: true, + }); }; - if (userInfo) { - notification.localNotification = userInfo.localNotification; - } + loadFromNotification = async (notification) => { + await this.store.dispatch(loadFromPushNotification(notification, true)); - if (data.type === 'clear') { - dispatch(markChannelViewedAndRead(data.channel_id, null, false)); - } else { - // get the posts for the channel as soon as possible - retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); - - if (foreground) { - EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification); - } else if (userInteraction && !notification.localNotification) { - EventEmitter.emit('close_channel_drawer'); - if (getState().views.root.hydrationComplete) { - setTimeout(() => { - loadFromNotification(notification); - }, 0); - } else { - const waitForHydration = () => { - if (getState().views.root.hydrationComplete && !stopLoadingNotification) { - stopLoadingNotification = true; - unsubscribeFromStore(); - loadFromNotification(notification); - } - }; - - unsubscribeFromStore = store.subscribe(waitForHydration); - } + if (!ephemeralStore.appStartedFromPushNotification) { + EventEmitter.emit(ViewTypes.NOTIFICATION_TAPPED); + PushNotifications.resetNotification(); } - } -}; + }; -export const onPushNotificationReply = async (data, text, badge, completed) => { - const {dispatch, getState} = store; - const state = getState(); - const reduxCurrentUser = getCurrentUser(state); - const reduxCredentialsUrl = state.entities.general.credentials.url; - const reduxCredentialsToken = state.entities.general.credentials.token; + onPushNotification = async (deviceNotification) => { + const {dispatch, getState} = this.store; + let unsubscribeFromStore = null; + let stopLoadingNotification = false; - const currentUserId = reduxCurrentUser ? reduxCurrentUser.id : app.currentUserId; - const url = reduxCredentialsUrl || app.url; - const token = reduxCredentialsToken || app.token; + // mark the app as started as soon as possible + if (!ephemeralStore.appStarted) { + ephemeralStore.appStartedFromPushNotification = true; + } - if (currentUserId) { - // one thing to note is that for android it will reply to the last post in the stack - const rootId = data.root_id || data.post_id; - const post = { - user_id: currentUserId, - channel_id: data.channel_id, - root_id: rootId, - parent_id: rootId, - message: text, + const {data, foreground, message, userInfo, userInteraction} = deviceNotification; + const notification = { + data, + message, }; - if (!Client4.getUrl()) { - // Make sure the Client has the server url set - Client4.setUrl(url); + if (userInfo) { + notification.localNotification = userInfo.localNotification; } - if (!Client4.getToken()) { - // Make sure the Client has the server token set - Client4.setToken(token); - } + if (data.type === 'clear') { + dispatch(markChannelViewedAndRead(data.channel_id, null, false)); + } else { + // get the posts for the channel as soon as possible + retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); - retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); - const result = await dispatch(createPostForNotificationReply(post)); - if (result.error) { - const locale = reduxCurrentUser ? reduxCurrentUser.locale : DEFAULT_LOCALE; - PushNotifications.localNotification({ - message: getLocalizedMessage(locale, t('mobile.reply_post.failed')), - userInfo: { - localNotification: true, - localTest: true, - }, - }); - console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console + if (foreground) { + EventEmitter.emit(ViewTypes.NOTIFICATION_IN_APP, notification); + } else if (userInteraction && !notification.localNotification) { + EventEmitter.emit('close_channel_drawer'); + if (getState().views.root.hydrationComplete) { //TODO: Replace when realm is ready + setTimeout(() => { + this.loadFromNotification(notification); + }, 0); + } else { + const waitForHydration = () => { + if (getState().views.root.hydrationComplete && !stopLoadingNotification) { + stopLoadingNotification = true; + unsubscribeFromStore(); + this.loadFromNotification(notification); + } + }; + + unsubscribeFromStore = this.store.subscribe(waitForHydration); + } + } + } + }; + + onPushNotificationReply = async (data, text, badge, completed) => { + const {dispatch, getState} = this.store; + const state = getState(); + const credentials = await getAppCredentials(); // TODO Change to handle multiple servers + const url = await getCurrentServerUrl(); // TODO Change to handle multiple servers + const token = credentials.password; + const usernameParsed = credentials.username.split(','); + const [, currentUserId] = usernameParsed; + + if (currentUserId) { + // one thing to note is that for android it will reply to the last post in the stack + const rootId = data.root_id || data.post_id; + const post = { + user_id: currentUserId, + channel_id: data.channel_id, + root_id: rootId, + parent_id: rootId, + message: text, + }; + + if (!Client4.getUrl()) { + // Make sure the Client has the server url set + Client4.setUrl(url); + } + + if (!Client4.getToken()) { + // Make sure the Client has the server token set + Client4.setToken(token); + } + + retryGetPostsAction(getPosts(data.channel_id), dispatch, getState); + const result = await dispatch(createPostForNotificationReply(post)); + if (result.error) { + const locale = getCurrentLocale(state); + PushNotifications.localNotification({ + message: getLocalizedMessage(locale, t('mobile.reply_post.failed')), + userInfo: { + localNotification: true, + localTest: true, + }, + }); + console.warn('Failed to send reply to push notification', result.error); // eslint-disable-line no-console + completed(); + return; + } + + if (badge >= 0) { + PushNotifications.setApplicationIconBadgeNumber(badge); + } + + dispatch(markChannelViewedAndRead(data.channel_id)); + this.replyNotificationData = null; completed(); - return; + } else { + this.replyNotificationData = { + data, + text, + badge, + completed, + }; + } + }; + + onRegisterDevice = (data) => { + const {dispatch, getState} = this.store; + let unsubscribeFromStore = null; + + let prefix; + if (Platform.OS === 'ios') { + prefix = General.PUSH_NOTIFY_APPLE_REACT_NATIVE; + if (DeviceInfo.getBundleId().includes('rnbeta')) { + prefix = `${prefix}beta`; + } + } else { + prefix = General.PUSH_NOTIFY_ANDROID_REACT_NATIVE; } - if (badge >= 0) { - PushNotifications.setApplicationIconBadgeNumber(badge); - } + ephemeralStore.deviceToken = `${prefix}:${data.token}`; - dispatch(markChannelViewedAndRead(data.channel_id)); - app.setReplyNotificationData(null); - completed(); - } else { - app.setReplyNotificationData({ - data, - text, - badge, - completed, - }); + // TODO: Remove when realm is ready + const waitForHydration = () => { + if (getState().views.root.hydrationComplete && !this.configured) { + this.configured = true; + dispatch(setDeviceToken(ephemeralStore.deviceToken)); + unsubscribeFromStore(); + } + }; + + unsubscribeFromStore = this.store.subscribe(waitForHydration); } -}; +} -export const configurePushNotifications = () => { - PushNotifications.configure({ - onRegister: onRegisterDevice, - onNotification: onPushNotification, - onReply: onPushNotificationReply, - popInitialNotification: true, - requestPermissions: true, - }); - - if (app) { - app.setIsNotificationsConfigured(true); - } -}; +export default new PushNotificationUtils(); diff --git a/app/utils/url.js b/app/utils/url.js index 296a4e62c..e0feebc8f 100644 --- a/app/utils/url.js +++ b/app/utils/url.js @@ -103,9 +103,10 @@ export function matchDeepLink(url, serverURL, siteURL) { return null; } - const linkRoot = `(?:${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`; + const linkRoot = `(?:${escapeRegex('mattermost:/')}|${escapeRegex(serverURL)}|${escapeRegex(siteURL)})?`; let match = new RegExp('^' + linkRoot + '\\/([^\\/]+)\\/channels\\/(\\S+)').exec(url); + if (match) { return {type: DeepLinkTypes.CHANNEL, teamName: match[1], channelName: match[2]}; } diff --git a/index.js b/index.js index 54e26e7aa..84ce150c3 100644 --- a/index.js +++ b/index.js @@ -2,7 +2,7 @@ // See LICENSE.txt for license information. import 'react-native/Libraries/Core/InitializeCore'; -import {AppRegistry, DeviceEventEmitter, Platform, YellowBox} from 'react-native'; +import {AppRegistry, DeviceEventEmitter, Platform, Text, YellowBox} from 'react-native'; import 'react-native-gesture-handler'; import LocalConfig from 'assets/config'; @@ -10,16 +10,46 @@ import LocalConfig from 'assets/config'; import telemetry from 'app/telemetry'; import 'app/mattermost'; -import ShareExtension from 'share_extension/android'; -YellowBox.ignoreWarnings([ - 'Warning: componentWillMount is deprecated', - 'Warning: componentWillUpdate is deprecated', - 'Warning: componentWillReceiveProps is deprecated', -]); +if (__DEV__) { + YellowBox.ignoreWarnings([ + 'Warning: componentWillMount is deprecated', + 'Warning: componentWillUpdate is deprecated', + 'Warning: componentWillReceiveProps is deprecated', + + // Hide warnings caused by React Native (https://github.com/facebook/react-native/issues/20841) + 'Require cycle: node_modules/react-native/Libraries/Network/fetch.js', + ]); +} + +const setFontFamily = () => { + // Set a global font for Android + const defaultFontFamily = { + style: { + fontFamily: 'Roboto', + }, + }; + const TextRender = Text.render; + const initialDefaultProps = Text.defaultProps; + Text.defaultProps = { + ...initialDefaultProps, + ...defaultFontFamily, + }; + Text.render = function render(props, ...args) { + const oldProps = props; + let newProps = {...props, style: [defaultFontFamily.style, props.style]}; + try { + return Reflect.apply(TextRender, this, [newProps, ...args]); + } finally { + newProps = oldProps; + } + }; +}; if (Platform.OS === 'android') { + const ShareExtension = require('share_extension/android').default; AppRegistry.registerComponent('MattermostShare', () => ShareExtension); + setFontFamily(); if (LocalConfig.TelemetryEnabled) { const metricsSubscription = DeviceEventEmitter.addListener('nativeMetrics', (metrics) => { diff --git a/ios/Mattermost/MattermostManaged.m b/ios/Mattermost/MattermostManaged.m index b2f5c5ce1..a96f94d2f 100644 --- a/ios/Mattermost/MattermostManaged.m +++ b/ios/Mattermost/MattermostManaged.m @@ -68,6 +68,7 @@ RCT_EXPORT_MODULE(); return @{ @"hasSafeAreaInsets": @([self hasSafeAreaInsets]), + @"appGroupIdentifier": APP_GROUP_ID }; } diff --git a/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj index 871083eef..35da9ccc2 100644 --- a/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj +++ b/ios/UploadAttachments/UploadAttachments.xcodeproj/project.pbxproj @@ -7,7 +7,10 @@ objects = { /* Begin PBXBuildFile section */ - 7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* Constants.h */; }; + 7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 7FABE054221387B500D0F595 /* MMMConstants.h */; }; + 7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 7F82907022C295AC0035544F /* MMKeychainManager.m */; }; + 7F82907322C296970035544F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907222C296970035544F /* Security.framework */; }; + 7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7F82907422C296A10035544F /* LocalAuthentication.framework */; }; 7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE04C2213818900D0F595 /* MattermostBucket.m */; }; 7FABE055221387B500D0F595 /* MMMConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE053221387B400D0F595 /* MMMConstants.m */; }; 7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABE057221388D600D0F595 /* UploadSessionManager.swift */; }; @@ -25,13 +28,17 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( - 7F80232C229C91AD0034D6D4 /* Constants.h in CopyFiles */, + 7F80232C229C91AD0034D6D4 /* MMMConstants.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 7F82906F22C295AB0035544F /* MMKeychainManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MMKeychainManager.h; sourceTree = ""; }; + 7F82907022C295AC0035544F /* MMKeychainManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MMKeychainManager.m; sourceTree = ""; }; + 7F82907222C296970035544F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 7F82907422C296A10035544F /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = System/Library/Frameworks/LocalAuthentication.framework; sourceTree = SDKROOT; }; 7FABE03622137F2900D0F595 /* libUploadAttachments.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libUploadAttachments.a; sourceTree = BUILT_PRODUCTS_DIR; }; 7FABE04B2213818900D0F595 /* UploadAttachments-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UploadAttachments-Bridging-Header.h"; sourceTree = ""; }; 7FABE04C2213818900D0F595 /* MattermostBucket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MattermostBucket.m; sourceTree = ""; }; @@ -52,17 +59,29 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 7F82907522C296A10035544F /* LocalAuthentication.framework in Frameworks */, + 7F82907322C296970035544F /* Security.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7F82901A22C28F770035544F /* Frameworks */ = { + isa = PBXGroup; + children = ( + 7F82907422C296A10035544F /* LocalAuthentication.framework */, + 7F82907222C296970035544F /* Security.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; 7FABE02D22137F2900D0F595 = { isa = PBXGroup; children = ( 7FABE03822137F2900D0F595 /* UploadAttachments */, 7FABE03722137F2900D0F595 /* Products */, + 7F82901A22C28F770035544F /* Frameworks */, ); sourceTree = ""; }; @@ -77,6 +96,8 @@ 7FABE03822137F2900D0F595 /* UploadAttachments */ = { isa = PBXGroup; children = ( + 7F82906F22C295AB0035544F /* MMKeychainManager.h */, + 7F82907022C295AC0035544F /* MMKeychainManager.m */, 7FABE05A2213892100D0F595 /* AttachmentArray.swift */, 7FABE0592213892100D0F595 /* AttachmentItem.swift */, 7FABE054221387B500D0F595 /* MMMConstants.h */, @@ -175,6 +196,7 @@ files = ( 7FABE05C2213892200D0F595 /* AttachmentArray.swift in Sources */, 7FABE0FA2214674200D0F595 /* StoreManager.m in Sources */, + 7F82907122C295AC0035544F /* MMKeychainManager.m in Sources */, 7FABE058221388D700D0F595 /* UploadSessionManager.swift in Sources */, 7FABE0F7221466F900D0F595 /* UploadManager.swift in Sources */, 7FABE04E2213818A00D0F595 /* MattermostBucket.m in Sources */, @@ -309,6 +331,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UQ8HT4Q2XM; + HEADER_SEARCH_PATHS = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -331,6 +354,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = UQ8HT4Q2XM; + HEADER_SEARCH_PATHS = ""; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h new file mode 100644 index 000000000..13489b9db --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.h @@ -0,0 +1,3 @@ +@interface MMKeychainManager : NSObject +-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options; +@end diff --git a/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m new file mode 100644 index 000000000..866ce90fb --- /dev/null +++ b/ios/UploadAttachments/UploadAttachments/MMKeychainManager.m @@ -0,0 +1,73 @@ +#import +#import "MMKeychainManager.h" + +#import +#import + +@implementation MMKeychainManager + +// Messages from the comments in + +#pragma mark - Proposed functionality - Helpers + +#define kAuthenticationType @"authenticationType" +#define kAuthenticationTypeBiometrics @"AuthenticationWithBiometrics" + +#define kAccessControlType @"accessControl" +#define kAccessControlUserPresence @"UserPresence" +#define kAccessControlBiometryAny @"BiometryAny" +#define kAccessControlBiometryCurrentSet @"BiometryCurrentSet" +#define kAccessControlDevicePasscode @"DevicePasscode" +#define kAccessControlApplicationPassword @"ApplicationPassword" +#define kAccessControlBiometryAnyOrDevicePasscode @"BiometryAnyOrDevicePasscode" +#define kAccessControlBiometryCurrentSetOrDevicePasscode @"BiometryCurrentSetOrDevicePasscode" + +#define kBiometryTypeTouchID @"TouchID" +#define kBiometryTypeFaceID @"FaceID" + +#define kAuthenticationPromptMessage @"authenticationPrompt" + +#pragma mark - Native access + +-(NSDictionary *)getInternetCredentialsForServer:(NSString *)server withOptions:(NSDictionary *)options +{ + if (server == nil) { + return nil; + } + + NSDictionary *query = @{ + (__bridge NSString *)kSecClass: (__bridge id)(kSecClassInternetPassword), + (__bridge NSString *)kSecAttrServer: server, + (__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue, + (__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue, + (__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne + }; + + // Look up server in the keychain + NSDictionary *found = nil; + CFTypeRef foundTypeRef = NULL; + OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef) query, (CFTypeRef*)&foundTypeRef); + + if (osStatus != noErr && osStatus != errSecItemNotFound) { + return nil; + } + + found = (__bridge NSDictionary*)(foundTypeRef); + if (!found) { + return nil; + } + + // Found + NSString *username = (NSString *) [found objectForKey:(__bridge id)(kSecAttrAccount)]; + NSString *password = [[NSString alloc] initWithData:[found objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding]; + + CFRelease(foundTypeRef); + NSDictionary *result = @{ + @"server": server, + @"username": username, + @"password": password + }; + return result; +} + +@end diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.h b/ios/UploadAttachments/UploadAttachments/StoreManager.h index ce4e4f5eb..23fb17be6 100644 --- a/ios/UploadAttachments/UploadAttachments/StoreManager.h +++ b/ios/UploadAttachments/UploadAttachments/StoreManager.h @@ -1,8 +1,10 @@ #import #import "MattermostBucket.h" +#import "MMKeychainManager.h" @interface StoreManager : NSObject @property MattermostBucket *bucket; +@property MMKeychainManager *keychain; @property (nonatomic, strong) NSDictionary *entities; +(instancetype)shared; diff --git a/ios/UploadAttachments/UploadAttachments/StoreManager.m b/ios/UploadAttachments/UploadAttachments/StoreManager.m index 143f37a5d..bdaddae1b 100644 --- a/ios/UploadAttachments/UploadAttachments/StoreManager.m +++ b/ios/UploadAttachments/UploadAttachments/StoreManager.m @@ -15,8 +15,9 @@ -(instancetype)init { self = [super init]; if (self) { - self.bucket = [[MattermostBucket alloc] init]; - [self getEntities:true]; + self.bucket = [[MattermostBucket alloc] init]; + self.keychain = [[MMKeychainManager alloc] init]; + [self getEntities:true]; } return self; } @@ -150,10 +151,12 @@ } -(NSString *)getToken { - NSDictionary *general = [self.entities objectForKey:@"general"]; - NSDictionary *credentials = [general objectForKey:@"credentials"]; + NSDictionary *options = @{ + @"accessGroup": APP_GROUP_ID + }; + NSDictionary *credentials = [self.keychain getInternetCredentialsForServer:[self getServerUrl] withOptions:options]; - return [credentials objectForKey:@"token"]; + return [credentials objectForKey:@"password"]; } -(UInt64)scanValueFromConfig:(NSDictionary *)config key:(NSString *)key { diff --git a/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h index 194d712c7..ec1e7cbcc 100644 --- a/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h +++ b/ios/UploadAttachments/UploadAttachments/UploadAttachments-Bridging-Header.h @@ -5,3 +5,4 @@ #import "MMMConstants.h" #import "MattermostBucket.h" #import "StoreManager.h" +#import "MMKeychainManager.h" diff --git a/packager/moduleNames.js b/packager/moduleNames.js index 2b780a989..1263945e9 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -2,88 +2,8 @@ // See LICENSE.txt for license information. module.exports = [ - 'app/app.js', - 'app/components/app_icon.js', - 'app/components/at_mention/at_mention.js', - 'app/components/at_mention/index.js', - 'app/components/attachment_button.js', - 'app/components/badge.js', - 'app/components/emoji/emoji.js', - 'app/components/emoji/index.js', - 'app/components/fade.js', - 'app/components/file_attachment_list/file_attachment_icon.js', - 'app/components/file_attachment_list/file_attachment_image.js', - 'app/components/formatted_date.js', - 'app/components/formatted_markdown_text.js', - 'app/components/formatted_text.js', - 'app/components/formatted_time.js', - 'app/components/interactive_dialog_controller/index.js', - 'app/components/interactive_dialog_controller/interactive_dialog_controller.js', - 'app/components/layout/keyboard_layout/index.js', - 'app/components/layout/keyboard_layout/keyboard_layout.js', 'app/components/loading.js', - 'app/components/markdown/hashtag/hashtag.js', - 'app/components/markdown/hashtag/index.js', - 'app/components/markdown/index.js', - 'app/components/markdown/markdown.js', - 'app/components/markdown/markdown_block_quote.js', - 'app/components/markdown/markdown_code_block/index.js', - 'app/components/markdown/markdown_code_block/markdown_code_block.js', - 'app/components/markdown/markdown_emoji/index.js', - 'app/components/markdown/markdown_emoji/markdown_emoji.js', - 'app/components/markdown/markdown_image/index.js', - 'app/components/markdown/markdown_image/markdown_image.js', - 'app/components/markdown/markdown_link/index.js', - 'app/components/markdown/markdown_link/markdown_link.js', - 'app/components/markdown/markdown_list.js', - 'app/components/markdown/markdown_list_item.js', - 'app/components/markdown/markdown_table/index.js', - 'app/components/markdown/markdown_table/markdown_table.js', - 'app/components/markdown/markdown_table_cell/index.js', - 'app/components/markdown/markdown_table_cell/markdown_table_cell.js', - 'app/components/markdown/markdown_table_image/index.js', - 'app/components/markdown/markdown_table_image/markdown_table_image.js', - 'app/components/markdown/markdown_table_row/index.js', - 'app/components/markdown/markdown_table_row/markdown_table_row.js', - 'app/components/network_indicator/index.js', - 'app/components/network_indicator/network_indicator.js', 'app/components/paper_plane.js', - 'app/components/post/index.js', - 'app/components/post/post.js', - 'app/components/post_body/index.js', - 'app/components/post_body/post_body.js', - 'app/components/post_header/index.js', - 'app/components/post_header/post_header.js', - 'app/components/post_header/post_pre_header.js', - 'app/components/post_list/date_header/date_header.js', - 'app/components/post_list/date_header/index.js', - 'app/components/post_list/index.js', - 'app/components/post_list/new_messages_divider.js', - 'app/components/post_list/post_list.js', - 'app/components/post_list_retry.js', - 'app/components/post_profile_picture/index.js', - 'app/components/post_profile_picture/post_profile_picture.js', - 'app/components/post_textbox/components/typing/index.js', - 'app/components/post_textbox/components/typing/typing.js', - 'app/components/post_textbox/index.js', - 'app/components/post_textbox/post_textbox.android.js', - 'app/components/post_textbox/post_textbox_base.js', - 'app/components/profile_picture/index.js', - 'app/components/profile_picture/profile_picture.js', - 'app/components/progressive_image/index.js', - 'app/components/progressive_image/progressive_image.js', - 'app/components/reply_icon.js', - 'app/components/safe_area_view/index.js', - 'app/components/safe_area_view/safe_area_view.android.js', - 'app/components/send_button.js', - 'app/components/show_more_button/index.js', - 'app/components/show_more_button/show_more_button.js', - 'app/components/start/empty_toolbar.js', - 'app/components/status_bar/index.js', - 'app/components/status_bar/status_bar.js', - 'app/components/user_status/index.js', - 'app/components/user_status/user_status.js', - 'app/components/vector_icon.js', 'app/constants/custom_prop_types.js', 'app/constants/deep_linking.js', 'app/constants/device.js', @@ -92,7 +12,10 @@ module.exports = [ 'app/constants/navigation.js', 'app/constants/permissions.js', 'app/constants/view.js', - 'app/fetch_preconfig.js', + 'app/init/credentials.js', + 'app/init/emm_provider.js', + 'app/init/fetch.js', + 'app/init/global_event_handler.js', 'app/initial_state.js', 'app/mattermost.js', 'app/mattermost_bucket/index.js', @@ -128,61 +51,17 @@ module.exports = [ 'app/reducers/views/team.js', 'app/reducers/views/thread.js', 'app/reducers/views/user.js', - 'app/screens/channel/channel.android.js', - 'app/screens/channel/channel_base.js', - 'app/screens/channel/channel_nav_bar/channel_drawer_button.js', - 'app/screens/channel/channel_nav_bar/channel_nav_bar.js', - 'app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js', - 'app/screens/channel/channel_nav_bar/channel_search_button/index.js', - 'app/screens/channel/channel_nav_bar/channel_title/channel_title.js', - 'app/screens/channel/channel_nav_bar/channel_title/index.js', - 'app/screens/channel/channel_nav_bar/index.js', - 'app/screens/channel/channel_nav_bar/settings_drawer_button.js', - 'app/screens/channel/channel_post_list/channel_post_list.js', - 'app/screens/channel/channel_post_list/index.js', - 'app/screens/channel/index.js', - 'app/screens/entry/entry.js', - 'app/screens/entry/index.js', - 'app/screens/index.js', - 'app/screens/select_server/index.js', - 'app/screens/select_server/select_server.js', - 'app/selectors/client_upgrade.js', + 'app/store/ephemeral_store.js', 'app/store/index.js', 'app/store/middleware.js', 'app/store/thunk.js', 'app/telemetry/index.js', 'app/telemetry/telemetry.android.js', - 'app/telemetry/telemetry_utils.js', - 'app/utils/avoid_native_bridge.js', - 'app/utils/general.js', - 'app/utils/i18n.js', 'app/utils/image_cache_manager.js', - 'app/utils/network.js', 'app/utils/push_notifications.js', 'app/utils/sentry/middleware.js', 'app/utils/theme.js', - 'app/utils/time_tracker.js', 'dist/assets/config.json', - 'dist/assets/images/icons/audio.png', - 'dist/assets/images/icons/brokenimage.png', - 'dist/assets/images/icons/code.png', - 'dist/assets/images/icons/excel.png', - 'dist/assets/images/icons/generic.png', - 'dist/assets/images/icons/image.png', - 'dist/assets/images/icons/patch.png', - 'dist/assets/images/icons/pdf.png', - 'dist/assets/images/icons/ppt.png', - 'dist/assets/images/icons/video.png', - 'dist/assets/images/icons/webhook.jpg', - 'dist/assets/images/icons/word.png', - 'dist/assets/images/post_header/flag.png', - 'dist/assets/images/post_header/pin.png', - 'dist/assets/images/profile.jpg', - 'dist/assets/images/status/away.png', - 'dist/assets/images/status/dnd.png', - 'dist/assets/images/status/offline.png', - 'dist/assets/images/status/online.png', - 'dist/assets/images/thumb.png', 'index.js', 'node_modules/@babel/runtime/helpers/arrayWithHoles.js', 'node_modules/@babel/runtime/helpers/assertThisInitialized.js', @@ -208,9 +87,7 @@ module.exports = [ 'node_modules/@babel/runtime/regenerator/index.js', 'node_modules/@react-native-community/async-storage/lib/AsyncStorage.js', 'node_modules/@react-native-community/async-storage/lib/index.js', - 'node_modules/@react-native-community/netinfo/js/index.js', 'node_modules/base-64/base64.js', - 'node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js', 'node_modules/component-emitter/index.js', 'node_modules/core-js/modules/_a-function.js', 'node_modules/core-js/modules/_add-to-unscopables.js', @@ -288,12 +165,10 @@ module.exports = [ 'node_modules/core-js/modules/es6.set.js', 'node_modules/core-js/modules/es6.string.includes.js', 'node_modules/core-js/modules/es6.string.iterator.js', - 'node_modules/core-js/modules/es6.string.starts-with.js', 'node_modules/core-js/modules/es6.symbol.js', 'node_modules/core-js/modules/es7.array.includes.js', 'node_modules/core-js/modules/es7.object.define-getter.js', 'node_modules/core-js/modules/es7.object.define-setter.js', - 'node_modules/core-js/modules/es7.object.entries.js', 'node_modules/core-js/modules/es7.object.values.js', 'node_modules/core-js/modules/es7.symbol.async-iterator.js', 'node_modules/core-js/modules/web.dom.iterable.js', @@ -311,7 +186,6 @@ module.exports = [ 'node_modules/fbjs/lib/keyOf.js', 'node_modules/fbjs/lib/performance.js', 'node_modules/fbjs/lib/performanceNow.js', - 'node_modules/fuse.js/dist/fuse.js', 'node_modules/global/window.js', 'node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js', 'node_modules/intl-format-cache/index.js', @@ -473,10 +347,6 @@ module.exports = [ 'node_modules/mattermost-redux/reducers/requests/search.js', 'node_modules/mattermost-redux/reducers/requests/teams.js', 'node_modules/mattermost-redux/reducers/requests/users.js', - 'node_modules/mattermost-redux/selectors/entities/emojis.js', - 'node_modules/mattermost-redux/selectors/entities/general.js', - 'node_modules/mattermost-redux/selectors/entities/integrations.js', - 'node_modules/mattermost-redux/selectors/entities/teams.js', 'node_modules/mattermost-redux/store/configureStore.prod.js', 'node_modules/mattermost-redux/store/helpers.js', 'node_modules/mattermost-redux/store/index.js', @@ -485,17 +355,9 @@ module.exports = [ 'node_modules/mattermost-redux/store/reducer_registry.js', 'node_modules/mattermost-redux/utils/deep_freeze.js', 'node_modules/mattermost-redux/utils/event_emitter.js', - 'node_modules/mattermost-redux/utils/file_utils.js', - 'node_modules/mattermost-redux/utils/helpers.js', 'node_modules/mattermost-redux/utils/key_mirror.js', - 'node_modules/mattermost-redux/utils/post_list.js', 'node_modules/mattermost-redux/utils/theme_utils.js', - 'node_modules/moment-timezone/data/packed/latest.json', - 'node_modules/moment-timezone/index.js', - 'node_modules/moment-timezone/moment-timezone.js', - 'node_modules/moment/moment.js', 'node_modules/object-assign/index.js', - 'node_modules/pascalcase/index.js', 'node_modules/path-to-regexp/index.js', 'node_modules/path-to-regexp/node_modules/isarray/index.js', 'node_modules/promise/setimmediate/core.js', @@ -517,11 +379,8 @@ module.exports = [ 'node_modules/react-intl/locale-data/index.js', 'node_modules/react-is/cjs/react-is.production.min.js', 'node_modules/react-is/index.js', - 'node_modules/react-native-button/Button.js', - 'node_modules/react-native-button/coalesceNonElementChildren.js', 'node_modules/react-native-device-info/deviceinfo.js', - 'node_modules/react-native-linear-gradient/common.js', - 'node_modules/react-native-linear-gradient/index.android.js', + 'node_modules/react-native-keychain/index.js', 'node_modules/react-native-local-auth/LocalAuth.android.js', 'node_modules/react-native-local-auth/index.js', 'node_modules/react-native-navigation/src/NativeEventsReceiver.js', @@ -541,7 +400,6 @@ module.exports = [ 'node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js', 'node_modules/react-native/Libraries/Animated/src/Easing.js', 'node_modules/react-native/Libraries/Animated/src/components/AnimatedText.js', - 'node_modules/react-native/Libraries/Animated/src/components/AnimatedView.js', 'node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js', 'node_modules/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js', 'node_modules/react-native/Libraries/Animated/src/nodes/AnimatedNode.js', @@ -562,13 +420,9 @@ module.exports = [ 'node_modules/react-native/Libraries/Components/TextInput/TextInputState.js', 'node_modules/react-native/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js', 'node_modules/react-native/Libraries/Components/Touchable/Touchable.js', - 'node_modules/react-native/Libraries/Components/Touchable/TouchableOpacity.js', - 'node_modules/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js', 'node_modules/react-native/Libraries/Components/View/PlatformViewPropTypes.android.js', 'node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js', 'node_modules/react-native/Libraries/Components/View/ReactNativeViewAttributes.js', - 'node_modules/react-native/Libraries/Components/View/View.js', - 'node_modules/react-native/Libraries/Components/View/ViewNativeComponent.js', 'node_modules/react-native/Libraries/Core/ExceptionsManager.js', 'node_modules/react-native/Libraries/Core/InitializeCore.js', 'node_modules/react-native/Libraries/Core/ReactNativeVersion.js', @@ -605,7 +459,6 @@ module.exports = [ 'node_modules/react-native/Libraries/Image/AssetRegistry.js', 'node_modules/react-native/Libraries/Image/AssetSourceResolver.js', 'node_modules/react-native/Libraries/Image/Image.android.js', - 'node_modules/react-native/Libraries/Image/ImageBackground.js', 'node_modules/react-native/Libraries/Image/resolveAssetSource.js', 'node_modules/react-native/Libraries/Interaction/PanResponder.js', 'node_modules/react-native/Libraries/JSInspector/InspectorAgent.js', @@ -614,10 +467,8 @@ module.exports = [ 'node_modules/react-native/Libraries/Linking/Linking.js', 'node_modules/react-native/Libraries/Network/NetInfo.js', 'node_modules/react-native/Libraries/Performance/Systrace.js', - 'node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js', 'node_modules/react-native/Libraries/Promise.js', 'node_modules/react-native/Libraries/ReactNative/AppRegistry.js', - 'node_modules/react-native/Libraries/ReactNative/FabricUIManager.js', 'node_modules/react-native/Libraries/ReactNative/I18nManager.js', 'node_modules/react-native/Libraries/ReactNative/UIManager.js', 'node_modules/react-native/Libraries/ReactNative/UIManagerProperties.js', @@ -634,7 +485,6 @@ module.exports = [ 'node_modules/react-native/Libraries/StyleSheet/processTransform.js', 'node_modules/react-native/Libraries/Text/Text.js', 'node_modules/react-native/Libraries/Text/TextStylePropTypes.js', - 'node_modules/react-native/Libraries/Utilities/BackHandler.android.js', 'node_modules/react-native/Libraries/Utilities/DeviceInfo.js', 'node_modules/react-native/Libraries/Utilities/Dimensions.js', 'node_modules/react-native/Libraries/Utilities/HMRClient.js', @@ -718,15 +568,30 @@ module.exports = [ 'node_modules/redux-thunk/lib/index.js', 'node_modules/redux/lib/redux.js', 'node_modules/regenerator-runtime/runtime.js', - 'node_modules/reselect/lib/index.js', 'node_modules/rn-fetch-blob/android.js', 'node_modules/rn-fetch-blob/cba/index.js', 'node_modules/rn-fetch-blob/class/RNFetchBlobFile.js', + 'node_modules/rn-fetch-blob/class/RNFetchBlobReadStream.js', + 'node_modules/rn-fetch-blob/class/RNFetchBlobSession.js', + 'node_modules/rn-fetch-blob/class/RNFetchBlobWriteStream.js', 'node_modules/rn-fetch-blob/fs.js', 'node_modules/rn-fetch-blob/index.js', 'node_modules/rn-fetch-blob/json-stream.js', 'node_modules/rn-fetch-blob/lib/oboe-browser.min.js', + 'node_modules/rn-fetch-blob/polyfill/Blob.js', + 'node_modules/rn-fetch-blob/polyfill/Event.js', + 'node_modules/rn-fetch-blob/polyfill/EventTarget.js', 'node_modules/rn-fetch-blob/polyfill/Fetch.js', + 'node_modules/rn-fetch-blob/polyfill/File.js', + 'node_modules/rn-fetch-blob/polyfill/FileReader.js', + 'node_modules/rn-fetch-blob/polyfill/ProgressEvent.js', + 'node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js', + 'node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js', + 'node_modules/rn-fetch-blob/polyfill/index.js', + 'node_modules/rn-fetch-blob/utils/log.js', + 'node_modules/rn-fetch-blob/utils/unicode.js', + 'node_modules/rn-fetch-blob/utils/uri.js', + 'node_modules/rn-fetch-blob/utils/uuid.js', 'node_modules/rn-host-detect/index.js', 'node_modules/sc-errors/decycle.js', 'node_modules/sc-errors/index.js', @@ -736,10 +601,8 @@ module.exports = [ 'node_modules/scheduler/index.js', 'node_modules/scheduler/tracing.js', 'node_modules/semver/semver.js', - 'node_modules/shallow-equals/index.js', 'node_modules/stacktrace-parser/dist/stack-trace-parser.umd.js', 'node_modules/symbol-observable/lib/index.js', 'node_modules/symbol-observable/lib/ponyfill.js', - 'node_modules/tinycolor2/tinycolor.js', 'node_modules/url-parse/index.js', ]; diff --git a/packager/modulePaths.js b/packager/modulePaths.js index 0f657849a..301f51f92 100644 --- a/packager/modulePaths.js +++ b/packager/modulePaths.js @@ -1,87 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -module.exports = ['./node_modules/app/app.js', - './node_modules/app/components/app_icon.js', - './node_modules/app/components/at_mention/at_mention.js', - './node_modules/app/components/at_mention/index.js', - './node_modules/app/components/attachment_button.js', - './node_modules/app/components/badge.js', - './node_modules/app/components/emoji/emoji.js', - './node_modules/app/components/emoji/index.js', - './node_modules/app/components/fade.js', - './node_modules/app/components/file_attachment_list/file_attachment_icon.js', - './node_modules/app/components/file_attachment_list/file_attachment_image.js', - './node_modules/app/components/formatted_date.js', - './node_modules/app/components/formatted_markdown_text.js', - './node_modules/app/components/formatted_text.js', - './node_modules/app/components/formatted_time.js', - './node_modules/app/components/interactive_dialog_controller/index.js', - './node_modules/app/components/interactive_dialog_controller/interactive_dialog_controller.js', - './node_modules/app/components/layout/keyboard_layout/index.js', - './node_modules/app/components/layout/keyboard_layout/keyboard_layout.js', - './node_modules/app/components/loading.js', - './node_modules/app/components/markdown/hashtag/hashtag.js', - './node_modules/app/components/markdown/hashtag/index.js', - './node_modules/app/components/markdown/index.js', - './node_modules/app/components/markdown/markdown.js', - './node_modules/app/components/markdown/markdown_block_quote.js', - './node_modules/app/components/markdown/markdown_code_block/index.js', - './node_modules/app/components/markdown/markdown_code_block/markdown_code_block.js', - './node_modules/app/components/markdown/markdown_emoji/index.js', - './node_modules/app/components/markdown/markdown_emoji/markdown_emoji.js', - './node_modules/app/components/markdown/markdown_image/index.js', - './node_modules/app/components/markdown/markdown_image/markdown_image.js', - './node_modules/app/components/markdown/markdown_link/index.js', - './node_modules/app/components/markdown/markdown_link/markdown_link.js', - './node_modules/app/components/markdown/markdown_list.js', - './node_modules/app/components/markdown/markdown_list_item.js', - './node_modules/app/components/markdown/markdown_table/index.js', - './node_modules/app/components/markdown/markdown_table/markdown_table.js', - './node_modules/app/components/markdown/markdown_table_cell/index.js', - './node_modules/app/components/markdown/markdown_table_cell/markdown_table_cell.js', - './node_modules/app/components/markdown/markdown_table_image/index.js', - './node_modules/app/components/markdown/markdown_table_image/markdown_table_image.js', - './node_modules/app/components/markdown/markdown_table_row/index.js', - './node_modules/app/components/markdown/markdown_table_row/markdown_table_row.js', - './node_modules/app/components/network_indicator/index.js', - './node_modules/app/components/network_indicator/network_indicator.js', + +module.exports = ['./node_modules/app/components/loading.js', './node_modules/app/components/paper_plane.js', - './node_modules/app/components/post/index.js', - './node_modules/app/components/post/post.js', - './node_modules/app/components/post_body/index.js', - './node_modules/app/components/post_body/post_body.js', - './node_modules/app/components/post_header/index.js', - './node_modules/app/components/post_header/post_header.js', - './node_modules/app/components/post_header/post_pre_header.js', - './node_modules/app/components/post_list/date_header/date_header.js', - './node_modules/app/components/post_list/date_header/index.js', - './node_modules/app/components/post_list/index.js', - './node_modules/app/components/post_list/new_messages_divider.js', - './node_modules/app/components/post_list/post_list.js', - './node_modules/app/components/post_list_retry.js', - './node_modules/app/components/post_profile_picture/index.js', - './node_modules/app/components/post_profile_picture/post_profile_picture.js', - './node_modules/app/components/post_textbox/components/typing/index.js', - './node_modules/app/components/post_textbox/components/typing/typing.js', - './node_modules/app/components/post_textbox/index.js', - './node_modules/app/components/post_textbox/post_textbox.android.js', - './node_modules/app/components/post_textbox/post_textbox_base.js', - './node_modules/app/components/profile_picture/index.js', - './node_modules/app/components/profile_picture/profile_picture.js', - './node_modules/app/components/progressive_image/index.js', - './node_modules/app/components/progressive_image/progressive_image.js', - './node_modules/app/components/reply_icon.js', - './node_modules/app/components/safe_area_view/index.js', - './node_modules/app/components/safe_area_view/safe_area_view.android.js', - './node_modules/app/components/send_button.js', - './node_modules/app/components/show_more_button/index.js', - './node_modules/app/components/show_more_button/show_more_button.js', - './node_modules/app/components/start/empty_toolbar.js', - './node_modules/app/components/status_bar/index.js', - './node_modules/app/components/status_bar/status_bar.js', - './node_modules/app/components/user_status/index.js', - './node_modules/app/components/user_status/user_status.js', - './node_modules/app/components/vector_icon.js', './node_modules/app/constants/custom_prop_types.js', './node_modules/app/constants/deep_linking.js', './node_modules/app/constants/device.js', @@ -90,7 +11,10 @@ module.exports = ['./node_modules/app/app.js', './node_modules/app/constants/navigation.js', './node_modules/app/constants/permissions.js', './node_modules/app/constants/view.js', - './node_modules/app/fetch_preconfig.js', + './node_modules/app/init/credentials.js', + './node_modules/app/init/emm_provider.js', + './node_modules/app/init/fetch.js', + './node_modules/app/init/global_event_handler.js', './node_modules/app/initial_state.js', './node_modules/app/mattermost.js', './node_modules/app/mattermost_bucket/index.js', @@ -126,40 +50,16 @@ module.exports = ['./node_modules/app/app.js', './node_modules/app/reducers/views/team.js', './node_modules/app/reducers/views/thread.js', './node_modules/app/reducers/views/user.js', - './node_modules/app/screens/channel/channel.android.js', - './node_modules/app/screens/channel/channel_base.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_drawer_button.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_nav_bar.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_search_button/channel_search_button.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_search_button/index.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_title/channel_title.js', - './node_modules/app/screens/channel/channel_nav_bar/channel_title/index.js', - './node_modules/app/screens/channel/channel_nav_bar/index.js', - './node_modules/app/screens/channel/channel_nav_bar/settings_drawer_button.js', - './node_modules/app/screens/channel/channel_post_list/channel_post_list.js', - './node_modules/app/screens/channel/channel_post_list/index.js', - './node_modules/app/screens/channel/index.js', - './node_modules/app/screens/entry/entry.js', - './node_modules/app/screens/entry/index.js', - './node_modules/app/screens/index.js', - './node_modules/app/screens/select_server/index.js', - './node_modules/app/screens/select_server/select_server.js', - './node_modules/app/selectors/client_upgrade.js', + './node_modules/app/store/ephemeral_store.js', './node_modules/app/store/index.js', './node_modules/app/store/middleware.js', './node_modules/app/store/thunk.js', './node_modules/app/telemetry/index.js', './node_modules/app/telemetry/telemetry.android.js', - './node_modules/app/telemetry/telemetry_utils.js', - './node_modules/app/utils/avoid_native_bridge.js', - './node_modules/app/utils/general.js', - './node_modules/app/utils/i18n.js', './node_modules/app/utils/image_cache_manager.js', - './node_modules/app/utils/network.js', './node_modules/app/utils/push_notifications.js', './node_modules/app/utils/sentry/middleware.js', './node_modules/app/utils/theme.js', - './node_modules/app/utils/time_tracker.js', './node_modules/index.js', './node_modules/node_modules/@babel/runtime/helpers/arrayWithHoles.js', './node_modules/node_modules/@babel/runtime/helpers/assertThisInitialized.js', @@ -185,9 +85,7 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/@babel/runtime/regenerator/index.js', './node_modules/node_modules/@react-native-community/async-storage/lib/AsyncStorage.js', './node_modules/node_modules/@react-native-community/async-storage/lib/index.js', - './node_modules/node_modules/@react-native-community/netinfo/js/index.js', './node_modules/node_modules/base-64/base64.js', - './node_modules/node_modules/commonmark-react-renderer/src/commonmark-react-renderer.js', './node_modules/node_modules/component-emitter/index.js', './node_modules/node_modules/core-js/modules/_a-function.js', './node_modules/node_modules/core-js/modules/_add-to-unscopables.js', @@ -265,12 +163,10 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/core-js/modules/es6.set.js', './node_modules/node_modules/core-js/modules/es6.string.includes.js', './node_modules/node_modules/core-js/modules/es6.string.iterator.js', - './node_modules/node_modules/core-js/modules/es6.string.starts-with.js', './node_modules/node_modules/core-js/modules/es6.symbol.js', './node_modules/node_modules/core-js/modules/es7.array.includes.js', './node_modules/node_modules/core-js/modules/es7.object.define-getter.js', './node_modules/node_modules/core-js/modules/es7.object.define-setter.js', - './node_modules/node_modules/core-js/modules/es7.object.entries.js', './node_modules/node_modules/core-js/modules/es7.object.values.js', './node_modules/node_modules/core-js/modules/es7.symbol.async-iterator.js', './node_modules/node_modules/core-js/modules/web.dom.iterable.js', @@ -288,7 +184,6 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/fbjs/lib/keyOf.js', './node_modules/node_modules/fbjs/lib/performance.js', './node_modules/node_modules/fbjs/lib/performanceNow.js', - './node_modules/node_modules/fuse.js/dist/fuse.js', './node_modules/node_modules/global/window.js', './node_modules/node_modules/hoist-non-react-statics/dist/hoist-non-react-statics.cjs.js', './node_modules/node_modules/intl-format-cache/index.js', @@ -450,10 +345,6 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/mattermost-redux/reducers/requests/search.js', './node_modules/node_modules/mattermost-redux/reducers/requests/teams.js', './node_modules/node_modules/mattermost-redux/reducers/requests/users.js', - './node_modules/node_modules/mattermost-redux/selectors/entities/emojis.js', - './node_modules/node_modules/mattermost-redux/selectors/entities/general.js', - './node_modules/node_modules/mattermost-redux/selectors/entities/integrations.js', - './node_modules/node_modules/mattermost-redux/selectors/entities/teams.js', './node_modules/node_modules/mattermost-redux/store/configureStore.prod.js', './node_modules/node_modules/mattermost-redux/store/helpers.js', './node_modules/node_modules/mattermost-redux/store/index.js', @@ -462,16 +353,9 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/mattermost-redux/store/reducer_registry.js', './node_modules/node_modules/mattermost-redux/utils/deep_freeze.js', './node_modules/node_modules/mattermost-redux/utils/event_emitter.js', - './node_modules/node_modules/mattermost-redux/utils/file_utils.js', - './node_modules/node_modules/mattermost-redux/utils/helpers.js', './node_modules/node_modules/mattermost-redux/utils/key_mirror.js', - './node_modules/node_modules/mattermost-redux/utils/post_list.js', './node_modules/node_modules/mattermost-redux/utils/theme_utils.js', - './node_modules/node_modules/moment-timezone/index.js', - './node_modules/node_modules/moment-timezone/moment-timezone.js', - './node_modules/node_modules/moment/moment.js', './node_modules/node_modules/object-assign/index.js', - './node_modules/node_modules/pascalcase/index.js', './node_modules/node_modules/path-to-regexp/index.js', './node_modules/node_modules/path-to-regexp/node_modules/isarray/index.js', './node_modules/node_modules/promise/setimmediate/core.js', @@ -493,11 +377,8 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-intl/locale-data/index.js', './node_modules/node_modules/react-is/cjs/react-is.production.min.js', './node_modules/node_modules/react-is/index.js', - './node_modules/node_modules/react-native-button/Button.js', - './node_modules/node_modules/react-native-button/coalesceNonElementChildren.js', './node_modules/node_modules/react-native-device-info/deviceinfo.js', - './node_modules/node_modules/react-native-linear-gradient/common.js', - './node_modules/node_modules/react-native-linear-gradient/index.android.js', + './node_modules/node_modules/react-native-keychain/index.js', './node_modules/node_modules/react-native-local-auth/LocalAuth.android.js', './node_modules/node_modules/react-native-local-auth/index.js', './node_modules/node_modules/react-native-navigation/src/NativeEventsReceiver.js', @@ -517,7 +398,6 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-native/Libraries/Animated/src/AnimatedImplementation.js', './node_modules/node_modules/react-native/Libraries/Animated/src/Easing.js', './node_modules/node_modules/react-native/Libraries/Animated/src/components/AnimatedText.js', - './node_modules/node_modules/react-native/Libraries/Animated/src/components/AnimatedView.js', './node_modules/node_modules/react-native/Libraries/Animated/src/createAnimatedComponent.js', './node_modules/node_modules/react-native/Libraries/Animated/src/nodes/AnimatedInterpolation.js', './node_modules/node_modules/react-native/Libraries/Animated/src/nodes/AnimatedNode.js', @@ -538,13 +418,9 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-native/Libraries/Components/TextInput/TextInputState.js', './node_modules/node_modules/react-native/Libraries/Components/ToolbarAndroid/ToolbarAndroid.android.js', './node_modules/node_modules/react-native/Libraries/Components/Touchable/Touchable.js', - './node_modules/node_modules/react-native/Libraries/Components/Touchable/TouchableOpacity.js', - './node_modules/node_modules/react-native/Libraries/Components/Touchable/TouchableWithoutFeedback.js', './node_modules/node_modules/react-native/Libraries/Components/View/PlatformViewPropTypes.android.js', './node_modules/node_modules/react-native/Libraries/Components/View/ReactNativeStyleAttributes.js', './node_modules/node_modules/react-native/Libraries/Components/View/ReactNativeViewAttributes.js', - './node_modules/node_modules/react-native/Libraries/Components/View/View.js', - './node_modules/node_modules/react-native/Libraries/Components/View/ViewNativeComponent.js', './node_modules/node_modules/react-native/Libraries/Core/ExceptionsManager.js', './node_modules/node_modules/react-native/Libraries/Core/InitializeCore.js', './node_modules/node_modules/react-native/Libraries/Core/ReactNativeVersion.js', @@ -581,7 +457,6 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-native/Libraries/Image/AssetRegistry.js', './node_modules/node_modules/react-native/Libraries/Image/AssetSourceResolver.js', './node_modules/node_modules/react-native/Libraries/Image/Image.android.js', - './node_modules/node_modules/react-native/Libraries/Image/ImageBackground.js', './node_modules/node_modules/react-native/Libraries/Image/resolveAssetSource.js', './node_modules/node_modules/react-native/Libraries/Interaction/PanResponder.js', './node_modules/node_modules/react-native/Libraries/JSInspector/InspectorAgent.js', @@ -590,10 +465,8 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-native/Libraries/Linking/Linking.js', './node_modules/node_modules/react-native/Libraries/Network/NetInfo.js', './node_modules/node_modules/react-native/Libraries/Performance/Systrace.js', - './node_modules/node_modules/react-native/Libraries/PermissionsAndroid/PermissionsAndroid.js', './node_modules/node_modules/react-native/Libraries/Promise.js', './node_modules/node_modules/react-native/Libraries/ReactNative/AppRegistry.js', - './node_modules/node_modules/react-native/Libraries/ReactNative/FabricUIManager.js', './node_modules/node_modules/react-native/Libraries/ReactNative/I18nManager.js', './node_modules/node_modules/react-native/Libraries/ReactNative/UIManager.js', './node_modules/node_modules/react-native/Libraries/ReactNative/UIManagerProperties.js', @@ -610,7 +483,6 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/react-native/Libraries/StyleSheet/processTransform.js', './node_modules/node_modules/react-native/Libraries/Text/Text.js', './node_modules/node_modules/react-native/Libraries/Text/TextStylePropTypes.js', - './node_modules/node_modules/react-native/Libraries/Utilities/BackHandler.android.js', './node_modules/node_modules/react-native/Libraries/Utilities/DeviceInfo.js', './node_modules/node_modules/react-native/Libraries/Utilities/Dimensions.js', './node_modules/node_modules/react-native/Libraries/Utilities/HMRClient.js', @@ -694,15 +566,30 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/redux-thunk/lib/index.js', './node_modules/node_modules/redux/lib/redux.js', './node_modules/node_modules/regenerator-runtime/runtime.js', - './node_modules/node_modules/reselect/lib/index.js', './node_modules/node_modules/rn-fetch-blob/android.js', './node_modules/node_modules/rn-fetch-blob/cba/index.js', './node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobFile.js', + './node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobReadStream.js', + './node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobSession.js', + './node_modules/node_modules/rn-fetch-blob/class/RNFetchBlobWriteStream.js', './node_modules/node_modules/rn-fetch-blob/fs.js', './node_modules/node_modules/rn-fetch-blob/index.js', './node_modules/node_modules/rn-fetch-blob/json-stream.js', './node_modules/node_modules/rn-fetch-blob/lib/oboe-browser.min.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/Blob.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/Event.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/EventTarget.js', './node_modules/node_modules/rn-fetch-blob/polyfill/Fetch.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/File.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/FileReader.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/ProgressEvent.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequest.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/XMLHttpRequestEventTarget.js', + './node_modules/node_modules/rn-fetch-blob/polyfill/index.js', + './node_modules/node_modules/rn-fetch-blob/utils/log.js', + './node_modules/node_modules/rn-fetch-blob/utils/unicode.js', + './node_modules/node_modules/rn-fetch-blob/utils/uri.js', + './node_modules/node_modules/rn-fetch-blob/utils/uuid.js', './node_modules/node_modules/rn-host-detect/index.js', './node_modules/node_modules/sc-errors/decycle.js', './node_modules/node_modules/sc-errors/index.js', @@ -712,9 +599,7 @@ module.exports = ['./node_modules/app/app.js', './node_modules/node_modules/scheduler/index.js', './node_modules/node_modules/scheduler/tracing.js', './node_modules/node_modules/semver/semver.js', - './node_modules/node_modules/shallow-equals/index.js', './node_modules/node_modules/stacktrace-parser/dist/stack-trace-parser.umd.js', './node_modules/node_modules/symbol-observable/lib/index.js', './node_modules/node_modules/symbol-observable/lib/ponyfill.js', - './node_modules/node_modules/tinycolor2/tinycolor.js', - './node_modules/node_modules/url-parse/index.js']; \ No newline at end of file + './node_modules/node_modules/url-parse/index.js']; diff --git a/share_extension/android/extension_post/extension_post.js b/share_extension/android/extension_post/extension_post.js index a1cfa5679..00f357d03 100644 --- a/share_extension/android/extension_post/extension_post.js +++ b/share_extension/android/extension_post/extension_post.js @@ -21,7 +21,6 @@ import MaterialIcon from 'react-native-vector-icons/MaterialIcons'; import Video from 'react-native-video'; import LocalAuth from 'react-native-local-auth'; import RNFetchBlob from 'rn-fetch-blob'; -import {getGenericPassword} from 'react-native-keychain'; import {Client4} from 'mattermost-redux/client'; import {Preferences} from 'mattermost-redux/constants'; @@ -30,8 +29,8 @@ import {getFormattedFileSize, lookupMimeType} from 'mattermost-redux/utils/file_ import Loading from 'app/components/loading'; import PaperPlane from 'app/components/paper_plane'; import {MAX_FILE_COUNT} from 'app/constants/post_textbox'; +import {getCurrentServerUrl, getAppCredentials} from 'app/init/credentials'; import mattermostManaged from 'app/mattermost_managed'; -import avoidNativeBridge from 'app/utils/avoid_native_bridge'; import {getExtensionFromMime} from 'app/utils/file'; import {emptyFunction} from 'app/utils/general'; import {setCSRFFromCookie} from 'app/utils/security'; @@ -49,7 +48,6 @@ import { import ChannelButton from './channel_button'; import TeamButton from './team_button'; -const {Initialization} = NativeModules; const defaultTheme = Preferences.THEMES.default; const extensionSvg = { csv: ExcelSvg, @@ -232,32 +230,18 @@ export default class ExtensionPost extends PureComponent { getAppCredentials = async () => { try { - const credentials = await avoidNativeBridge( - () => { - return Initialization.credentialsExist; - }, - () => { - return Initialization.credentials; - }, - () => { - return getGenericPassword(); - } - ); + const url = await getCurrentServerUrl(); + const credentials = await getAppCredentials(); if (credentials) { - const passwordParsed = credentials.password.split(','); + const token = credentials.password; - // password == token, url - if (passwordParsed.length === 2) { - const [token, url] = passwordParsed; - - if (url && url !== 'undefined' && token && token !== 'undefined') { - this.token = token; - this.url = url; - Client4.setUrl(url); - Client4.setToken(token); - await setCSRFFromCookie(url); - } + if (url && url !== 'undefined' && token && token !== 'undefined') { + this.token = token; + this.url = url; + Client4.setUrl(url); + Client4.setToken(token); + await setCSRFFromCookie(url); } } } catch (error) { From 4491e8515fb59a489394f33472909dd7ec8c94f0 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 27 Jun 2019 09:42:01 -0400 Subject: [PATCH 21/28] Bump app build number to 202 (#2933) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9d60e7df6..50011f440 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -122,7 +122,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 201 + versionCode 202 versionName "1.21.0" multiDexEnabled = true ndk { diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 78b742f59..2e1b3e91b 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 202; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2828,7 +2828,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 202; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index 2080c88ac..ee4ed2946 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 201 + 202 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index d317efc1f..826e9ace0 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 201 + 202 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 04c126b06..214ca6bea 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 201 + 202 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 186ab3194..02cea6761 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 201 + 202 NSExtension NSExtensionPointIdentifier From 94632595cf7d75b5633ed89642f92b2f67f4c7a6 Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Thu, 27 Jun 2019 08:09:51 -0700 Subject: [PATCH 22/28] Update jsc to intl version (#2931) --- android/app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 50011f440..6562e417b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -211,7 +211,7 @@ configurations.all { dependencies { // Make sure to put android-jsc at the top - implementation "org.webkit:android-jsc:r241213" + implementation "org.webkit:android-jsc-intl:r241213" implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "com.android.support:appcompat-v7:${rootProject.ext.supportLibVersion}" From c50215cf33f34fb498a110e1d5ac61553d64319c Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Thu, 27 Jun 2019 11:22:53 -0400 Subject: [PATCH 23/28] Bump app build number to 203 (#2935) --- android/app/build.gradle | 2 +- ios/Mattermost.xcodeproj/project.pbxproj | 4 ++-- ios/Mattermost/Info.plist | 2 +- ios/MattermostShare/Info.plist | 2 +- ios/MattermostTests/Info.plist | 2 +- ios/NotificationService/Info.plist | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 6562e417b..bfe323a23 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -122,7 +122,7 @@ android { applicationId "com.mattermost.rnbeta" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 202 + versionCode 203 versionName "1.21.0" multiDexEnabled = true ndk { diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj index 2e1b3e91b..70f89e6d5 100644 --- a/ios/Mattermost.xcodeproj/project.pbxproj +++ b/ios/Mattermost.xcodeproj/project.pbxproj @@ -2768,7 +2768,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 202; + CURRENT_PROJECT_VERSION = 203; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; @@ -2828,7 +2828,7 @@ CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements; CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 202; + CURRENT_PROJECT_VERSION = 203; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = UQ8HT4Q2XM; ENABLE_BITCODE = NO; diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist index ee4ed2946..7677d8aa4 100644 --- a/ios/Mattermost/Info.plist +++ b/ios/Mattermost/Info.plist @@ -34,7 +34,7 @@ CFBundleVersion - 202 + 203 ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist index 826e9ace0..2b1905d7d 100644 --- a/ios/MattermostShare/Info.plist +++ b/ios/MattermostShare/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 202 + 203 NSAppTransportSecurity NSAllowsArbitraryLoads diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist index 214ca6bea..3b893649d 100644 --- a/ios/MattermostTests/Info.plist +++ b/ios/MattermostTests/Info.plist @@ -19,6 +19,6 @@ CFBundleSignature ???? CFBundleVersion - 202 + 203 diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist index 02cea6761..f2bcfb68e 100644 --- a/ios/NotificationService/Info.plist +++ b/ios/NotificationService/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 1.21.0 CFBundleVersion - 202 + 203 NSExtension NSExtensionPointIdentifier From 1588f46ae41988bb1fd1b626f4e8605e013f5486 Mon Sep 17 00:00:00 2001 From: Michael Kochell Date: Tue, 2 Jul 2019 08:18:34 -0400 Subject: [PATCH 24/28] Pass cookie for post actions (#2945) --- .../message_attachments/action_button/action_button.js | 7 ++++--- app/components/message_attachments/action_button/index.js | 4 ++-- app/components/message_attachments/attachment_actions.js | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/components/message_attachments/action_button/action_button.js b/app/components/message_attachments/action_button/action_button.js index 86374fd04..a604f6995 100644 --- a/app/components/message_attachments/action_button/action_button.js +++ b/app/components/message_attachments/action_button/action_button.js @@ -12,17 +12,18 @@ import ActionButtonText from './action_button_text'; export default class ActionButton extends PureComponent { static propTypes = { actions: PropTypes.shape({ - doPostAction: PropTypes.func.isRequired, + doPostActionWithCookie: PropTypes.func.isRequired, }).isRequired, id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, postId: PropTypes.string.isRequired, theme: PropTypes.object.isRequired, + cookie: PropTypes.string.isRequired, }; handleActionPress = preventDoubleTap(() => { - const {actions, id, postId} = this.props; - actions.doPostAction(postId, id); + const {actions, id, postId, cookie} = this.props; + actions.doPostActionWithCookie(postId, id, cookie); }, 4000); render() { diff --git a/app/components/message_attachments/action_button/index.js b/app/components/message_attachments/action_button/index.js index 7b87164f8..fc7e7cec3 100644 --- a/app/components/message_attachments/action_button/index.js +++ b/app/components/message_attachments/action_button/index.js @@ -4,7 +4,7 @@ import {bindActionCreators} from 'redux'; import {connect} from 'react-redux'; -import {doPostAction} from 'mattermost-redux/actions/posts'; +import {doPostActionWithCookie} from 'mattermost-redux/actions/posts'; import {getTheme} from 'mattermost-redux/selectors/entities/preferences'; import ActionButton from './action_button'; @@ -18,7 +18,7 @@ function mapStateToProps(state) { function mapDispatchToProps(dispatch) { return { actions: bindActionCreators({ - doPostAction, + doPostActionWithCookie, }, dispatch), }; } diff --git a/app/components/message_attachments/attachment_actions.js b/app/components/message_attachments/attachment_actions.js index 8b46580c2..865d1fb66 100644 --- a/app/components/message_attachments/attachment_actions.js +++ b/app/components/message_attachments/attachment_actions.js @@ -53,6 +53,7 @@ export default class AttachmentActions extends PureComponent { From 614e6da1424211205fc0799f536e953ccd0b4bce Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 2 Jul 2019 10:11:14 -0400 Subject: [PATCH 25/28] Fix AD/LDAP & SAML profile sync properties (#2941) --- app/screens/edit_profile/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/screens/edit_profile/index.js b/app/screens/edit_profile/index.js index 44ee8b27f..ee29b73a3 100644 --- a/app/screens/edit_profile/index.js +++ b/app/screens/edit_profile/index.js @@ -15,7 +15,7 @@ import EditProfile from './edit_profile'; function mapStateToProps(state, ownProps) { const config = getConfig(state); const {serverVersion} = state.entities.general; - const {service} = ownProps.currentUser; + const {auth_service: service} = ownProps.currentUser; const firstNameDisabled = (service === 'ldap' && config.LdapFirstNameAttributeSet === 'true') || (service === 'saml' && config.SamlFirstNameAttributeSet === 'true'); From 20bcb9d44a77e47d8763884056223299fb601c6e Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 2 Jul 2019 10:33:40 -0400 Subject: [PATCH 26/28] Allow the server url to be cleared (#2942) * Allow the server url to be cleared * Remove getDerivedStateFromProps --- app/screens/select_server/select_server.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/screens/select_server/select_server.js b/app/screens/select_server/select_server.js index 79d291ac3..f65c1de77 100644 --- a/app/screens/select_server/select_server.js +++ b/app/screens/select_server/select_server.js @@ -67,16 +67,6 @@ export default class SelectServer extends PureComponent { intl: intlShape.isRequired, }; - static getDerivedStateFromProps(props, state) { - if (props.serverUrl && !state.url) { - return { - url: props.serverUrl, - }; - } - - return null; - } - constructor(props) { super(props); From 9f88923656bb11409c943fbe5e08005aa92ccaa0 Mon Sep 17 00:00:00 2001 From: Elias Nahum Date: Tue, 2 Jul 2019 12:28:13 -0400 Subject: [PATCH 27/28] Place the new message indicator at the top of the screen (#2943) * Place the new message indicator at the top of the screen * Add unit tests for channel postVisibility reducer --- .../post_attachment_opengraph.js | 14 +++ .../post_body_additional_content.js | 5 +- app/components/post_list/post_list.js | 12 +- app/reducers/views/channel.js | 6 +- app/reducers/views/channel.test.js | 112 ++++++++++++++++++ 5 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 app/reducers/views/channel.test.js diff --git a/app/components/post_attachment_opengraph/post_attachment_opengraph.js b/app/components/post_attachment_opengraph/post_attachment_opengraph.js index d74293ea7..a495f159c 100644 --- a/app/components/post_attachment_opengraph/post_attachment_opengraph.js +++ b/app/components/post_attachment_opengraph/post_attachment_opengraph.js @@ -89,6 +89,7 @@ export default class PostAttachmentOpenGraph extends PureComponent { const bestImage = getNearestPoint(bestDimensions, data.images, 'width', 'height'); const imageUrl = bestImage.secure_url || bestImage.url; + let ogImage; if (imagesMetadata && imagesMetadata[imageUrl]) { ogImage = imagesMetadata[imageUrl]; @@ -98,6 +99,12 @@ export default class PostAttachmentOpenGraph extends PureComponent { ogImage = data.images.find((i) => i.url === imageUrl || i.secure_url === imageUrl); } + // Fallback when the ogImage does not have dimensions but there is a metaImage defined + const metaImages = imagesMetadata ? Object.values(imagesMetadata) : null; + if ((!ogImage?.width || !ogImage?.height) && metaImages?.length) { + ogImage = metaImages[0]; + } + let dimensions = bestDimensions; if (ogImage?.width && ogImage?.height) { dimensions = calculateDimensions(ogImage.height, ogImage.width, this.getViewPostWidth()); @@ -139,9 +146,16 @@ export default class PostAttachmentOpenGraph extends PureComponent { ogImage = openGraphData.images.find((i) => i.url === openGraphImageUrl || i.secure_url === openGraphImageUrl); } + // Fallback when the ogImage does not have dimensions but there is a metaImage defined + const metaImages = imagesMetadata ? Object.values(imagesMetadata) : null; + if ((!ogImage?.width || !ogImage?.height) && metaImages?.length) { + ogImage = metaImages[0]; + } + if (ogImage?.width && ogImage?.height) { this.setImageSize(imageUrl, ogImage.width, ogImage.height); } else { + // if we get to this point there can be a scroll pop Image.getSize(imageUrl, (width, height) => { this.setImageSize(imageUrl, width, height); }, () => null); diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js index 9d957b074..5358c7695 100644 --- a/app/components/post_body_additional_content/post_body_additional_content.js +++ b/app/components/post_body_additional_content/post_body_additional_content.js @@ -182,11 +182,12 @@ export default class PostBodyAdditionalContent extends PureComponent { }; generateStaticEmbed = (isYouTube, isImage) => { - if (isYouTube || isImage) { + const {isReplyPost, link, metadata, navigator, openGraphData, showLinkPreviews, theme} = this.props; + + if (isYouTube || (isImage && !openGraphData)) { return null; } - const {isReplyPost, link, metadata, navigator, openGraphData, showLinkPreviews, theme} = this.props; const attachments = this.getMessageAttachment(); if (attachments) { return attachments; diff --git a/app/components/post_list/post_list.js b/app/components/post_list/post_list.js index ddbae2381..8634cf44e 100644 --- a/app/components/post_list/post_list.js +++ b/app/components/post_list/post_list.js @@ -294,11 +294,13 @@ export default class PostList extends PureComponent { this.props.initialIndex > 0 && !this.hasDoneInitialScroll ) { - this.flatListRef.current.scrollToIndex({ - animated: false, - index: this.props.initialIndex, - viewOffset: 50, - viewPosition: 0.5, + requestAnimationFrame(() => { + this.flatListRef.current.scrollToIndex({ + animated: false, + index: this.props.initialIndex, + viewOffset: 0, + viewPosition: 1, // 0 is at bottom + }); }); this.hasDoneInitialScroll = true; } diff --git a/app/reducers/views/channel.js b/app/reducers/views/channel.js index 87fd7f930..3ffce2343 100644 --- a/app/reducers/views/channel.js +++ b/app/reducers/views/channel.js @@ -266,7 +266,11 @@ function postVisibility(state = {}, action) { } case ViewTypes.INCREASE_POST_VISIBILITY: { const nextState = {...state}; - nextState[action.data] += action.amount; + if (nextState[action.data]) { + nextState[action.data] += action.amount; + } else { + nextState[action.data] = action.amount; + } return nextState; } case ViewTypes.RECEIVED_FOCUSED_POST: { diff --git a/app/reducers/views/channel.test.js b/app/reducers/views/channel.test.js new file mode 100644 index 000000000..e5fa05ff3 --- /dev/null +++ b/app/reducers/views/channel.test.js @@ -0,0 +1,112 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import channelReducer from './channel'; +import {ViewTypes} from 'app/constants'; + +describe('Reducers.channel', () => { + const initialState = { + displayName: '', + drafts: {}, + loading: false, + refreshing: false, + postCountInChannel: {}, + postVisibility: {}, + loadingPosts: {}, + lastGetPosts: {}, + retryFailed: false, + loadMorePostsVisible: true, + lastChannelViewTime: {}, + keepChannelIdAsUnread: null, + }; + + test('Initial state', () => { + const nextState = channelReducer( + { + displayName: '', + drafts: {}, + loading: false, + refreshing: false, + postCountInChannel: {}, + postVisibility: {}, + loadingPosts: {}, + lastGetPosts: {}, + retryFailed: false, + loadMorePostsVisible: true, + lastChannelViewTime: {}, + keepChannelIdAsUnread: null, + }, + {} + ); + + expect(nextState).toEqual(initialState); + }); + + test('should set the postVisibility amount for a channel', () => { + const channelId = 'channel_id'; + const amount = 15; + const nextState = channelReducer( + { + displayName: '', + drafts: {}, + loading: false, + refreshing: false, + postCountInChannel: {}, + postVisibility: {}, + loadingPosts: {}, + lastGetPosts: {}, + retryFailed: false, + loadMorePostsVisible: true, + lastChannelViewTime: {}, + keepChannelIdAsUnread: null, + }, + { + type: ViewTypes.INCREASE_POST_VISIBILITY, + data: channelId, + amount, + } + ); + + expect(nextState).toEqual({ + ...initialState, + postVisibility: { + [channelId]: amount, + }, + }); + }); + + test('should increase the postVisibility amount for a channel', () => { + const channelId = 'channel_id'; + const amount = 15; + const nextState = channelReducer( + { + displayName: '', + drafts: {}, + loading: false, + refreshing: false, + postCountInChannel: {}, + postVisibility: { + [channelId]: amount, + }, + loadingPosts: {}, + lastGetPosts: {}, + retryFailed: false, + loadMorePostsVisible: true, + lastChannelViewTime: {}, + keepChannelIdAsUnread: null, + }, + { + type: ViewTypes.INCREASE_POST_VISIBILITY, + data: channelId, + amount, + } + ); + + expect(nextState).toEqual({ + ...initialState, + postVisibility: { + [channelId]: 2 * amount, + }, + }); + }); +}); From 0ff6dba3c86e88b53fcee19d12e82cd4601ed862 Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Tue, 2 Jul 2019 14:20:48 -0400 Subject: [PATCH 28/28] MM-16477 Check for modified users on reconnect (#2936) * MM-16477 Move websocket status to redux * MM-16477 Check for modified users on reconnect * Update mattermost-redux * Update mattermost-redux --- app/actions/views/channel.js | 2 +- app/reducers/device/index.js | 2 -- app/reducers/device/websocket.js | 34 -------------------------------- app/store/middleware.js | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- packager/moduleNames.js | 1 - packager/modulePaths.js | 1 - 8 files changed, 12 insertions(+), 42 deletions(-) delete mode 100644 app/reducers/device/websocket.js diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index 32418fd2e..687d1178d 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -198,7 +198,7 @@ export function loadPostsIfNecessaryWithRetry(channelId) { }); } } else { - const {lastConnectAt} = state.device.websocket; + const {lastConnectAt} = state.websocket; const lastGetPosts = state.views.channel.lastGetPosts[channelId]; let since; diff --git a/app/reducers/device/index.js b/app/reducers/device/index.js index 14a8e351d..5145f3e85 100644 --- a/app/reducers/device/index.js +++ b/app/reducers/device/index.js @@ -8,7 +8,6 @@ import dimension from './dimension'; import isTablet from './is_tablet'; import orientation from './orientation'; import statusBarHeight from './status_bar'; -import websocket from './websocket'; export default combineReducers({ connection, @@ -16,5 +15,4 @@ export default combineReducers({ isTablet, orientation, statusBarHeight, - websocket, }); diff --git a/app/reducers/device/websocket.js b/app/reducers/device/websocket.js deleted file mode 100644 index 803e8fdde..000000000 --- a/app/reducers/device/websocket.js +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. -// See LICENSE.txt for license information. - -import {GeneralTypes, UserTypes} from 'mattermost-redux/action_types'; - -function getInitialState() { - return { - connected: false, - lastConnectAt: 0, - lastDisconnectAt: 0, - }; -} - -export default function(state = getInitialState(), action) { - if (!state.connected && action.type === GeneralTypes.WEBSOCKET_SUCCESS) { - return { - ...state, - connected: true, - lastConnectAt: new Date().getTime(), - }; - } else if (state.connected && (action.type === GeneralTypes.WEBSOCKET_FAILURE || action.type === GeneralTypes.WEBSOCKET_CLOSED)) { - return { - ...state, - connected: false, - lastDisconnectAt: new Date().getTime(), - }; - } - - if (action.type === UserTypes.LOGOUT_SUCCESS) { - return getInitialState(); - } - - return state; -} diff --git a/app/store/middleware.js b/app/store/middleware.js index 6910aeef3..05a8cbbb7 100644 --- a/app/store/middleware.js +++ b/app/store/middleware.js @@ -172,6 +172,10 @@ function resetStateForNewVersion(action) { selectServer, recentEmojis, }, + websocket: { + lastConnectAt: payload.websocket?.lastConnectAt, + lastDisconnectAt: payload.websocket?.lastDisconnectAt, + }, }; return { @@ -328,6 +332,10 @@ export function cleanUpState(action, keepCurrent = false) { ...payload.views.channel, }, }, + websocket: { + lastConnectAt: payload.websocket?.lastConnectAt, + lastDisconnectAt: payload.websocket?.lastDisconnectAt, + }, }; nextState.errors = payload.errors; diff --git a/package-lock.json b/package-lock.json index b37b97a3e..d8a94641b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13155,8 +13155,8 @@ "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" }, "mattermost-redux": { - "version": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", - "from": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", + "version": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7", + "from": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7", "requires": { "deep-equal": "1.0.1", "eslint-plugin-header": "3.0.0", diff --git a/package.json b/package.json index e99f64bb7..f621eeb05 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "intl": "1.2.5", "jail-monkey": "2.2.0", "jsc-android": "241213.2.0", - "mattermost-redux": "github:mattermost/mattermost-redux#f9b616dfbb532e1a196be67744e2277366532a73", + "mattermost-redux": "github:mattermost/mattermost-redux#66dcea7c71de9f87e3fe12652458e16f9517f6b7", "mime-db": "1.40.0", "moment-timezone": "0.5.25", "prop-types": "15.7.2", diff --git a/packager/moduleNames.js b/packager/moduleNames.js index 1263945e9..7e79e8e76 100644 --- a/packager/moduleNames.js +++ b/packager/moduleNames.js @@ -32,7 +32,6 @@ module.exports = [ 'app/reducers/device/is_tablet.js', 'app/reducers/device/orientation.js', 'app/reducers/device/status_bar.js', - 'app/reducers/device/websocket.js', 'app/reducers/index.js', 'app/reducers/navigation/index.js', 'app/reducers/views/announcement.js', diff --git a/packager/modulePaths.js b/packager/modulePaths.js index 301f51f92..cef5a4ff2 100644 --- a/packager/modulePaths.js +++ b/packager/modulePaths.js @@ -31,7 +31,6 @@ module.exports = ['./node_modules/app/components/loading.js', './node_modules/app/reducers/device/is_tablet.js', './node_modules/app/reducers/device/orientation.js', './node_modules/app/reducers/device/status_bar.js', - './node_modules/app/reducers/device/websocket.js', './node_modules/app/reducers/index.js', './node_modules/app/reducers/navigation/index.js', './node_modules/app/reducers/views/announcement.js',