Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e7733cce | ||
|
|
e978b3abf9 | ||
|
|
aaa9ee9af7 | ||
|
|
c31b5172e7 | ||
|
|
d91023d88e | ||
|
|
19009904a9 |
20 changed files with 461 additions and 144 deletions
|
|
@ -111,8 +111,8 @@ android {
|
||||||
applicationId "com.mattermost.rnbeta"
|
applicationId "com.mattermost.rnbeta"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 557
|
versionCode 565
|
||||||
versionName "2.20.0"
|
versionName "2.21.0"
|
||||||
testBuildType System.getProperty('testBuildType', 'debug')
|
testBuildType System.getProperty('testBuildType', 'debug')
|
||||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {nativeApplicationVersion} from 'expo-application';
|
||||||
|
import {RESULTS, checkNotifications} from 'react-native-permissions';
|
||||||
|
|
||||||
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, type MyChannelsRequest} from '@actions/remote/channel';
|
import {fetchMissingDirectChannelsInfo, fetchMyChannelsForTeam, handleKickFromChannel, type MyChannelsRequest} from '@actions/remote/channel';
|
||||||
import {fetchGroupsForMember} from '@actions/remote/groups';
|
import {fetchGroupsForMember} from '@actions/remote/groups';
|
||||||
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
|
import {fetchPostsForUnreadChannels} from '@actions/remote/post';
|
||||||
|
|
@ -22,12 +25,12 @@ import {getDeviceToken} from '@queries/app/global';
|
||||||
import {getChannelById, queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
|
import {getChannelById, queryAllChannelsForTeam, queryChannelsById} from '@queries/servers/channel';
|
||||||
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
|
import {prepareModels, truncateCrtRelatedTables} from '@queries/servers/entry';
|
||||||
import {getHasCRTChanged} from '@queries/servers/preference';
|
import {getHasCRTChanged} from '@queries/servers/preference';
|
||||||
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getLastFullSync, setCurrentTeamAndChannelId} from '@queries/servers/system';
|
import {getConfig, getCurrentChannelId, getCurrentTeamId, getIsDataRetentionEnabled, getPushVerificationStatus, getLastFullSync, setCurrentTeamAndChannelId, getConfigValue} from '@queries/servers/system';
|
||||||
import {deleteMyTeams, getAvailableTeamIds, getTeamChannelHistory, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
|
import {deleteMyTeams, getAvailableTeamIds, getTeamChannelHistory, queryMyTeams, queryMyTeamsByIds, queryTeamsById} from '@queries/servers/team';
|
||||||
import NavigationStore from '@store/navigation_store';
|
import NavigationStore from '@store/navigation_store';
|
||||||
import {isDMorGM, sortChannelsByDisplayName} from '@utils/channel';
|
import {isDMorGM, sortChannelsByDisplayName} from '@utils/channel';
|
||||||
import {getFullErrorMessage, isErrorWithStatusCode} from '@utils/errors';
|
import {getFullErrorMessage, isErrorWithStatusCode} from '@utils/errors';
|
||||||
import {isTablet} from '@utils/helpers';
|
import {isMinimumServerVersion, isTablet} from '@utils/helpers';
|
||||||
import {logDebug} from '@utils/log';
|
import {logDebug} from '@utils/log';
|
||||||
import {processIsCRTEnabled} from '@utils/thread';
|
import {processIsCRTEnabled} from '@utils/thread';
|
||||||
|
|
||||||
|
|
@ -413,17 +416,25 @@ async function restDeferredAppEntryActions(
|
||||||
}, FETCH_MISSING_DM_TIMEOUT);
|
}, FETCH_MISSING_DM_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerDeviceToken = async (serverUrl: string) => {
|
export const setExtraSessionProps = async (serverUrl: string) => {
|
||||||
try {
|
try {
|
||||||
const client = NetworkManager.getClient(serverUrl);
|
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||||
|
const serverVersion = await getConfigValue(database, 'Version');
|
||||||
const deviceToken = await getDeviceToken();
|
const deviceToken = await getDeviceToken();
|
||||||
if (deviceToken) {
|
|
||||||
client.attachDevice(deviceToken);
|
// For new servers, we want to send all the information.
|
||||||
|
// For old servers, we only want to send the information when there
|
||||||
|
// is a device token. Sending the rest of the information should not
|
||||||
|
// create any issue.
|
||||||
|
if (isMinimumServerVersion(serverVersion, 10, 1, 0) || deviceToken) {
|
||||||
|
const res = await checkNotifications();
|
||||||
|
const granted = res.status === RESULTS.GRANTED || res.status === RESULTS.LIMITED;
|
||||||
|
const client = NetworkManager.getClient(serverUrl);
|
||||||
|
client.setExtraSessionProps(deviceToken, !granted, nativeApplicationVersion);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logDebug('error on registerDeviceToken', getFullErrorMessage(error));
|
logDebug('error on setExtraSessionProps', getFullErrorMessage(error));
|
||||||
return {error};
|
return {error};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
deferredAppEntryActions,
|
deferredAppEntryActions,
|
||||||
entry,
|
entry,
|
||||||
handleEntryAfterLoadNavigation,
|
handleEntryAfterLoadNavigation,
|
||||||
registerDeviceToken,
|
setExtraSessionProps,
|
||||||
} from '@actions/remote/entry/common';
|
} from '@actions/remote/entry/common';
|
||||||
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
|
import {fetchPostsForChannel, fetchPostThread} from '@actions/remote/post';
|
||||||
import {openAllUnreadChannels} from '@actions/remote/preference';
|
import {openAllUnreadChannels} from '@actions/remote/preference';
|
||||||
|
|
@ -37,7 +37,7 @@ import {isTablet} from '@utils/helpers';
|
||||||
import {logDebug, logInfo} from '@utils/log';
|
import {logDebug, logInfo} from '@utils/log';
|
||||||
|
|
||||||
export async function handleFirstConnect(serverUrl: string) {
|
export async function handleFirstConnect(serverUrl: string) {
|
||||||
registerDeviceToken(serverUrl);
|
setExtraSessionProps(serverUrl);
|
||||||
autoUpdateTimezone(serverUrl);
|
autoUpdateTimezone(serverUrl);
|
||||||
return doReconnect(serverUrl);
|
return doReconnect(serverUrl);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ export interface ClientUsersMix {
|
||||||
autocompleteUsers: (name: string, teamId: string, channelId?: string, options?: Record<string, any>) => Promise<{users: UserProfile[]; out_of_channel?: UserProfile[]}>;
|
autocompleteUsers: (name: string, teamId: string, channelId?: string, options?: Record<string, any>) => Promise<{users: UserProfile[]; out_of_channel?: UserProfile[]}>;
|
||||||
getSessions: (userId: string) => Promise<Session[]>;
|
getSessions: (userId: string) => Promise<Session[]>;
|
||||||
checkUserMfa: (loginId: string) => Promise<{mfa_required: boolean}>;
|
checkUserMfa: (loginId: string) => Promise<{mfa_required: boolean}>;
|
||||||
attachDevice: (deviceId: string) => Promise<any>;
|
setExtraSessionProps: (deviceId: string, notificationsEnabled: boolean, version: string | null) => Promise<{}>;
|
||||||
searchUsers: (term: string, options: SearchUserOptions) => Promise<UserProfile[]>;
|
searchUsers: (term: string, options: SearchUserOptions) => Promise<UserProfile[]>;
|
||||||
getStatusesByIds: (userIds: string[]) => Promise<UserStatus[]>;
|
getStatusesByIds: (userIds: string[]) => Promise<UserStatus[]>;
|
||||||
getStatus: (userId: string) => Promise<UserStatus>;
|
getStatus: (userId: string) => Promise<UserStatus>;
|
||||||
|
|
@ -325,10 +325,17 @@ const ClientUsers = <TBase extends Constructor<ClientBase>>(superclass: TBase) =
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
attachDevice = async (deviceId: string) => {
|
setExtraSessionProps = async (deviceId: string, deviceNotificationDisabled: boolean, version: string | null) => {
|
||||||
return this.doFetch(
|
return this.doFetch(
|
||||||
`${this.getUsersRoute()}/sessions/device`,
|
`${this.getUsersRoute()}/sessions/device`,
|
||||||
{method: 'put', body: {device_id: deviceId}},
|
{
|
||||||
|
method: 'put',
|
||||||
|
body: {
|
||||||
|
device_id: deviceId,
|
||||||
|
device_notification_disabled: deviceNotificationDisabled ? 'true' : 'false',
|
||||||
|
mobile_version: version || '',
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ type ConditionalProps = | {iconName: string; iconSize: number} | {iconName?: nev
|
||||||
type Props = ConditionalProps & {
|
type Props = ConditionalProps & {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
backgroundStyle?: StyleProp<ViewStyle>;
|
backgroundStyle?: StyleProp<ViewStyle>;
|
||||||
|
buttonContainerStyle?: StyleProp<ViewStyle>;
|
||||||
textStyle?: StyleProp<TextStyle>;
|
textStyle?: StyleProp<TextStyle>;
|
||||||
size?: ButtonSize;
|
size?: ButtonSize;
|
||||||
emphasis?: ButtonEmphasis;
|
emphasis?: ButtonEmphasis;
|
||||||
|
|
@ -25,7 +26,7 @@ type Props = ConditionalProps & {
|
||||||
iconComponent?: ReactNode;
|
iconComponent?: ReactNode;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
hitSlop?: Insets;
|
hitSlop?: Insets;
|
||||||
}
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {flexDirection: 'row'},
|
container: {flexDirection: 'row'},
|
||||||
|
|
@ -35,6 +36,7 @@ const styles = StyleSheet.create({
|
||||||
const Button = ({
|
const Button = ({
|
||||||
theme,
|
theme,
|
||||||
backgroundStyle,
|
backgroundStyle,
|
||||||
|
buttonContainerStyle,
|
||||||
textStyle,
|
textStyle,
|
||||||
size,
|
size,
|
||||||
emphasis,
|
emphasis,
|
||||||
|
|
@ -59,7 +61,7 @@ const Button = ({
|
||||||
textStyle,
|
textStyle,
|
||||||
], [theme, textStyle, size, emphasis, buttonType]);
|
], [theme, textStyle, size, emphasis, buttonType]);
|
||||||
|
|
||||||
const containerStyle = useMemo(
|
const textContainerStyle = useMemo(
|
||||||
() =>
|
() =>
|
||||||
(iconSize ? [
|
(iconSize ? [
|
||||||
styles.container,
|
styles.container,
|
||||||
|
|
@ -68,11 +70,11 @@ const Button = ({
|
||||||
[iconSize],
|
[iconSize],
|
||||||
);
|
);
|
||||||
|
|
||||||
let buttonContainerStyle = StyleSheet.flatten(bgStyle);
|
let buttonStyle = StyleSheet.flatten(bgStyle);
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
buttonContainerStyle = {
|
buttonStyle = {
|
||||||
...buttonContainerStyle,
|
...buttonStyle,
|
||||||
backgroundColor: changeOpacity(buttonContainerStyle.backgroundColor! as string, 0.4),
|
backgroundColor: changeOpacity(buttonStyle.backgroundColor! as string, 0.4),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,13 +95,14 @@ const Button = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ElementButton
|
<ElementButton
|
||||||
buttonStyle={buttonContainerStyle}
|
buttonStyle={buttonStyle}
|
||||||
|
containerStyle={buttonContainerStyle}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
hitSlop={hitSlop}
|
hitSlop={hitSlop}
|
||||||
>
|
>
|
||||||
<View style={containerStyle}>
|
<View style={textContainerStyle}>
|
||||||
{icon}
|
{icon}
|
||||||
<Text
|
<Text
|
||||||
style={txtStyle}
|
style={txtStyle}
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
|
leftButton: {
|
||||||
|
flex: 1,
|
||||||
|
marginRight: 5,
|
||||||
|
},
|
||||||
|
rightButton: {
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: 5,
|
||||||
|
},
|
||||||
close: {
|
close: {
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
height: 44,
|
height: 44,
|
||||||
|
|
@ -83,14 +91,6 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
leftButton: {
|
|
||||||
flex: 1,
|
|
||||||
marginRight: 5,
|
|
||||||
},
|
|
||||||
rightButton: {
|
|
||||||
flex: 1,
|
|
||||||
marginLeft: 5,
|
|
||||||
},
|
|
||||||
dontAsk: {
|
dontAsk: {
|
||||||
...typography('Body', 75, 'SemiBold'),
|
...typography('Body', 75, 'SemiBold'),
|
||||||
color: theme.buttonBg,
|
color: theme.buttonBg,
|
||||||
|
|
@ -204,14 +204,14 @@ const ReviewApp = ({
|
||||||
emphasis={'tertiary'}
|
emphasis={'tertiary'}
|
||||||
onPress={onPressNeedsWork}
|
onPress={onPressNeedsWork}
|
||||||
text={intl.formatMessage({id: 'rate.button.needs_work', defaultMessage: 'Needs work'})}
|
text={intl.formatMessage({id: 'rate.button.needs_work', defaultMessage: 'Needs work'})}
|
||||||
backgroundStyle={styles.leftButton}
|
buttonContainerStyle={styles.leftButton}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
theme={theme}
|
theme={theme}
|
||||||
size={'lg'}
|
size={'lg'}
|
||||||
onPress={onPressYes}
|
onPress={onPressYes}
|
||||||
text={intl.formatMessage({id: 'rate.button.yes', defaultMessage: 'Love it!'})}
|
text={intl.formatMessage({id: 'rate.button.yes', defaultMessage: 'Love it!'})}
|
||||||
backgroundStyle={styles.rightButton}
|
buttonContainerStyle={styles.rightButton}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{hasAskedBefore && (
|
{hasAskedBefore && (
|
||||||
|
|
|
||||||
75
app/screens/sso/components/auth_error.tsx
Normal file
75
app/screens/sso/components/auth_error.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {Button} from '@rneui/base';
|
||||||
|
import React from 'react';
|
||||||
|
import {Text, View} from 'react-native';
|
||||||
|
|
||||||
|
import FormattedText from '@components/formatted_text';
|
||||||
|
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
|
||||||
|
interface AuthErrorProps {
|
||||||
|
error: string;
|
||||||
|
retry: () => void;
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
button: {
|
||||||
|
marginTop: 25,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
textAlign: 'center',
|
||||||
|
...typography('Body', 200, 'Regular'),
|
||||||
|
},
|
||||||
|
infoContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
...typography('Body', 100, 'Regular'),
|
||||||
|
},
|
||||||
|
infoTitle: {
|
||||||
|
color: theme.centerChannelColor,
|
||||||
|
marginBottom: 4,
|
||||||
|
...typography('Heading', 700),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const AuthError = ({error, retry, theme}: AuthErrorProps) => {
|
||||||
|
const style = getStyleSheet(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.infoContainer}>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.switch_to_browser.error_title'
|
||||||
|
testID='mobile.oauth.switch_to_browser.error_title'
|
||||||
|
defaultMessage='Sign in error'
|
||||||
|
style={style.infoTitle}
|
||||||
|
/>
|
||||||
|
<Text style={style.errorText}>
|
||||||
|
{`${error}.`}
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
buttonStyle={[style.button, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]}
|
||||||
|
testID='mobile.oauth.try_again'
|
||||||
|
onPress={retry}
|
||||||
|
>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.try_again'
|
||||||
|
defaultMessage='Try again'
|
||||||
|
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthError;
|
||||||
55
app/screens/sso/components/auth_redirect.tsx
Normal file
55
app/screens/sso/components/auth_redirect.tsx
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {View} from 'react-native';
|
||||||
|
|
||||||
|
import FormattedText from '@components/formatted_text';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
|
||||||
|
interface AuthRedirectProps {
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
infoContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
...typography('Body', 100, 'Regular'),
|
||||||
|
},
|
||||||
|
infoTitle: {
|
||||||
|
color: theme.centerChannelColor,
|
||||||
|
marginBottom: 4,
|
||||||
|
...typography('Heading', 700),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const AuthRedirect = ({theme}: AuthRedirectProps) => {
|
||||||
|
const style = getStyleSheet(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.infoContainer}>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.switch_to_browser.title'
|
||||||
|
testID='mobile.oauth.switch_to_browser.title'
|
||||||
|
defaultMessage='Redirecting...'
|
||||||
|
style={style.infoTitle}
|
||||||
|
/>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.switch_to_browser'
|
||||||
|
testID='mobile.oauth.switch_to_browser'
|
||||||
|
defaultMessage='You are being redirected to your login provider'
|
||||||
|
style={style.infoText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthRedirect;
|
||||||
57
app/screens/sso/components/auth_success.tsx
Normal file
57
app/screens/sso/components/auth_success.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {View} from 'react-native';
|
||||||
|
|
||||||
|
import FormattedText from '@components/formatted_text';
|
||||||
|
import Loading from '@components/loading';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
|
||||||
|
interface AuthSuccessProps {
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
infoContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
...typography('Body', 100, 'Regular'),
|
||||||
|
},
|
||||||
|
infoTitle: {
|
||||||
|
color: theme.centerChannelColor,
|
||||||
|
marginBottom: 4,
|
||||||
|
...typography('Heading', 700),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const AuthSuccess = ({theme}: AuthSuccessProps) => {
|
||||||
|
const style = getStyleSheet(theme);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.infoContainer}>
|
||||||
|
<Loading/>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.success.title'
|
||||||
|
testID='mobile.oauth.success.title'
|
||||||
|
defaultMessage='Authentication successful'
|
||||||
|
style={style.infoTitle}
|
||||||
|
/>
|
||||||
|
<FormattedText
|
||||||
|
id='mobile.oauth.success.description'
|
||||||
|
testID='mobile.oauth.success.description'
|
||||||
|
defaultMessage='Signing in now, just a moment...'
|
||||||
|
style={style.infoText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthSuccess;
|
||||||
|
|
@ -18,6 +18,7 @@ import {getFullErrorMessage, isErrorWithUrl} from '@utils/errors';
|
||||||
import {logWarning} from '@utils/log';
|
import {logWarning} from '@utils/log';
|
||||||
|
|
||||||
import SSOAuthentication from './sso_authentication';
|
import SSOAuthentication from './sso_authentication';
|
||||||
|
import SSOAuthenticationWithExternalBrowser from './sso_authentication_with_external_browser';
|
||||||
|
|
||||||
import type {LaunchProps} from '@typings/launch';
|
import type {LaunchProps} from '@typings/launch';
|
||||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||||
|
|
@ -155,14 +156,28 @@ const SSO = ({
|
||||||
theme,
|
theme,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let authentication;
|
||||||
|
if (config.MobileExternalBrowser === 'true') {
|
||||||
|
authentication = (
|
||||||
|
<SSOAuthenticationWithExternalBrowser
|
||||||
|
{...props}
|
||||||
|
serverUrl={serverUrl!}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
authentication = (
|
||||||
|
<SSOAuthentication
|
||||||
|
{...props}
|
||||||
|
serverUrl={serverUrl!}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.flex}>
|
<View style={styles.flex}>
|
||||||
<Background theme={theme}/>
|
<Background theme={theme}/>
|
||||||
<AnimatedSafeArea style={[styles.flex, transform]}>
|
<AnimatedSafeArea style={[styles.flex, transform]}>
|
||||||
<SSOAuthentication
|
{authentication}
|
||||||
{...props}
|
|
||||||
serverUrl={serverUrl!}
|
|
||||||
/>
|
|
||||||
</AnimatedSafeArea>
|
</AnimatedSafeArea>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,22 @@
|
||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import {Button} from '@rneui/base';
|
|
||||||
import {openAuthSessionAsync} from 'expo-web-browser';
|
import {openAuthSessionAsync} from 'expo-web-browser';
|
||||||
import qs from 'querystringify';
|
import qs from 'querystringify';
|
||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {useIntl} from 'react-intl';
|
import {useIntl} from 'react-intl';
|
||||||
import {Linking, Platform, Text, View, type EventSubscription} from 'react-native';
|
import {Linking, Platform, StyleSheet, View, type EventSubscription} from 'react-native';
|
||||||
import urlParse from 'url-parse';
|
import urlParse from 'url-parse';
|
||||||
|
|
||||||
import FormattedText from '@components/formatted_text';
|
|
||||||
import Loading from '@components/loading';
|
|
||||||
import {Sso} from '@constants';
|
import {Sso} from '@constants';
|
||||||
import NetworkManager from '@managers/network_manager';
|
import NetworkManager from '@managers/network_manager';
|
||||||
import {buttonBackgroundStyle, buttonTextStyle} from '@utils/buttonStyles';
|
|
||||||
import {isBetaApp} from '@utils/general';
|
import {isBetaApp} from '@utils/general';
|
||||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
|
||||||
import {typography} from '@utils/typography';
|
|
||||||
|
|
||||||
interface SSOWithRedirectURLProps {
|
import AuthError from './components/auth_error';
|
||||||
|
import AuthRedirect from './components/auth_redirect';
|
||||||
|
import AuthSuccess from './components/auth_success';
|
||||||
|
|
||||||
|
interface SSOAuthenticationProps {
|
||||||
doSSOLogin: (bearerToken: string, csrfToken: string) => void;
|
doSSOLogin: (bearerToken: string, csrfToken: string) => void;
|
||||||
loginError: string;
|
loginError: string;
|
||||||
loginUrl: string;
|
loginUrl: string;
|
||||||
|
|
@ -27,41 +25,16 @@ interface SSOWithRedirectURLProps {
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
const style = StyleSheet.create({
|
||||||
return {
|
container: {
|
||||||
button: {
|
flex: 1,
|
||||||
marginTop: 25,
|
paddingHorizontal: 24,
|
||||||
},
|
},
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
},
|
|
||||||
errorText: {
|
|
||||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
|
||||||
textAlign: 'center',
|
|
||||||
...typography('Body', 200, 'Regular'),
|
|
||||||
},
|
|
||||||
infoContainer: {
|
|
||||||
alignItems: 'center',
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
infoText: {
|
|
||||||
color: changeOpacity(theme.centerChannelColor, 0.72),
|
|
||||||
...typography('Body', 100, 'Regular'),
|
|
||||||
},
|
|
||||||
infoTitle: {
|
|
||||||
color: theme.centerChannelColor,
|
|
||||||
marginBottom: 4,
|
|
||||||
...typography('Heading', 700),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLoginError, theme}: SSOWithRedirectURLProps) => {
|
const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLoginError, theme}: SSOAuthenticationProps) => {
|
||||||
const [error, setError] = useState<string>('');
|
const [error, setError] = useState<string>('');
|
||||||
const [loginSuccess, setLoginSuccess] = useState(false);
|
const [loginSuccess, setLoginSuccess] = useState(false);
|
||||||
const style = getStyleSheet(theme);
|
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
|
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
|
||||||
if (isBetaApp) {
|
if (isBetaApp) {
|
||||||
|
|
@ -84,7 +57,7 @@ const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLogi
|
||||||
};
|
};
|
||||||
parsedUrl.set('query', qs.stringify(query));
|
parsedUrl.set('query', qs.stringify(query));
|
||||||
const url = parsedUrl.toString();
|
const url = parsedUrl.toString();
|
||||||
const result = await openAuthSessionAsync(url, null, {preferEphemeralSession: true});
|
const result = await openAuthSessionAsync(url, null, {preferEphemeralSession: true, createTask: false});
|
||||||
if ('url' in result && result.url) {
|
if ('url' in result && result.url) {
|
||||||
const resultUrl = urlParse(result.url, true);
|
const resultUrl = urlParse(result.url, true);
|
||||||
const bearerToken = resultUrl.query?.MMAUTHTOKEN;
|
const bearerToken = resultUrl.query?.MMAUTHTOKEN;
|
||||||
|
|
@ -142,65 +115,17 @@ const SSOAuthentication = ({doSSOLogin, loginError, loginUrl, serverUrl, setLogi
|
||||||
|
|
||||||
let content;
|
let content;
|
||||||
if (loginSuccess) {
|
if (loginSuccess) {
|
||||||
content = (
|
content = (<AuthSuccess theme={theme}/>);
|
||||||
<View style={style.infoContainer}>
|
|
||||||
<Loading/>
|
|
||||||
<FormattedText
|
|
||||||
id='mobile.oauth.success.title'
|
|
||||||
testID='mobile.oauth.success.title'
|
|
||||||
defaultMessage='Authentication successful'
|
|
||||||
style={style.infoTitle}
|
|
||||||
/>
|
|
||||||
<FormattedText
|
|
||||||
id='mobile.oauth.success.description'
|
|
||||||
testID='mobile.oauth.success.description'
|
|
||||||
defaultMessage='Signing in now, just a moment...'
|
|
||||||
style={style.infoText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
} else if (loginError || error) {
|
} else if (loginError || error) {
|
||||||
content = (
|
content = (
|
||||||
<View style={style.infoContainer}>
|
<AuthError
|
||||||
<FormattedText
|
error={loginError || error}
|
||||||
id='mobile.oauth.switch_to_browser.error_title'
|
retry={init}
|
||||||
testID='mobile.oauth.switch_to_browser.error_title'
|
theme={theme}
|
||||||
defaultMessage='Sign in error'
|
/>
|
||||||
style={style.infoTitle}
|
|
||||||
/>
|
|
||||||
<Text style={style.errorText}>
|
|
||||||
{`${loginError || error}.`}
|
|
||||||
</Text>
|
|
||||||
<Button
|
|
||||||
buttonStyle={[style.button, buttonBackgroundStyle(theme, 'lg', 'primary', 'default')]}
|
|
||||||
testID='mobile.oauth.try_again'
|
|
||||||
onPress={() => init()}
|
|
||||||
>
|
|
||||||
<FormattedText
|
|
||||||
id='mobile.oauth.try_again'
|
|
||||||
defaultMessage='Try again'
|
|
||||||
style={buttonTextStyle(theme, 'lg', 'primary', 'default')}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = (
|
content = (<AuthRedirect theme={theme}/>);
|
||||||
<View style={style.infoContainer}>
|
|
||||||
<FormattedText
|
|
||||||
id='mobile.oauth.switch_to_browser.title'
|
|
||||||
testID='mobile.oauth.switch_to_browser.title'
|
|
||||||
defaultMessage='Redirecting...'
|
|
||||||
style={style.infoTitle}
|
|
||||||
/>
|
|
||||||
<FormattedText
|
|
||||||
id='mobile.oauth.switch_to_browser'
|
|
||||||
testID='mobile.oauth.switch_to_browser'
|
|
||||||
defaultMessage='You are being redirected to your login provider'
|
|
||||||
style={style.infoText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
166
app/screens/sso/sso_authentication_with_external_browser.tsx
Normal file
166
app/screens/sso/sso_authentication_with_external_browser.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import qs from 'querystringify';
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {useIntl} from 'react-intl';
|
||||||
|
import {Linking, Platform, View} from 'react-native';
|
||||||
|
import urlParse from 'url-parse';
|
||||||
|
|
||||||
|
import {Sso} from '@constants';
|
||||||
|
import NetworkManager from '@managers/network_manager';
|
||||||
|
import {isErrorWithMessage} from '@utils/errors';
|
||||||
|
import {isBetaApp} from '@utils/general';
|
||||||
|
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||||
|
import {typography} from '@utils/typography';
|
||||||
|
import {tryOpenURL} from '@utils/url';
|
||||||
|
|
||||||
|
import AuthError from './components/auth_error';
|
||||||
|
import AuthRedirect from './components/auth_redirect';
|
||||||
|
import AuthSuccess from './components/auth_success';
|
||||||
|
|
||||||
|
interface SSOWithRedirectURLProps {
|
||||||
|
doSSOLogin: (bearerToken: string, csrfToken: string) => void;
|
||||||
|
loginError: string;
|
||||||
|
loginUrl: string;
|
||||||
|
serverUrl: string;
|
||||||
|
setLoginError: (value: string) => void;
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
button: {
|
||||||
|
marginTop: 25,
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
textAlign: 'center',
|
||||||
|
...typography('Body', 200, 'Regular'),
|
||||||
|
},
|
||||||
|
infoContainer: {
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: changeOpacity(theme.centerChannelColor, 0.72),
|
||||||
|
...typography('Body', 100, 'Regular'),
|
||||||
|
},
|
||||||
|
infoTitle: {
|
||||||
|
color: theme.centerChannelColor,
|
||||||
|
marginBottom: 4,
|
||||||
|
...typography('Heading', 700),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const SSOAuthenticationWithExternalBrowser = ({doSSOLogin, loginError, loginUrl, serverUrl, setLoginError, theme}: SSOWithRedirectURLProps) => {
|
||||||
|
const [error, setError] = useState<string>('');
|
||||||
|
const [loginSuccess, setLoginSuccess] = useState(false);
|
||||||
|
const style = getStyleSheet(theme);
|
||||||
|
const intl = useIntl();
|
||||||
|
let customUrlScheme = Sso.REDIRECT_URL_SCHEME;
|
||||||
|
if (isBetaApp) {
|
||||||
|
customUrlScheme = Sso.REDIRECT_URL_SCHEME_DEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
const redirectUrl = customUrlScheme + 'callback';
|
||||||
|
const init = (resetErrors = true) => {
|
||||||
|
setLoginSuccess(false);
|
||||||
|
if (resetErrors !== false) {
|
||||||
|
setError('');
|
||||||
|
setLoginError('');
|
||||||
|
NetworkManager.invalidateClient(serverUrl);
|
||||||
|
NetworkManager.createClient(serverUrl);
|
||||||
|
}
|
||||||
|
const parsedUrl = urlParse(loginUrl, true);
|
||||||
|
const query: Record<string, string> = {
|
||||||
|
...parsedUrl.query,
|
||||||
|
redirect_to: redirectUrl,
|
||||||
|
};
|
||||||
|
parsedUrl.set('query', qs.stringify(query));
|
||||||
|
const url = parsedUrl.toString();
|
||||||
|
|
||||||
|
const onError = (e: Error) => {
|
||||||
|
let message;
|
||||||
|
if (e && Platform.OS === 'android' && isErrorWithMessage(e) && e.message.match(/no activity found to handle intent/i)) {
|
||||||
|
message = intl.formatMessage({
|
||||||
|
id: 'mobile.oauth.failed_to_open_link_no_browser',
|
||||||
|
defaultMessage: 'The link failed to open. Please verify that a browser is installed on the device.',
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
message = intl.formatMessage({
|
||||||
|
id: 'mobile.oauth.failed_to_open_link',
|
||||||
|
defaultMessage: 'The link failed to open. Please try again.',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
setError(
|
||||||
|
message,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
tryOpenURL(url, onError);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onURLChange = ({url}: { url: string }) => {
|
||||||
|
if (url && url.startsWith(redirectUrl)) {
|
||||||
|
const parsedUrl = urlParse(url, true);
|
||||||
|
const bearerToken = parsedUrl.query?.MMAUTHTOKEN;
|
||||||
|
const csrfToken = parsedUrl.query?.MMCSRF;
|
||||||
|
if (bearerToken && csrfToken) {
|
||||||
|
setLoginSuccess(true);
|
||||||
|
doSSOLogin(bearerToken, csrfToken);
|
||||||
|
} else {
|
||||||
|
setError(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'mobile.oauth.failed_to_login',
|
||||||
|
defaultMessage: 'Your login attempt failed. Please try again.',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const listener = Linking.addEventListener('url', onURLChange);
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
init(false);
|
||||||
|
}, 1000);
|
||||||
|
return () => {
|
||||||
|
listener.remove();
|
||||||
|
clearTimeout(timeout);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
let content;
|
||||||
|
if (loginSuccess) {
|
||||||
|
content = (<AuthSuccess theme={theme}/>);
|
||||||
|
} else if (loginError || error) {
|
||||||
|
content = (
|
||||||
|
<AuthError
|
||||||
|
error={loginError || error}
|
||||||
|
retry={init}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = (<AuthRedirect theme={theme}/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={style.container}
|
||||||
|
testID='sso.redirect_url'
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SSOAuthenticationWithExternalBrowser;
|
||||||
|
|
@ -702,6 +702,8 @@
|
||||||
"mobile.no_results_with_term.messages": "No matches found for “{term}”",
|
"mobile.no_results_with_term.messages": "No matches found for “{term}”",
|
||||||
"mobile.no_results.spelling": "Check the spelling or try another search.",
|
"mobile.no_results.spelling": "Check the spelling or try another search.",
|
||||||
"mobile.oauth.failed_to_login": "Your login attempt failed. Please try again.",
|
"mobile.oauth.failed_to_login": "Your login attempt failed. Please try again.",
|
||||||
|
"mobile.oauth.failed_to_open_link": "The link failed to open. Please try again.",
|
||||||
|
"mobile.oauth.failed_to_open_link_no_browser": "The link failed to open. Please verify that a browser is installed on the device.",
|
||||||
"mobile.oauth.something_wrong.okButton": "OK",
|
"mobile.oauth.something_wrong.okButton": "OK",
|
||||||
"mobile.oauth.success.description": "Signing in now, just a moment...",
|
"mobile.oauth.success.description": "Signing in now, just a moment...",
|
||||||
"mobile.oauth.success.title": "Authentication successful",
|
"mobile.oauth.success.title": "Authentication successful",
|
||||||
|
|
|
||||||
|
|
@ -1984,7 +1984,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 557;
|
CURRENT_PROJECT_VERSION = 565;
|
||||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||||
|
|
@ -2026,7 +2026,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CURRENT_PROJECT_VERSION = 557;
|
CURRENT_PROJECT_VERSION = 565;
|
||||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
HEADER_SEARCH_PATHS = "$(inherited)";
|
HEADER_SEARCH_PATHS = "$(inherited)";
|
||||||
|
|
@ -2169,7 +2169,7 @@
|
||||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 557;
|
CURRENT_PROJECT_VERSION = 565;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
|
@ -2219,7 +2219,7 @@
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 557;
|
CURRENT_PROJECT_VERSION = 565;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.20.0</string>
|
<string>2.21.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|
@ -37,7 +37,7 @@
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>557</string>
|
<string>565</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.20.0</string>
|
<string>2.21.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>557</string>
|
<string>565</string>
|
||||||
<key>UIAppFonts</key>
|
<key>UIAppFonts</key>
|
||||||
<array>
|
<array>
|
||||||
<string>OpenSans-Bold.ttf</string>
|
<string>OpenSans-Bold.ttf</string>
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2.20.0</string>
|
<string>2.21.0</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>557</string>
|
<string>565</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|
|
||||||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mattermost-mobile",
|
"name": "mattermost-mobile",
|
||||||
"version": "2.20.0",
|
"version": "2.21.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "mattermost-mobile",
|
"name": "mattermost-mobile",
|
||||||
"version": "2.20.0",
|
"version": "2.21.0",
|
||||||
"description": "Mattermost Mobile with React Native",
|
"description": "Mattermost Mobile with React Native",
|
||||||
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
"repository": "git@github.com:mattermost/mattermost-mobile.git",
|
||||||
"author": "Mattermost, Inc.",
|
"author": "Mattermost, Inc.",
|
||||||
|
|
|
||||||
1
types/api/config.d.ts
vendored
1
types/api/config.d.ts
vendored
|
|
@ -147,6 +147,7 @@ interface ClientConfig {
|
||||||
MaxNotificationsPerChannel: string;
|
MaxNotificationsPerChannel: string;
|
||||||
MaxPostSize: string;
|
MaxPostSize: string;
|
||||||
MinimumHashtagLength: string;
|
MinimumHashtagLength: string;
|
||||||
|
MobileExternalBrowser: string;
|
||||||
OpenIdButtonColor: string;
|
OpenIdButtonColor: string;
|
||||||
OpenIdButtonText: string;
|
OpenIdButtonText: string;
|
||||||
PasswordEnableForgotLink: string;
|
PasswordEnableForgotLink: string;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue