Enable SAML authentication (#325)
* enable saml authentication * Fix unit tests * Fix android cookies * Feedback review
This commit is contained in:
parent
13a1f8a9a8
commit
c13a436d18
24 changed files with 390 additions and 39 deletions
|
|
@ -143,6 +143,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-cookies')
|
||||
compile project(':react-native-vector-icons')
|
||||
compile project(':react-native-svg')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.app.Application;
|
|||
import android.util.Log;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.psykar.cookiemanager.CookieManagerPackage;
|
||||
import com.oblador.vectoricons.VectorIconsPackage;
|
||||
import com.horcrux.svg.RNSvgPackage;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
|
@ -27,6 +28,7 @@ public class MainApplication extends Application implements ReactApplication {
|
|||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new CookieManagerPackage(),
|
||||
new VectorIconsPackage(),
|
||||
new RNSvgPackage()
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
rootProject.name = 'Mattermost'
|
||||
include ':react-native-cookies'
|
||||
project(':react-native-cookies').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-cookies/android')
|
||||
include ':react-native-vector-icons'
|
||||
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,15 @@ export function goToLogin() {
|
|||
};
|
||||
}
|
||||
|
||||
export function goToLoginOptions() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.LoginOptions
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToMfa() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
@ -40,6 +49,15 @@ export function goToMfa() {
|
|||
};
|
||||
}
|
||||
|
||||
export function goToSaml() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: NavigationTypes.NAVIGATION_PUSH,
|
||||
route: Routes.Saml
|
||||
}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function goToLoadTeam() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export function loadStorage() {
|
|||
const actions = [
|
||||
{type: GeneralTypes.RECEIVED_APP_CREDENTIALS, data: credentials},
|
||||
{type: GeneralTypes.RECEIVED_SERVER_VERSION, data: serverVersion},
|
||||
{type: TeamsTypes.SELECT_TEAM, data: currentTeamId},
|
||||
{type: TeamsTypes.SELECT_TEAM, data: (currentTeamId || '')},
|
||||
{type: ChannelTypes.SELECT_CHANNEL, data: currentChannelId}
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See License.txt for license information.
|
||||
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import {goToLogin, goToLoginOptions} from 'app/actions/navigation';
|
||||
|
||||
export function handleServerUrlChanged(serverUrl) {
|
||||
return async (dispatch, getState) => {
|
||||
|
|
@ -12,6 +13,26 @@ export function handleServerUrlChanged(serverUrl) {
|
|||
};
|
||||
}
|
||||
|
||||
export function handleLoginOptions() {
|
||||
return async (dispatch, getState) => {
|
||||
const {config, license} = getState().entities.general;
|
||||
|
||||
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
|
||||
|
||||
let options = 0;
|
||||
if (samlEnabled) {
|
||||
options += 1;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
await goToLoginOptions()(dispatch, getState);
|
||||
} else {
|
||||
await goToLogin()(dispatch, getState);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
handleServerUrlChanged
|
||||
handleServerUrlChanged,
|
||||
handleLoginOptions
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@ import {
|
|||
CreateChannel,
|
||||
LoadTeam,
|
||||
Login,
|
||||
LoginOptions,
|
||||
Mfa,
|
||||
MoreChannels,
|
||||
MoreDirectMessages,
|
||||
OptionsModal,
|
||||
RightMenuDrawer,
|
||||
Root,
|
||||
Saml,
|
||||
Search,
|
||||
SelectServer,
|
||||
SelectTeam,
|
||||
|
|
@ -99,6 +101,14 @@ export const Routes = {
|
|||
title: {id: 'mobile.routes.login', defaultMessage: 'Login'}
|
||||
}
|
||||
},
|
||||
LoginOptions: {
|
||||
key: 'LoginOptions',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: LoginOptions,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'}
|
||||
}
|
||||
},
|
||||
Mfa: {
|
||||
key: 'Mfa',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
|
|
@ -136,6 +146,14 @@ export const Routes = {
|
|||
key: 'Root',
|
||||
component: Root
|
||||
},
|
||||
Saml: {
|
||||
key: 'Saml',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
component: Saml,
|
||||
navigationProps: {
|
||||
title: {id: 'mobile.routes.saml', defaultMessage: 'Single SignOn'}
|
||||
}
|
||||
},
|
||||
Search: {
|
||||
key: 'Search',
|
||||
transition: RouteTransitions.Horizontal,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import ChannelAddMembers from './channel_add_members';
|
|||
import CreateChannel from './create_channel';
|
||||
import LoadTeam from './load_team';
|
||||
import Login from './login/login_container.js';
|
||||
import LoginOptions from './login_options';
|
||||
import Mfa from './mfa';
|
||||
import MoreChannels from './more_channels';
|
||||
import MoreDirectMessages from './more_dms';
|
||||
|
|
@ -21,6 +22,7 @@ import SelectServer from './select_server/select_server_container.js';
|
|||
import SelectTeam from './select_team/select_team_container.js';
|
||||
import Thread from './thread';
|
||||
import UserProfile from './user_profile';
|
||||
import Saml from './saml';
|
||||
|
||||
module.exports = {
|
||||
AccountSettings,
|
||||
|
|
@ -32,6 +34,7 @@ module.exports = {
|
|||
CreateChannel,
|
||||
LoadTeam,
|
||||
Login,
|
||||
LoginOptions,
|
||||
Mfa,
|
||||
MoreChannels,
|
||||
MoreDirectMessages,
|
||||
|
|
@ -42,5 +45,6 @@ module.exports = {
|
|||
SelectServer,
|
||||
SelectTeam,
|
||||
Thread,
|
||||
UserProfile
|
||||
UserProfile,
|
||||
Saml
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import Button from 'react-native-button';
|
|||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
|
||||
import logo from 'assets/images/logo.png';
|
||||
|
|
@ -40,9 +39,7 @@ class Login extends Component {
|
|||
loginId: PropTypes.string.isRequired,
|
||||
password: PropTypes.string.isRequired,
|
||||
checkMfaRequest: PropTypes.object.isRequired,
|
||||
loginRequest: PropTypes.object.isRequired,
|
||||
configRequest: PropTypes.object.isRequired,
|
||||
licenseRequest: PropTypes.object.isRequired
|
||||
loginRequest: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -205,10 +202,6 @@ class Login extends Component {
|
|||
};
|
||||
|
||||
render() {
|
||||
if (this.props.configRequest.status === RequestStatus.STARTED || this.props.licenseRequest.status === RequestStatus.STARTED) {
|
||||
return <Loading/>;
|
||||
}
|
||||
|
||||
return (
|
||||
<KeyboardLayout
|
||||
behavior='padding'
|
||||
|
|
|
|||
|
|
@ -12,15 +12,12 @@ import {checkMfa, login} from 'service/actions/users';
|
|||
import Login from './login.js';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
const {config: configRequest, license: licenseRequest} = state.requests.general;
|
||||
const {checkMfa: checkMfaRequest, login: loginRequest} = state.requests.users;
|
||||
const {config, license} = state.entities.general;
|
||||
return {
|
||||
...state.views.login,
|
||||
checkMfaRequest,
|
||||
loginRequest,
|
||||
configRequest,
|
||||
licenseRequest,
|
||||
config,
|
||||
license
|
||||
};
|
||||
|
|
|
|||
29
app/scenes/login_options/index.js
Normal file
29
app/scenes/login_options/index.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {goToLogin, goToSaml} from 'app/actions/navigation';
|
||||
|
||||
import LoginOptions from './login_options';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config, license} = state.entities.general;
|
||||
return {
|
||||
config,
|
||||
license
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
goToLogin,
|
||||
goToSaml
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(LoginOptions);
|
||||
93
app/scenes/login_options/login_options.js
Normal file
93
app/scenes/login_options/login_options.js
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PureComponent, PropTypes} from 'react';
|
||||
import {
|
||||
Image,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
import Button from 'react-native-button';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
|
||||
import logo from 'assets/images/logo.png';
|
||||
|
||||
export default class LoginOptions extends PureComponent {
|
||||
static propTypes = {
|
||||
actions: React.PropTypes.shape({
|
||||
goToLogin: PropTypes.func.isRequired,
|
||||
goToSaml: PropTypes.func.isRequired
|
||||
}).isRequired,
|
||||
config: PropTypes.object.isRequired,
|
||||
license: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
renderEmailOption = () => {
|
||||
const config = this.props.config;
|
||||
if (config.EnableSignInWithEmail === 'true' || config.EnableSignInWithUsername === 'true') {
|
||||
return (
|
||||
<Button
|
||||
key='email'
|
||||
onPress={this.props.actions.goToLogin}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#2389d7'}]}
|
||||
>
|
||||
<FormattedText
|
||||
id='signup.email'
|
||||
defaultMessage='Email and Password'
|
||||
style={[GlobalStyles.signupButtonText, {color: 'white'}]}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
renderSamlOption = () => {
|
||||
const {config, license} = this.props;
|
||||
if (config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true') {
|
||||
return (
|
||||
<Button
|
||||
key='saml'
|
||||
onPress={this.props.actions.goToSaml}
|
||||
containerStyle={[GlobalStyles.signupButton, {backgroundColor: '#34a28b'}]}
|
||||
>
|
||||
<Text
|
||||
style={[GlobalStyles.signupButtonText, {color: 'white'}]}
|
||||
>
|
||||
{config.SamlLoginButtonText}
|
||||
</Text>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View style={[GlobalStyles.container, GlobalStyles.signupContainer]}>
|
||||
<Image
|
||||
source={logo}
|
||||
/>
|
||||
<Text style={GlobalStyles.header}>
|
||||
{this.props.config.SiteName}
|
||||
</Text>
|
||||
<FormattedText
|
||||
style={GlobalStyles.subheader}
|
||||
id='web.root.signup_info'
|
||||
defaultMessage='All team communication in one place, searchable and accessible anywhere'
|
||||
/>
|
||||
<FormattedText
|
||||
style={[GlobalStyles.subheader, {fontWeight: 'bold', marginTop: 10}]}
|
||||
id='mobile.login_options.choose_title'
|
||||
defaultMessage='Choose your login method'
|
||||
/>
|
||||
{this.renderEmailOption()}
|
||||
{this.renderSamlOption()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
30
app/scenes/saml/index.js
Normal file
30
app/scenes/saml/index.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import {bindActionCreators} from 'redux';
|
||||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {handleSuccessfulLogin} from 'app/actions/views/login';
|
||||
import {goToLoadTeam} from 'app/actions/navigation';
|
||||
import {setStoreFromLocalData} from 'app/actions/views/root';
|
||||
|
||||
import Saml from './saml';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
...state.views.selectServer
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
handleSuccessfulLogin,
|
||||
setStoreFromLocalData,
|
||||
goToLoadTeam
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
||||
export default navigationSceneConnect(mapStateToProps, mapDispatchToProps)(Saml);
|
||||
63
app/scenes/saml/saml.js
Normal file
63
app/scenes/saml/saml.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
|
||||
// See License.txt for license information.
|
||||
|
||||
import React, {PropTypes, PureComponent} from 'react';
|
||||
import {
|
||||
InteractionManager,
|
||||
WebView
|
||||
} from 'react-native';
|
||||
|
||||
import CookieManager from 'react-native-cookies';
|
||||
|
||||
export default class Saml extends PureComponent {
|
||||
static propTypes = {
|
||||
serverUrl: PropTypes.string.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
handleSuccessfulLogin: PropTypes.func.isRequired,
|
||||
setStoreFromLocalData: PropTypes.func.isRequired,
|
||||
goToLoadTeam: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
this.setState({renderWebview: true});
|
||||
});
|
||||
}
|
||||
|
||||
onNavigationStateChange = (navState) => {
|
||||
const {url} = navState;
|
||||
if (url.includes('/login/sso/saml') && url.includes('mobile')) {
|
||||
CookieManager.get(this.props.serverUrl, (err, res) => {
|
||||
const token = res.MMAUTHTOKEN;
|
||||
const {
|
||||
handleSuccessfulLogin,
|
||||
setStoreFromLocalData,
|
||||
goToLoadTeam
|
||||
} = this.props.actions;
|
||||
|
||||
if (token) {
|
||||
handleSuccessfulLogin().
|
||||
then(setStoreFromLocalData.bind(null, {url: this.props.serverUrl, token})).
|
||||
then(goToLoadTeam);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state || !this.state.renderWebview) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<WebView
|
||||
source={{uri: `${this.props.serverUrl}/login/sso/saml?action=mobile`}}
|
||||
javaScriptEnabledAndroid={true}
|
||||
automaticallyAdjustContentInsets={false}
|
||||
scalesPageToFit={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -13,21 +13,29 @@ import Button from 'react-native-button';
|
|||
import ErrorText from 'app/components/error_text';
|
||||
import FormattedText from 'app/components/formatted_text';
|
||||
import KeyboardLayout from 'app/components/layout/keyboard_layout';
|
||||
import Loading from 'app/components/loading';
|
||||
import TextInputWithLocalizedPlaceholder from 'app/components/text_input_with_localized_placeholder';
|
||||
import {GlobalStyles} from 'app/styles';
|
||||
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
|
||||
|
||||
import logo from 'assets/images/logo.png';
|
||||
|
||||
import {RequestStatus} from 'service/constants';
|
||||
import Client from 'service/client';
|
||||
import RequestStatus from 'service/constants/request_status';
|
||||
|
||||
import {isValidUrl, stripTrailingSlashes} from 'app/utils/url';
|
||||
|
||||
export default class SelectServer extends PureComponent {
|
||||
static propTypes = {
|
||||
transition: PropTypes.bool.isRequired,
|
||||
serverUrl: PropTypes.string.isRequired,
|
||||
server: PropTypes.object.isRequired,
|
||||
actions: PropTypes.object.isRequired
|
||||
pingRequest: PropTypes.object.isRequired,
|
||||
configRequest: PropTypes.object.isRequired,
|
||||
licenseRequest: PropTypes.object.isRequired,
|
||||
actions: PropTypes.shape({
|
||||
getPing: PropTypes.func.isRequired,
|
||||
resetPing: PropTypes.func.isRequired,
|
||||
handleLoginOptions: PropTypes.func.isRequired,
|
||||
handleServerUrlChanged: PropTypes.func.isRequired
|
||||
}).isRequired
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
|
|
@ -38,19 +46,23 @@ export default class SelectServer extends PureComponent {
|
|||
};
|
||||
}
|
||||
|
||||
onClick = () => {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!this.props.transition && nextProps.transition) {
|
||||
this.props.actions.resetPing().then(() => {
|
||||
this.props.actions.handleLoginOptions();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClick = async () => {
|
||||
const url = this.props.serverUrl;
|
||||
let error = null;
|
||||
|
||||
Keyboard.dismiss();
|
||||
|
||||
if (isValidUrl(url)) {
|
||||
Client.setUrl(stripTrailingSlashes(url));
|
||||
|
||||
this.props.actions.getPing().then(() => {
|
||||
if (this.props.server.status === RequestStatus.SUCCESS) {
|
||||
Keyboard.dismiss();
|
||||
this.props.actions.goToLogin();
|
||||
}
|
||||
});
|
||||
await this.props.actions.getPing();
|
||||
} else {
|
||||
error = {
|
||||
intl: {
|
||||
|
|
@ -72,6 +84,17 @@ export default class SelectServer extends PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const {serverUrl, pingRequest, configRequest, licenseRequest} = this.props;
|
||||
const isLoading = pingRequest.status === RequestStatus.STARTED ||
|
||||
configRequest.status === RequestStatus.STARTED ||
|
||||
licenseRequest.status === RequestStatus.STARTED;
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading/>;
|
||||
}
|
||||
|
||||
const error = pingRequest.error || configRequest.error || licenseRequest.error;
|
||||
|
||||
return (
|
||||
<KeyboardLayout
|
||||
behavior='padding'
|
||||
|
|
@ -92,7 +115,7 @@ export default class SelectServer extends PureComponent {
|
|||
</View>
|
||||
<TextInputWithLocalizedPlaceholder
|
||||
ref={this.inputRef}
|
||||
value={this.props.serverUrl}
|
||||
value={serverUrl}
|
||||
onChangeText={this.props.actions.handleServerUrlChanged}
|
||||
onSubmitEditing={this.onClick}
|
||||
style={GlobalStyles.inputBox}
|
||||
|
|
@ -105,7 +128,6 @@ export default class SelectServer extends PureComponent {
|
|||
/>
|
||||
<Button
|
||||
onPress={this.onClick}
|
||||
loading={this.props.server.loading}
|
||||
containerStyle={GlobalStyles.signupButton}
|
||||
>
|
||||
<FormattedText
|
||||
|
|
@ -114,7 +136,7 @@ export default class SelectServer extends PureComponent {
|
|||
defaultMessage='Proceed'
|
||||
/>
|
||||
</Button>
|
||||
<ErrorText error={this.state.error || this.props.server.error}/>
|
||||
<ErrorText error={this.state.error || error}/>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardLayout>
|
||||
|
|
|
|||
|
|
@ -5,16 +5,24 @@ import {bindActionCreators} from 'redux';
|
|||
|
||||
import navigationSceneConnect from '../navigationSceneConnect';
|
||||
|
||||
import {getPing} from 'service/actions/general';
|
||||
import {goToLogin} from 'app/actions/navigation';
|
||||
import {getPing, resetPing} from 'service/actions/general';
|
||||
import {RequestStatus} from 'service/constants';
|
||||
import * as SelectServerActions from 'app/actions/views/select_server';
|
||||
|
||||
import SelectServer from './select_server';
|
||||
|
||||
function mapStateToProps(state) {
|
||||
const {config: configRequest, license: licenseRequest, server: pingRequest} = state.requests.general;
|
||||
|
||||
const success = RequestStatus.SUCCESS;
|
||||
const transition = (pingRequest.status === success && configRequest.status === success && licenseRequest.status === success);
|
||||
|
||||
return {
|
||||
...state.views.selectServer,
|
||||
server: state.requests.general.server
|
||||
pingRequest,
|
||||
configRequest,
|
||||
licenseRequest,
|
||||
transition
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -23,7 +31,7 @@ function mapDispatchToProps(dispatch) {
|
|||
actions: bindActionCreators({
|
||||
...SelectServerActions,
|
||||
getPing,
|
||||
goToLogin
|
||||
resetPing
|
||||
}, dispatch)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1511,9 +1511,11 @@
|
|||
"mobile.loading_channels": "Loading Channels...",
|
||||
"mobile.loading_members": "Loading Members...",
|
||||
"mobile.request.invalid_response": "Received invalid response from the server.",
|
||||
"mobile.login_options.choose_title": "Choose your login method",
|
||||
"mobile.routes.channels": "Channels",
|
||||
"mobile.routes.enterServerUrl": "Enter Server URL",
|
||||
"mobile.routes.login": "Login",
|
||||
"mobile.routes.loginOptions": "Login Chooser",
|
||||
"mobile.routes.mfa": "Multi-factor Authentication",
|
||||
"mobile.routes.postsList": "Posts List",
|
||||
"mobile.routes.selectTeam": "Select Team",
|
||||
|
|
@ -1530,6 +1532,7 @@
|
|||
"mobile.routes.user_profile.send_message": "Send Message",
|
||||
"mobile.server_ping_failed": "Cannot connect to the server. Please check your server URL and internet connection.",
|
||||
"mobile.server_url.invalid_format": "URL must start with http:// or https://",
|
||||
"mobile.routes.saml": "Single SignOn",
|
||||
"more_channels.close": "Close",
|
||||
"more_channels.create": "Create New Channel",
|
||||
"more_channels.createClick": "Click 'Create New Channel' to make a new one",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
7FBB5E9B1E1F5A4B000DE18A /* libRNVectorIcons.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7FDF290C1E1F4B4E00DBBE56 /* libRNVectorIcons.a */; };
|
||||
832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; };
|
||||
895C9A56B94A45C1BAF568FE /* Entypo.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */; };
|
||||
9906AC672C594EEB9D5EE503 /* libRNCookieManagerIOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = CE45EB909FB44F3597EF3C60 /* libRNCookieManagerIOS.a */; };
|
||||
B50561A109B4442299F82327 /* libRNSearchBar.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F81F6DC42D394831B4549928 /* libRNSearchBar.a */; };
|
||||
C035DB50ED2045F09923FFAE /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 04AA5E8EF3B54735A11E3B95 /* MaterialCommunityIcons.ttf */; };
|
||||
C337E8708D2845C6A8DBB47E /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */; };
|
||||
|
|
@ -222,6 +223,13 @@
|
|||
remoteGlobalIDString = 134814201AA4EA6300B7C361;
|
||||
remoteInfo = RNSearchBar;
|
||||
};
|
||||
7F906D961E6A1ADF00B8F49A /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 31A0780E2F224AC8AF8E6930 /* RNCookieManagerIOS.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 1BD725DA1CF77A8B005DBD79;
|
||||
remoteInfo = RNCookieManagerIOS;
|
||||
};
|
||||
7FDF28E01E1F4B1F00DBBE56 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */;
|
||||
|
|
@ -270,6 +278,7 @@
|
|||
2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; };
|
||||
2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = "<group>"; };
|
||||
2DCFD31D3F4A4154822AB532 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; };
|
||||
31A0780E2F224AC8AF8E6930 /* RNCookieManagerIOS.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNCookieManagerIOS.xcodeproj; path = "../node_modules/react-native-cookies/RNCookieManagerIOS.xcodeproj"; sourceTree = "<group>"; };
|
||||
349FBA7338E74D9BBD709528 /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; };
|
||||
356C9186FA374641A00EB2EA /* MaterialIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"; sourceTree = "<group>"; };
|
||||
51F5A483EA9F4A9A87ACFB59 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; };
|
||||
|
|
@ -278,6 +287,7 @@
|
|||
78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = "<group>"; };
|
||||
832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; };
|
||||
AC6EB561E1F64C17A69D2FAD /* Entypo.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = "<group>"; };
|
||||
CE45EB909FB44F3597EF3C60 /* libRNCookieManagerIOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNCookieManagerIOS.a; sourceTree = "<group>"; };
|
||||
EDC04CBCF81642219D199CBB /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
|
||||
F1F071EE85494E269A50AE88 /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; };
|
||||
F81F6DC42D394831B4549928 /* libRNSearchBar.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSearchBar.a; sourceTree = "<group>"; };
|
||||
|
|
@ -310,6 +320,7 @@
|
|||
00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */,
|
||||
139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */,
|
||||
B50561A109B4442299F82327 /* libRNSearchBar.a in Frameworks */,
|
||||
9906AC672C594EEB9D5EE503 /* libRNCookieManagerIOS.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -464,6 +475,14 @@
|
|||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7F906D7A1E6A1ADF00B8F49A /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7F906D971E6A1ADF00B8F49A /* libRNCookieManagerIOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
7FDF28C41E1F4B1F00DBBE56 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -497,6 +516,7 @@
|
|||
2CBE9C0FB56E4FDA96C30792 /* RNSVG.xcodeproj */,
|
||||
2B25899FDAC149EB96ED3305 /* RNVectorIcons.xcodeproj */,
|
||||
59954D479A89488091EB588F /* RNSearchBar.xcodeproj */,
|
||||
31A0780E2F224AC8AF8E6930 /* RNCookieManagerIOS.xcodeproj */,
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -585,6 +605,7 @@
|
|||
TestTargetID = 13B07F861A680F5B00A75B9A;
|
||||
};
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
DevelopmentTeam = UQ8HT4Q2XM;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
|
|
@ -645,6 +666,10 @@
|
|||
ProductGroup = 146834001AC3E56700842450 /* Products */;
|
||||
ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 7F906D7A1E6A1ADF00B8F49A /* Products */;
|
||||
ProjectRef = 31A0780E2F224AC8AF8E6930 /* RNCookieManagerIOS.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 7F48904C1E3B7D3B008EDBEA /* Products */;
|
||||
ProjectRef = 59954D479A89488091EB588F /* RNSearchBar.xcodeproj */;
|
||||
|
|
@ -842,6 +867,13 @@
|
|||
remoteRef = 7F4890681E3B7D3B008EDBEA /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7F906D971E6A1ADF00B8F49A /* libRNCookieManagerIOS.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
path = libRNCookieManagerIOS.a;
|
||||
remoteRef = 7F906D961E6A1ADF00B8F49A /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
7FDF28E11E1F4B1F00DBBE56 /* libRNSVG.a */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = archive.ar;
|
||||
|
|
@ -967,8 +999,6 @@
|
|||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
@ -988,8 +1018,6 @@
|
|||
LIBRARY_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
"\"$(SRCROOT)/$(TARGET_NAME)\"",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
"react-intl": "2.2.2",
|
||||
"react-native": "0.40.0",
|
||||
"react-native-button": "1.7.1",
|
||||
"react-native-cookies": "2.0.0",
|
||||
"react-native-drawer": "2.3.0",
|
||||
"react-native-keyboard-aware-scroll-view": "0.2.7",
|
||||
"react-native-keyboard-spacer": "0.3.1",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,12 @@ export function getPing() {
|
|||
};
|
||||
}
|
||||
|
||||
export function resetPing() {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({type: GeneralTypes.PING_RESET}, getState);
|
||||
};
|
||||
}
|
||||
|
||||
export function getClientConfig() {
|
||||
return bindClientFunc(
|
||||
Client.getClientConfig,
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ export default class Client {
|
|||
if (response.ok) {
|
||||
this.token = '';
|
||||
}
|
||||
this.serverVersion = '';
|
||||
return response;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const GeneralTypes = keyMirror({
|
|||
PING_REQUEST: null,
|
||||
PING_SUCCESS: null,
|
||||
PING_FAILURE: null,
|
||||
PING_RESET: null,
|
||||
|
||||
RECEIVED_SERVER_VERSION: null,
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import {GeneralTypes} from 'service/constants';
|
|||
import {handleRequest, initialRequestState} from './helpers';
|
||||
|
||||
function server(state = initialRequestState(), action) {
|
||||
if (action.type === GeneralTypes.PING_RESET) {
|
||||
return initialRequestState();
|
||||
}
|
||||
|
||||
return handleRequest(
|
||||
GeneralTypes.PING_REQUEST,
|
||||
GeneralTypes.PING_SUCCESS,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,16 @@ import path from 'path';
|
|||
import register from 'babel-core/register';
|
||||
import chai from 'chai';
|
||||
import chaiEnzyme from 'chai-enzyme';
|
||||
import ReactNative from 'react-native-mock';
|
||||
|
||||
const m = require('module');
|
||||
const originalLoader = m._load;
|
||||
|
||||
const NativeModules = ReactNative.NativeModules;
|
||||
|
||||
NativeModules.RNCookieManagerIOS = {};
|
||||
NativeModules.RNCookieManagerAndroid = {};
|
||||
|
||||
// Image file ignore setup from:
|
||||
// http://valuemotive.com/2016/08/01/unit-testing-react-native-components-with-mocha-and-enzyme/
|
||||
m._load = function hookedLoader(request, parent, isMain) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue