diff --git a/app/actions/navigation/index.js b/app/actions/navigation/index.js index 2e27c7591..9aa0385ca 100644 --- a/app/actions/navigation/index.js +++ b/app/actions/navigation/index.js @@ -5,6 +5,7 @@ import {Keyboard, Platform} from 'react-native'; import {Navigation} from 'react-native-navigation'; import merge from 'deepmerge'; +import {Preferences} from '@mm-redux/constants'; import {getTheme} from '@mm-redux/selectors/entities/preferences'; import EphemeralStore from '@store/ephemeral_store'; @@ -83,7 +84,7 @@ export function resetToChannel(passProps = {}) { } export function resetToSelectServer(allowOtherServers) { - const theme = getThemeFromState(); + const theme = Preferences.THEMES.default; Navigation.setRoot({ root: { diff --git a/app/init/global_event_handler.js b/app/init/global_event_handler.js index c9287958e..524cc21c2 100644 --- a/app/init/global_event_handler.js +++ b/app/init/global_event_handler.js @@ -157,33 +157,35 @@ class GlobalEventHandler { emmProvider.handleManagedConfig(true); }; - onLogout = async () => { - Store.redux.dispatch(closeWebSocket(false)); - Store.redux.dispatch(setServerVersion('')); - - if (analytics) { - await analytics.reset(); - } - - removeAppCredentials(); - deleteFileCache(); - resetMomentLocale(); - - // TODO: Handle when multi-server support is added + clearCookiesAndWebData = async () => { try { await CookieManager.clearAll(Platform.OS === 'ios'); } catch (error) { // Nothing to clear } - PushNotifications.clearNotifications(); - const cacheDir = RNFetchBlob.fs.dirs.CacheDir; - const mainPath = cacheDir.split('/').slice(0, -1).join('/'); - mattermostBucket.removePreference('cert'); - mattermostBucket.removePreference('emm'); - if (Platform.OS === 'ios') { - mattermostBucket.removeFile('entities'); - } else { + switch (Platform.OS) { + case 'ios': { + const mainPath = RNFetchBlob.fs.dirs.DocumentDir.split('/').slice(0, -1).join('/'); + const libraryDir = `${mainPath}/Library`; + const cookiesDir = `${libraryDir}/Cookies`; + const cookies = await RNFetchBlob.fs.exists(cookiesDir); + const webkitDir = `${libraryDir}/WebKit`; + const webkit = await RNFetchBlob.fs.exists(webkitDir); + + if (cookies) { + RNFetchBlob.fs.unlink(cookiesDir); + } + + if (webkit) { + RNFetchBlob.fs.unlink(webkitDir); + } + break; + } + + case 'android': { + const cacheDir = RNFetchBlob.fs.dirs.CacheDir; + const mainPath = cacheDir.split('/').slice(0, -1).join('/'); const cookies = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies`); const cookiesJ = await RNFetchBlob.fs.exists(`${mainPath}/app_webview/Cookies-journal`); if (cookies) { @@ -193,7 +195,31 @@ class GlobalEventHandler { if (cookiesJ) { RNFetchBlob.fs.unlink(`${mainPath}/app_webview/Cookies-journal`); } + break; } + } + }; + + onLogout = async () => { + Store.redux.dispatch(closeWebSocket(false)); + Store.redux.dispatch(setServerVersion('')); + + if (analytics) { + await analytics.reset(); + } + + mattermostBucket.removePreference('cert'); + mattermostBucket.removePreference('emm'); + if (Platform.OS === 'ios') { + mattermostBucket.removeFile('entities'); + } + + removeAppCredentials(); + deleteFileCache(); + resetMomentLocale(); + + await this.clearCookiesAndWebData(); + PushNotifications.clearNotifications(); if (this.launchApp) { this.launchApp(); diff --git a/app/screens/login_options/login_options.js b/app/screens/login_options/login_options.js index 6373a5d93..d4fccd769 100644 --- a/app/screens/login_options/login_options.js +++ b/app/screens/login_options/login_options.js @@ -7,13 +7,11 @@ import {intlShape} from 'react-intl'; import { Dimensions, Image, - Platform, ScrollView, StyleSheet, Text, } from 'react-native'; import Button from 'react-native-button'; -import CookieManager from '@react-native-community/cookies'; import {goToScreen} from '@actions/navigation'; import LocalConfig from '@assets/config'; @@ -23,6 +21,7 @@ import FormattedText from '@components/formatted_text'; import StatusBar from '@components/status_bar'; import {paddingHorizontal as padding} from '@components/safe_area_view/iphone_x_spacing'; import {ViewTypes} from '@constants'; +import globalEventHandler from '@init/global_event_handler'; import {preventDoubleTap} from '@utils/tap'; import {GlobalStyles} from 'app/styles'; @@ -50,11 +49,8 @@ export default class LoginOptions extends PureComponent { const {intl} = this.context; const screen = 'Login'; const title = intl.formatMessage({id: 'mobile.routes.login', defaultMessage: 'Login'}); - try { - await CookieManager.clearAll(Platform.OS === 'ios'); - } catch (error) { - // Nothing to clear - } + + globalEventHandler.clearCookiesAndWebData(); goToScreen(screen, title); }); @@ -62,11 +58,8 @@ export default class LoginOptions extends PureComponent { const {intl} = this.context; const screen = 'SSO'; const title = intl.formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'}); - try { - await CookieManager.clearAll(Platform.OS === 'ios'); - } catch (error) { - // Nothing to clear - } + + globalEventHandler.clearCookiesAndWebData(); goToScreen(screen, title, {ssoType}); }; diff --git a/app/screens/sso/sso.js b/app/screens/sso/sso.js index 9acef9918..7f4c7ef5f 100644 --- a/app/screens/sso/sso.js +++ b/app/screens/sso/sso.js @@ -27,7 +27,7 @@ const HEADERS = { 'X-Mobile-App': 'mattermost', }; -const postMessageJS = "window.ReactNativeWebView.postMessage(document.body.innerText, '*');"; +const postMessageJS = 'window.ReactNativeWebView.postMessage(document.body.innerText);'; // Used to make sure that OneLogin forms scale appropriately on both platforms. const oneLoginFormScalingJS = ` @@ -104,6 +104,44 @@ class SSO extends PureComponent { } } + componentWillUnmount() { + clearTimeout(this.cookiesTimeout); + } + + extractCookie = (parsedUrl) => { + CookieManager.get(parsedUrl.origin, true).then((res) => { + const mmtoken = res.MMAUTHTOKEN; + const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken; + + if (token) { + clearTimeout(this.cookiesTimeout); + this.setState({renderWebView: false}); + const { + ssoLogin, + } = this.props.actions; + + Client4.setToken(token); + if (this.props.serverUrl !== parsedUrl.origin) { + const original = urlParse(this.props.serverUrl); + + // Check whether we need to set a sub-path + parsedUrl.set('pathname', original.pathname || ''); + Client4.setUrl(parsedUrl.href); + } + ssoLogin(token).then((result) => { + if (result.error) { + this.onLoadEndError(result.error); + return; + } + this.goToChannel(); + }); + } else if (this.webView && !this.state.error) { + this.webView.injectJavaScript(postMessageJS); + this.cookiesTimeout = setTimeout(this.extractCookie.bind(null, parsedUrl), 250); + } + }); + } + goToChannel = () => { tracker.initialLoad = Date.now(); @@ -122,6 +160,7 @@ class SSO extends PureComponent { status_code: statusCode, } = response; if (id && message && statusCode !== 200) { + clearTimeout(this.cookiesTimeout); this.setState({error: message}); } } @@ -158,35 +197,7 @@ class SSO extends PureComponent { } if (isLastRedirect) { - CookieManager.get(parsed.origin, this.useWebkit).then((res) => { - const mmtoken = res.MMAUTHTOKEN; - const token = typeof mmtoken === 'object' ? mmtoken.value : mmtoken; - - if (token) { - this.setState({renderWebView: false}); - const { - ssoLogin, - } = this.props.actions; - - Client4.setToken(token); - if (this.props.serverUrl !== parsed.origin) { - const original = urlParse(this.props.serverUrl); - - // Check whether we need to set a sub-path - parsed.set('pathname', original.pathname || ''); - Client4.setUrl(parsed.href); - } - ssoLogin(token).then((result) => { - if (result.error) { - this.onLoadEndError(result.error); - return; - } - this.goToChannel(); - }); - } else if (this.webView && !this.state.error) { - this.webView.injectJavaScript(postMessageJS); - } - }); + this.extractCookie(parsed); } }; @@ -235,9 +246,10 @@ class SSO extends PureComponent { onShouldStartLoadWithRequest={() => true} injectedJavaScript={jsCode} onLoadEnd={this.onLoadEnd} - onMessage={messagingEnabled ? this.onMessage : null} + onMessage={(messagingEnabled || Platform.OS === 'android') ? this.onMessage : null} sharedCookiesEnabled={Platform.OS === 'android'} cacheEnabled={false} + useSharedProcessPool={false} /> ); } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c05b5b763..e54b8a088 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -207,7 +207,7 @@ PODS: - React-jsinspector (0.62.2) - react-native-cameraroll (1.6.2): - React - - react-native-cookies (2.0.9): + - react-native-cookies (3.0.0): - React - react-native-document-picker (3.4.0): - React @@ -597,7 +597,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 1540d1c01bb493ae3124ed83351b1b6a155db7da React-jsinspector: 512e560d0e985d0e8c479a54a4e5c147a9c83493 react-native-cameraroll: 94bec91c68b94ac946c61b497b594bb38692c41b - react-native-cookies: bc1f91a504ee091f1998a1b6c7ff4b7a9ed5b80e + react-native-cookies: 6fe6a70d3b2fd8e81ddcd94d60a87727010d9b2e react-native-document-picker: d694111879537cec2c258a1dcd2243d9df746824 react-native-hw-keyboard-event: b517cefb8d5c659a38049c582de85ff43337dc53 react-native-image-picker: 668e72d0277dc8c12ae90e835507c1eddd2e4f85 diff --git a/package-lock.json b/package-lock.json index 8d579d44b..4a8ca8d95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5223,9 +5223,9 @@ } }, "@react-native-community/cookies": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@react-native-community/cookies/-/cookies-2.0.9.tgz", - "integrity": "sha512-gZvnLQjkpZX//4/CIOX4lOFdfOT2gQi/Ov1t+K6D2KZ0GyqY1CQ9xe0F1NsRSIGciTUkxOPTeway/LRWgwHsxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cookies/-/cookies-3.0.0.tgz", + "integrity": "sha512-ms/CsrBlHffTyTRhXsKSmSK6WlWScG8wklfSmkr4WeeA0j0AQ1sknTg8YNsK0bMr6Ij1uDuvX9t96vZrFGpYqQ==", "requires": { "invariant": "^2.2.4" } diff --git a/package.json b/package.json index 3e9564740..ec55275fa 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "@babel/runtime": "7.9.6", "@react-native-community/async-storage": "1.10.1", "@react-native-community/cameraroll": "1.6.2", - "@react-native-community/cookies": "2.0.9", + "@react-native-community/cookies": "3.0.0", "@react-native-community/masked-view": "0.1.10", "@react-native-community/netinfo": "5.8.1", "@react-navigation/native": "5.3.2", diff --git a/patches/@react-native-community+cookies+2.0.9.patch b/patches/@react-native-community+cookies+2.0.9.patch deleted file mode 100644 index 197e61096..000000000 --- a/patches/@react-native-community+cookies+2.0.9.patch +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m b/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m -index 9cdc74d..5477405 100644 ---- a/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m -+++ b/node_modules/@react-native-community/cookies/ios/RNCookieManagerIOS/RNCookieManagerIOS.m -@@ -139,7 +139,8 @@ + (BOOL)requiresMainQueueSetup - [cookieStore getAllCookies:^(NSArray *allCookies) { - NSMutableDictionary *cookies = [NSMutableDictionary dictionary]; - for (NSHTTPCookie *cookie in allCookies) { -- if ([topLevelDomain containsString:cookie.domain]) { -+ NSString *domainWithDot = [NSString stringWithFormat:@".%@", cookie.domain]; -+ if([cookie.domain containsString:topLevelDomain] || [domainWithDot containsString:topLevelDomain]) { - [cookies setObject:[self createCookieData:cookie] forKey:cookie.name]; - } - } -@@ -185,6 +186,8 @@ + (BOOL)requiresMainQueueSetup - } - resolve(nil); - } -+ -+ [self deleteBinaryCookiesAndWebsiteData]; - } - - RCT_EXPORT_METHOD(clearByName:(NSString *) name -@@ -244,4 +247,15 @@ -(NSDictionary *)createCookieData:(NSHTTPCookie *)cookie - return cookieData; - } - -+-(void) deleteBinaryCookiesAndWebsiteData { -+ NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0]; -+ NSString *cookiesPath = [libraryPath stringByAppendingString:@"/Cookies/Cookies.binarycookies"]; -+ NSString *dataPath = [libraryPath stringByAppendingString:@"/WebKit/WebsiteData"]; -+ -+ NSError *error; -+ NSFileManager *fileManager = [NSFileManager defaultManager]; -+ [fileManager removeItemAtPath:cookiesPath error:&error]; -+ [fileManager removeItemAtPath:dataPath error:&error]; -+} -+ - @end diff --git a/test/setup.js b/test/setup.js index ab4b8b5c6..72a5916c7 100644 --- a/test/setup.js +++ b/test/setup.js @@ -122,7 +122,7 @@ jest.mock('react-native-device-info', () => { jest.mock('rn-fetch-blob', () => ({ fs: { dirs: { - DocumentDir: () => jest.fn(), + DocumentDir: '/data/com.mattermost.beta/documents', CacheDir: '/data/com.mattermost.beta/cache', }, exists: jest.fn(),