[WIP] remove meteor lib (#146)
* removed meteor lib * reconnect saga * Focused text input touch bug fixed
This commit is contained in:
parent
a15774c4ff
commit
8599d6a7cc
|
@ -75,7 +75,7 @@ export const SERVER = createRequestTypes('SERVER', [
|
||||||
'ADD',
|
'ADD',
|
||||||
'GOTO_ADD'
|
'GOTO_ADD'
|
||||||
]);
|
]);
|
||||||
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT']);
|
export const METEOR = createRequestTypes('METEOR_CONNECT', [...defaultTypes, 'DISCONNECT', 'DISCONNECT_BY_USER']);
|
||||||
export const LOGOUT = 'LOGOUT'; // logout is always success
|
export const LOGOUT = 'LOGOUT'; // logout is always success
|
||||||
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
|
export const ACTIVE_USERS = createRequestTypes('ACTIVE_USERS', ['SET', 'REQUEST']);
|
||||||
|
|
||||||
|
|
|
@ -25,3 +25,8 @@ export function disconnect(err) {
|
||||||
err
|
err
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export function disconnect_by_user() {
|
||||||
|
return {
|
||||||
|
type: types.METEOR.DISCONNECT_BY_USER
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -41,10 +41,9 @@ export default class Routes extends React.Component {
|
||||||
return (<Loading />);
|
return (<Loading />);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (login.token && !login.failure && !login.isRegistering) {
|
if (!login.token || login.isRegistering) {
|
||||||
return (<AuthRoutes ref={nav => this.navigator = nav} />);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (<PublicRoutes ref={nav => this.navigator = nav} />);
|
return (<PublicRoutes ref={nav => this.navigator = nav} />);
|
||||||
}
|
}
|
||||||
|
return (<AuthRoutes ref={nav => this.navigator = nav} />);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import RoomsListView from '../../views/RoomsListView';
|
||||||
import RoomView from '../../views/RoomView';
|
import RoomView from '../../views/RoomView';
|
||||||
import CreateChannelView from '../../views/CreateChannelView';
|
import CreateChannelView from '../../views/CreateChannelView';
|
||||||
import SelectUsersView from '../../views/SelectUsersView';
|
import SelectUsersView from '../../views/SelectUsersView';
|
||||||
|
import NewServerView from '../../views/NewServerView';
|
||||||
|
|
||||||
const AuthRoutes = StackNavigator(
|
const AuthRoutes = StackNavigator(
|
||||||
{
|
{
|
||||||
|
@ -26,6 +27,12 @@ const AuthRoutes = StackNavigator(
|
||||||
navigationOptions: {
|
navigationOptions: {
|
||||||
title: 'Select Users'
|
title: 'Select Users'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
AddServer: {
|
||||||
|
screen: NewServerView,
|
||||||
|
navigationOptions: {
|
||||||
|
title: 'New server'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
import EJSON from 'ejson';
|
||||||
|
|
||||||
|
class EventEmitter {
|
||||||
|
constructor() {
|
||||||
|
this.events = {};
|
||||||
|
}
|
||||||
|
on(event, listener) {
|
||||||
|
if (typeof this.events[event] !== 'object') {
|
||||||
|
this.events[event] = [];
|
||||||
|
}
|
||||||
|
this.events[event].push(listener);
|
||||||
|
}
|
||||||
|
removeListener(event, listener) {
|
||||||
|
if (typeof this.events[event] === 'object') {
|
||||||
|
const idx = this.events[event].indexOf(listener);
|
||||||
|
if (idx > -1) {
|
||||||
|
this.events[event].splice(idx, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(event, ...args) {
|
||||||
|
if (typeof this.events[event] === 'object') {
|
||||||
|
this.events[event].forEach((listener) => {
|
||||||
|
try {
|
||||||
|
listener.apply(this, args);
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
once(event, listener) {
|
||||||
|
this.on(event, function g(...args) {
|
||||||
|
this.removeListener(event, g);
|
||||||
|
listener.apply(this, args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Socket extends EventEmitter {
|
||||||
|
constructor(url) {
|
||||||
|
super();
|
||||||
|
this.url = url.replace(/^http/, 'ws');
|
||||||
|
this.id = 0;
|
||||||
|
this.subscriptions = {};
|
||||||
|
this._connect();
|
||||||
|
this.ddp = new EventEmitter();
|
||||||
|
this.on('ping', () => this.send({ msg: 'pong' }));
|
||||||
|
this.on('result', data => this.ddp.emit(data.id, { result: data.result, error: data.error }));
|
||||||
|
this.on('ready', data => this.ddp.emit(data.subs[0], data));
|
||||||
|
}
|
||||||
|
send(obj) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.id += 1;
|
||||||
|
const id = obj.id || `${ this.id }`;
|
||||||
|
this.connection.send(EJSON.stringify({ ...obj, id }));
|
||||||
|
this.ddp.once(id, data => (data.error ? reject(data.error) : resolve(data.result || data.subs)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_connect() {
|
||||||
|
const connection = new WebSocket(`${ this.url }/websocket`);
|
||||||
|
connection.onopen = () => {
|
||||||
|
this.emit('open');
|
||||||
|
this.send({ msg: 'connect', version: '1', support: ['1', 'pre2', 'pre1'] });
|
||||||
|
};
|
||||||
|
connection.onclose = e => this.emit('disconnected', e);
|
||||||
|
// connection.onerror = () => {
|
||||||
|
// // alert(error.type);
|
||||||
|
// // console.log(error);
|
||||||
|
// // console.log(`WebSocket Error ${ JSON.stringify({...error}) }`);
|
||||||
|
// };
|
||||||
|
|
||||||
|
connection.onmessage = (e) => {
|
||||||
|
const data = EJSON.parse(e.data);
|
||||||
|
this.emit(data.msg, data);
|
||||||
|
return data.collection && this.emit(data.collection, data);
|
||||||
|
};
|
||||||
|
// this.on('disconnected', e => alert(JSON.stringify(e)));
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
logout() {
|
||||||
|
return this.call('logout').then(() => this.subscriptions = {});
|
||||||
|
}
|
||||||
|
disconnect() {
|
||||||
|
this.emit('disconnected_by_user');
|
||||||
|
this.connection.close();
|
||||||
|
}
|
||||||
|
reconnect() {
|
||||||
|
this.disconnect();
|
||||||
|
this.once('connected', () => {
|
||||||
|
Object.keys(this.subscriptions).forEach((key) => {
|
||||||
|
const { name, params } = this.subscriptions[key];
|
||||||
|
this.subscriptions[key].unsubscribe();
|
||||||
|
this.subscribe(name, params);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._connect();
|
||||||
|
}
|
||||||
|
call(method, ...params) {
|
||||||
|
return this.send({
|
||||||
|
msg: 'method', method, params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
unsubscribe(id) {
|
||||||
|
if (!this.subscriptions[id]) {
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
delete this.subscriptions[id];
|
||||||
|
return this.send({
|
||||||
|
msg: 'unsub',
|
||||||
|
id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
subscribe(name, ...params) {
|
||||||
|
return this.send({
|
||||||
|
msg: 'sub', name, params
|
||||||
|
}).then((data) => {
|
||||||
|
this.subscriptions[data.id] = {
|
||||||
|
name,
|
||||||
|
params,
|
||||||
|
unsubscribe: () => this.unsubscribe(data.id)
|
||||||
|
};
|
||||||
|
return this.subscriptions[data.id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
import Meteor from 'react-native-meteor';
|
|
||||||
import Random from 'react-native-meteor/lib/Random';
|
import Random from 'react-native-meteor/lib/Random';
|
||||||
import { AsyncStorage, Platform } from 'react-native';
|
import { AsyncStorage, Platform } from 'react-native';
|
||||||
import { hashPassword } from 'react-native-meteor/lib/utils';
|
import { hashPassword } from 'react-native-meteor/lib/utils';
|
||||||
|
@ -11,19 +10,13 @@ import realm from './realm';
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import { someoneTyping } from '../actions/room';
|
import { someoneTyping } from '../actions/room';
|
||||||
import { setUser } from '../actions/login';
|
import { setUser } from '../actions/login';
|
||||||
import { disconnect, connectSuccess } from '../actions/connect';
|
import { disconnect, disconnect_by_user, connectSuccess, connectFailure } from '../actions/connect';
|
||||||
import { requestActiveUser } from '../actions/activeUsers';
|
import { requestActiveUser } from '../actions/activeUsers';
|
||||||
|
import Ddp from './ddp';
|
||||||
|
|
||||||
export { Accounts } from 'react-native-meteor';
|
export { Accounts } from 'react-native-meteor';
|
||||||
|
|
||||||
const call = (method, ...params) => new Promise((resolve, reject) => {
|
const call = (method, ...params) => RocketChat.ddp.call(method, ...params); // eslint-disable-line
|
||||||
Meteor.call(method, ...params, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
}
|
|
||||||
resolve(data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
const TOKEN_KEY = 'reactnativemeteor_usertoken';
|
||||||
const SERVER_TIMEOUT = 30000;
|
const SERVER_TIMEOUT = 30000;
|
||||||
|
|
||||||
|
@ -67,47 +60,57 @@ const RocketChat = {
|
||||||
activeUser[ddpMessage.id] = status;
|
activeUser[ddpMessage.id] = status;
|
||||||
return reduxStore.dispatch(requestActiveUser(activeUser));
|
return reduxStore.dispatch(requestActiveUser(activeUser));
|
||||||
},
|
},
|
||||||
connect(_url) {
|
reconnect() {
|
||||||
|
if (this.ddp) {
|
||||||
|
this.ddp.reconnect();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
connect(url) {
|
||||||
|
if (this.ddp) {
|
||||||
|
this.ddp.disconnect();
|
||||||
|
}
|
||||||
|
this.ddp = new Ddp(url);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const url = `${ _url }/websocket`;
|
this.ddp.on('disconnected_by_user', () => {
|
||||||
|
reduxStore.dispatch(disconnect_by_user());
|
||||||
Meteor.connect(url, { autoConnect: true, autoReconnect: true });
|
});
|
||||||
|
this.ddp.on('disconnected', () => {
|
||||||
Meteor.ddp.on('disconnected', () => {
|
|
||||||
reduxStore.dispatch(disconnect());
|
reduxStore.dispatch(disconnect());
|
||||||
});
|
});
|
||||||
|
this.ddp.on('open', async() => resolve(reduxStore.dispatch(connectSuccess())));
|
||||||
Meteor.ddp.on('connected', () => {
|
this.ddp.on('connected', () => {
|
||||||
reduxStore.dispatch(connectSuccess());
|
RocketChat.getSettings();
|
||||||
resolve();
|
RocketChat.getPermissions();
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.ddp.on('connected', async() => {
|
this.ddp.on('error', (err) => {
|
||||||
Meteor.ddp.on('added', (ddpMessage) => {
|
alert(JSON.stringify(err));
|
||||||
|
reduxStore.dispatch(connectFailure());
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ddp.on('connected', () => this.ddp.subscribe('activeUsers', null, false));
|
||||||
|
|
||||||
|
|
||||||
|
this.ddp.on('users', (ddpMessage) => {
|
||||||
if (ddpMessage.collection === 'users') {
|
if (ddpMessage.collection === 'users') {
|
||||||
return RocketChat._setUser(ddpMessage);
|
return RocketChat._setUser(ddpMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Meteor.ddp.on('removed', (ddpMessage) => {
|
|
||||||
if (ddpMessage.collection === 'users') {
|
this.ddp.on('stream-room-messages', ddpMessage => realm.write(() => {
|
||||||
return RocketChat._setUser(ddpMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Meteor.ddp.on('changed', (ddpMessage) => {
|
|
||||||
if (ddpMessage.collection === 'stream-room-messages') {
|
|
||||||
return realm.write(() => {
|
|
||||||
const message = this._buildMessage(ddpMessage.fields.args[0]);
|
const message = this._buildMessage(ddpMessage.fields.args[0]);
|
||||||
realm.create('messages', message, true);
|
realm.create('messages', message, true);
|
||||||
});
|
}));
|
||||||
}
|
|
||||||
if (ddpMessage.collection === 'stream-notify-room') {
|
this.ddp.on('stream-notify-room', (ddpMessage) => {
|
||||||
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
const [_rid, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (ev !== 'typing') {
|
if (ev !== 'typing') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
return reduxStore.dispatch(someoneTyping({ _rid, username: ddpMessage.fields.args[0], typing: ddpMessage.fields.args[1] }));
|
||||||
}
|
});
|
||||||
if (ddpMessage.collection === 'stream-notify-user') {
|
|
||||||
|
this.ddp.on('stream-notify-user', (ddpMessage) => {
|
||||||
const [type, data] = ddpMessage.fields.args;
|
const [type, data] = ddpMessage.fields.args;
|
||||||
const [, ev] = ddpMessage.fields.eventName.split('/');
|
const [, ev] = ddpMessage.fields.eventName.split('/');
|
||||||
if (/subscriptions/.test(ev)) {
|
if (/subscriptions/.test(ev)) {
|
||||||
|
@ -124,36 +127,6 @@ const RocketChat = {
|
||||||
sub.roomUpdatedAt = data._updatedAt;
|
sub.roomUpdatedAt = data._updatedAt;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (ddpMessage.collection === 'users') {
|
|
||||||
return RocketChat._setUser(ddpMessage);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
RocketChat.getSettings();
|
|
||||||
RocketChat.getPermissions();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(e => console.error(e));
|
|
||||||
},
|
|
||||||
login(params, callback) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
Meteor._startLoggingIn();
|
|
||||||
return Meteor.call('login', params, (err, result) => {
|
|
||||||
Meteor._endLoggingIn();
|
|
||||||
Meteor._handleLoginCallback(err, result);
|
|
||||||
if (err) {
|
|
||||||
if (/user not found/i.test(err.reason)) {
|
|
||||||
err.error = 1;
|
|
||||||
err.reason = 'User or Password incorrect';
|
|
||||||
err.message = 'User or Password incorrect';
|
|
||||||
}
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
if (typeof callback === 'function') {
|
|
||||||
callback(err, result);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -236,10 +209,7 @@ const RocketChat = {
|
||||||
|
|
||||||
loadSubscriptions(cb) {
|
loadSubscriptions(cb) {
|
||||||
const { server } = reduxStore.getState().server;
|
const { server } = reduxStore.getState().server;
|
||||||
Meteor.call('subscriptions/get', (err, data) => {
|
this.ddp.call('subscriptions/get').then((data) => {
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
if (data.length) {
|
if (data.length) {
|
||||||
realm.write(() => {
|
realm.write(() => {
|
||||||
data.forEach((subscription) => {
|
data.forEach((subscription) => {
|
||||||
|
@ -305,14 +275,7 @@ const RocketChat = {
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
loadMessagesForRoom(rid, end, cb) {
|
loadMessagesForRoom(rid, end, cb) {
|
||||||
return new Promise((resolve, reject) => {
|
return this.ddp.call('loadHistory', rid, end, 20).then((data) => {
|
||||||
Meteor.call('loadHistory', rid, end, 20, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
if (cb) {
|
|
||||||
cb({ end: true });
|
|
||||||
}
|
|
||||||
return reject(err);
|
|
||||||
}
|
|
||||||
if (data && data.messages.length) {
|
if (data && data.messages.length) {
|
||||||
const messages = data.messages.map(message => this._buildMessage(message));
|
const messages = data.messages.map(message => this._buildMessage(message));
|
||||||
realm.write(() => {
|
realm.write(() => {
|
||||||
|
@ -321,16 +284,17 @@ const RocketChat = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cb) {
|
if (cb) {
|
||||||
if (data && data.messages.length < 20) {
|
cb({ end: data && data.messages.length < 20 });
|
||||||
|
}
|
||||||
|
return data.message;
|
||||||
|
}, (err) => {
|
||||||
|
if (err) {
|
||||||
|
if (cb) {
|
||||||
cb({ end: true });
|
cb({ end: true });
|
||||||
} else {
|
|
||||||
cb({ end: false });
|
|
||||||
}
|
}
|
||||||
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -484,14 +448,41 @@ const RocketChat = {
|
||||||
data.forEach(subscription =>
|
data.forEach(subscription =>
|
||||||
realm.create('subscriptions', subscription, true));
|
realm.create('subscriptions', subscription, true));
|
||||||
});
|
});
|
||||||
Meteor.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
|
this.ddp.subscribe('stream-notify-user', `${ login.user.id }/subscriptions-changed`, false);
|
||||||
Meteor.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
|
this.ddp.subscribe('stream-notify-user', `${ login.user.id }/rooms-changed`, false);
|
||||||
Meteor.subscribe('activeUsers', null, false);
|
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
|
disconnect() {
|
||||||
|
if (!this.ddp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reduxStore.dispatch(disconnect_by_user());
|
||||||
|
delete this.ddp;
|
||||||
|
return this.ddp.disconnect();
|
||||||
|
},
|
||||||
|
login(params, callback) {
|
||||||
|
return this.ddp.call('login', params).then((result) => {
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(null, result);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}, (err) => {
|
||||||
|
if (/user not found/i.test(err.reason)) {
|
||||||
|
err.error = 1;
|
||||||
|
err.reason = 'User or Password incorrect';
|
||||||
|
err.message = 'User or Password incorrect';
|
||||||
|
}
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(err, null);
|
||||||
|
}
|
||||||
|
return Promise.reject(err);
|
||||||
|
});
|
||||||
|
},
|
||||||
logout({ server }) {
|
logout({ server }) {
|
||||||
Meteor.logout();
|
if (this.ddp) {
|
||||||
Meteor.disconnect();
|
this.ddp.logout();
|
||||||
|
// this.disconnect();
|
||||||
|
}
|
||||||
AsyncStorage.removeItem(TOKEN_KEY);
|
AsyncStorage.removeItem(TOKEN_KEY);
|
||||||
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
|
AsyncStorage.removeItem(`${ TOKEN_KEY }-${ server }`);
|
||||||
},
|
},
|
||||||
|
@ -570,7 +561,7 @@ const RocketChat = {
|
||||||
return `${ room._server.id }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
return `${ room._server.id }/${ roomType }/${ room.name }?msg=${ message._id }`;
|
||||||
},
|
},
|
||||||
subscribe(...args) {
|
subscribe(...args) {
|
||||||
return Meteor.subscribe(...args);
|
return this.ddp.subscribe(...args);
|
||||||
},
|
},
|
||||||
emitTyping(room, t = true) {
|
emitTyping(room, t = true) {
|
||||||
const { login } = reduxStore.getState();
|
const { login } = reduxStore.getState();
|
||||||
|
|
|
@ -4,6 +4,7 @@ const initialState = {
|
||||||
connecting: false,
|
connecting: false,
|
||||||
connected: false,
|
connected: false,
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
|
disconnected_by_user: false,
|
||||||
failure: false
|
failure: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,7 +13,8 @@ export default function connect(state = initialState, action) {
|
||||||
case METEOR.REQUEST:
|
case METEOR.REQUEST:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
connecting: true
|
connecting: true,
|
||||||
|
disconnected_by_user: false
|
||||||
};
|
};
|
||||||
case METEOR.SUCCESS:
|
case METEOR.SUCCESS:
|
||||||
return {
|
return {
|
||||||
|
@ -29,6 +31,11 @@ export default function connect(state = initialState, action) {
|
||||||
failure: true,
|
failure: true,
|
||||||
errorMessage: action.err
|
errorMessage: action.err
|
||||||
};
|
};
|
||||||
|
case METEOR.DISCONNECT_BY_USER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
disconnected_by_user: true
|
||||||
|
};
|
||||||
case METEOR.DISCONNECT:
|
case METEOR.DISCONNECT:
|
||||||
return initialState;
|
return initialState;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -26,7 +26,7 @@ export default function login(state = initialState, action) {
|
||||||
...state,
|
...state,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isAuthenticated: true,
|
isAuthenticated: true,
|
||||||
user: action.user,
|
user: { ...state.user, ...action.user },
|
||||||
token: action.user.token,
|
token: action.user.token,
|
||||||
failure: false,
|
failure: false,
|
||||||
error: ''
|
error: ''
|
||||||
|
|
|
@ -1,27 +1,44 @@
|
||||||
import { put, call, takeLatest, select } from 'redux-saga/effects';
|
import { call, takeLatest, select, take, race } from 'redux-saga/effects';
|
||||||
|
import { delay } from 'redux-saga';
|
||||||
import { METEOR } from '../actions/actionsTypes';
|
import { METEOR } from '../actions/actionsTypes';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
|
||||||
import { connectSuccess, connectFailure } from '../actions/connect';
|
|
||||||
|
|
||||||
const getServer = ({ server }) => server.server;
|
const getServer = ({ server }) => server.server;
|
||||||
|
|
||||||
|
|
||||||
const connect = url => RocketChat.connect(url);
|
const connect = url => RocketChat.connect(url);
|
||||||
const test = function* test() {
|
const watchConnect = function* watchConnect() {
|
||||||
try {
|
const { disconnect } = yield race({
|
||||||
const server = yield select(getServer);
|
disconnect: take(METEOR.DISCONNECT),
|
||||||
const response = yield call(connect, server);
|
disconnected_by_user: take(METEOR.DISCONNECT_BY_USER)
|
||||||
yield put(connectSuccess(response));
|
});
|
||||||
} catch (err) {
|
if (disconnect) {
|
||||||
yield put(connectFailure(err.status));
|
while (true) {
|
||||||
|
const { connected } = yield race({
|
||||||
|
connected: take(METEOR.SUCCESS),
|
||||||
|
timeout: call(delay, 1000)
|
||||||
|
});
|
||||||
|
if (connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield RocketChat.reconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
// const watchConnect = function* watchConnect() {
|
const test = function* test() {
|
||||||
// };
|
// try {
|
||||||
|
const server = yield select(getServer);
|
||||||
|
// const response =
|
||||||
|
yield call(connect, server);
|
||||||
|
// yield put(connectSuccess(response));
|
||||||
|
// } catch (err) {
|
||||||
|
// yield put(connectFailure(err.status));
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(METEOR.REQUEST, test);
|
yield takeLatest(METEOR.REQUEST, test);
|
||||||
// yield fork(watchConnect);
|
// yield take(METEOR.SUCCESS, watchConnect);
|
||||||
// yield fork(auto);
|
yield takeLatest(METEOR.SUCCESS, watchConnect);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { take, put, call, takeLatest, select, all } from 'redux-saga/effects';
|
import { put, call, takeLatest, select, all } from 'redux-saga/effects';
|
||||||
import * as types from '../actions/actionsTypes';
|
import * as types from '../actions/actionsTypes';
|
||||||
import {
|
import {
|
||||||
loginRequest,
|
loginRequest,
|
||||||
|
@ -8,6 +8,7 @@ import {
|
||||||
registerIncomplete,
|
registerIncomplete,
|
||||||
loginSuccess,
|
loginSuccess,
|
||||||
loginFailure,
|
loginFailure,
|
||||||
|
logout,
|
||||||
setToken,
|
setToken,
|
||||||
registerSuccess,
|
registerSuccess,
|
||||||
setUsernameRequest,
|
setUsernameRequest,
|
||||||
|
@ -40,16 +41,13 @@ const getToken = function* getToken() {
|
||||||
console.log('getTokenerr', e);
|
console.log('getTokenerr', e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yield put(setToken());
|
return yield put(setToken());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
const handleLoginWhenServerChanges = function* handleLoginWhenServerChanges() {
|
||||||
try {
|
try {
|
||||||
yield take(types.METEOR.SUCCESS);
|
const user = yield call(getToken);
|
||||||
yield call(getToken);
|
|
||||||
|
|
||||||
const user = yield select(getUser);
|
|
||||||
if (user.token) {
|
if (user.token) {
|
||||||
yield put(loginRequest({ resume: user.token }));
|
yield put(loginRequest({ resume: user.token }));
|
||||||
}
|
}
|
||||||
|
@ -76,17 +74,19 @@ const handleLoginRequest = function* handleLoginRequest({ credentials }) {
|
||||||
|
|
||||||
// if user has username
|
// if user has username
|
||||||
if (me.username) {
|
if (me.username) {
|
||||||
user.username = me.username;
|
|
||||||
const userInfo = yield call(userInfoCall, { server, token: user.token, userId: user.id });
|
const userInfo = yield call(userInfoCall, { server, token: user.token, userId: user.id });
|
||||||
|
user.username = userInfo.user.username;
|
||||||
if (userInfo.user.roles) {
|
if (userInfo.user.roles) {
|
||||||
user.roles = userInfo.user.roles;
|
user.roles = userInfo.user.roles;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
yield put(registerIncomplete());
|
yield put(registerIncomplete());
|
||||||
}
|
}
|
||||||
|
|
||||||
yield put(loginSuccess(user));
|
yield put(loginSuccess(user));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err.error === 403) {
|
||||||
|
return yield put(logout());
|
||||||
|
}
|
||||||
yield put(loginFailure(err));
|
yield put(loginFailure(err));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -149,7 +149,7 @@ const handleForgotPasswordRequest = function* handleForgotPasswordRequest({ emai
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(types.SERVER.CHANGED, handleLoginWhenServerChanges);
|
yield takeLatest(types.METEOR.SUCCESS, handleLoginWhenServerChanges);
|
||||||
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
yield takeLatest(types.LOGIN.REQUEST, handleLoginRequest);
|
||||||
yield takeLatest(types.LOGIN.SUCCESS, saveToken);
|
yield takeLatest(types.LOGIN.SUCCESS, saveToken);
|
||||||
yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit);
|
yield takeLatest(types.LOGIN.SUBMIT, handleLoginSubmit);
|
||||||
|
|
|
@ -67,7 +67,7 @@ const watchRoomOpen = function* watchRoomOpen({ room }) {
|
||||||
const thread = yield fork(usersTyping, { rid: room.rid });
|
const thread = yield fork(usersTyping, { rid: room.rid });
|
||||||
yield take(types.ROOM.OPEN);
|
yield take(types.ROOM.OPEN);
|
||||||
cancel(thread);
|
cancel(thread);
|
||||||
subscriptions.forEach(sub => sub.stop());
|
subscriptions.forEach(sub => sub.unsubscribe());
|
||||||
};
|
};
|
||||||
|
|
||||||
const watchuserTyping = function* watchuserTyping({ status }) {
|
const watchuserTyping = function* watchuserTyping({ status }) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { put, takeEvery, call, takeLatest, race, take } from 'redux-saga/effects';
|
import { put, call, takeLatest, race, take } from 'redux-saga/effects';
|
||||||
import { delay } from 'redux-saga';
|
import { delay } from 'redux-saga';
|
||||||
import { AsyncStorage } from 'react-native';
|
import { AsyncStorage } from 'react-native';
|
||||||
import { SERVER } from '../actions/actionsTypes';
|
import { SERVER } from '../actions/actionsTypes';
|
||||||
import { connectRequest, disconnect } from '../actions/connect';
|
import { connectRequest, disconnect, disconnect_by_user } from '../actions/connect';
|
||||||
import { changedServer, serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
import { changedServer, serverSuccess, serverFailure, serverRequest, setServer } from '../actions/server';
|
||||||
import { logout } from '../actions/login';
|
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
import realm from '../lib/realm';
|
import realm from '../lib/realm';
|
||||||
import * as NavigationService from '../containers/routes/NavigationService';
|
import * as NavigationService from '../containers/routes/NavigationService';
|
||||||
|
@ -14,13 +13,13 @@ const validate = function* validate(server) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectServer = function* selectServer({ server }) {
|
const selectServer = function* selectServer({ server }) {
|
||||||
|
yield put(disconnect_by_user());
|
||||||
yield put(disconnect());
|
yield put(disconnect());
|
||||||
yield put(changedServer(server));
|
yield put(changedServer(server));
|
||||||
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
yield call([AsyncStorage, 'setItem'], 'currentServer', server);
|
||||||
yield put(connectRequest(server));
|
yield put(connectRequest(server));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const validateServer = function* validateServer({ server }) {
|
const validateServer = function* validateServer({ server }) {
|
||||||
try {
|
try {
|
||||||
yield delay(1000);
|
yield delay(1000);
|
||||||
|
@ -48,16 +47,14 @@ const addServer = function* addServer({ server }) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGotoAddServer = function* handleGotoAddServer() {
|
const handleGotoAddServer = function* handleGotoAddServer() {
|
||||||
yield put(logout());
|
|
||||||
yield call(AsyncStorage.removeItem, RocketChat.TOKEN_KEY);
|
yield call(AsyncStorage.removeItem, RocketChat.TOKEN_KEY);
|
||||||
yield delay(1000);
|
|
||||||
yield call(NavigationService.navigate, 'AddServer');
|
yield call(NavigationService.navigate, 'AddServer');
|
||||||
};
|
};
|
||||||
|
|
||||||
const root = function* root() {
|
const root = function* root() {
|
||||||
yield takeLatest(SERVER.REQUEST, validateServer);
|
yield takeLatest(SERVER.REQUEST, validateServer);
|
||||||
yield takeEvery(SERVER.SELECT, selectServer);
|
yield takeLatest(SERVER.SELECT, selectServer);
|
||||||
yield takeEvery(SERVER.ADD, addServer);
|
yield takeLatest(SERVER.ADD, addServer);
|
||||||
yield takeEvery(SERVER.GOTO_ADD, handleGotoAddServer);
|
yield takeLatest(SERVER.GOTO_ADD, handleGotoAddServer);
|
||||||
};
|
};
|
||||||
export default root;
|
export default root;
|
||||||
|
|
|
@ -8,7 +8,6 @@ import { connect } from 'react-redux';
|
||||||
import { setServer } from '../actions/server';
|
import { setServer } from '../actions/server';
|
||||||
import realm from '../lib/realm';
|
import realm from '../lib/realm';
|
||||||
import Fade from '../animations/fade';
|
import Fade from '../animations/fade';
|
||||||
import Banner from '../containers/Banner';
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
view: {
|
view: {
|
||||||
|
@ -184,7 +183,6 @@ export default class ListServerView extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<View style={styles.view}>
|
<View style={styles.view}>
|
||||||
<Banner />
|
|
||||||
<SafeAreaView style={styles.view}>
|
<SafeAreaView style={styles.view}>
|
||||||
<SectionList
|
<SectionList
|
||||||
style={styles.list}
|
style={styles.list}
|
||||||
|
|
|
@ -1,52 +1,13 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Text, TextInput, View, StyleSheet, Dimensions } from 'react-native';
|
import { Text, TouchableOpacity, ScrollView, TextInput } from 'react-native';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { serverRequest, addServer } from '../actions/server';
|
import { serverRequest, addServer } from '../actions/server';
|
||||||
import KeyboardView from '../presentation/KeyboardView';
|
import KeyboardView from '../presentation/KeyboardView';
|
||||||
|
import styles from './Styles';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
view: {
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'stretch',
|
|
||||||
backgroundColor: '#fff'
|
|
||||||
},
|
|
||||||
input: {
|
|
||||||
height: 40,
|
|
||||||
borderColor: '#aaa',
|
|
||||||
margin: 20,
|
|
||||||
padding: 5,
|
|
||||||
borderWidth: 0,
|
|
||||||
backgroundColor: '#f8f8f8'
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
textAlign: 'center',
|
|
||||||
color: '#888'
|
|
||||||
},
|
|
||||||
validateText: {
|
|
||||||
position: 'absolute',
|
|
||||||
color: 'green',
|
|
||||||
textAlign: 'center',
|
|
||||||
paddingLeft: 50,
|
|
||||||
paddingRight: 50,
|
|
||||||
width: '100%'
|
|
||||||
},
|
|
||||||
validText: {
|
|
||||||
color: 'green'
|
|
||||||
},
|
|
||||||
invalidText: {
|
|
||||||
color: 'red'
|
|
||||||
},
|
|
||||||
validatingText: {
|
|
||||||
color: '#aaa'
|
|
||||||
},
|
|
||||||
spaceView: {
|
|
||||||
flexGrow: 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
validInstance: !state.server.failure,
|
validInstance: !state.server.failure && !state.server.connecting,
|
||||||
validating: state.server.connecting
|
validating: state.server.connecting
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
validateServer: url => dispatch(serverRequest(url)),
|
validateServer: url => dispatch(serverRequest(url)),
|
||||||
|
@ -83,7 +44,7 @@ export default class NewServerView extends React.Component {
|
||||||
this.setState({ editable: true });
|
this.setState({ editable: true });
|
||||||
this.adding = false;
|
this.adding = false;
|
||||||
}
|
}
|
||||||
if (this.props.validInstance && !this.props.validating) {
|
if (this.props.validInstance) {
|
||||||
this.props.navigation.goBack();
|
this.props.navigation.goBack();
|
||||||
this.adding = false;
|
this.adding = false;
|
||||||
}
|
}
|
||||||
|
@ -127,7 +88,7 @@ export default class NewServerView extends React.Component {
|
||||||
if (this.props.validating) {
|
if (this.props.validating) {
|
||||||
return (
|
return (
|
||||||
<Text style={[styles.validateText, styles.validatingText]}>
|
<Text style={[styles.validateText, styles.validatingText]}>
|
||||||
Validating {this.state.url} ...
|
Validating {this.state.text} ...
|
||||||
</Text>
|
</Text>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -149,14 +110,17 @@ export default class NewServerView extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<KeyboardView
|
<KeyboardView
|
||||||
scrollEnabled={false}
|
contentContainerStyle={styles.container}
|
||||||
contentContainerStyle={[styles.view, { height: Dimensions.get('window').height }]}
|
|
||||||
keyboardVerticalOffset={128}
|
keyboardVerticalOffset={128}
|
||||||
>
|
>
|
||||||
<View style={styles.spaceView} />
|
<ScrollView
|
||||||
|
style={styles.loginView}
|
||||||
|
keyboardDismissMode='interactive'
|
||||||
|
keyboardShouldPersistTaps='always'
|
||||||
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
ref={ref => this.inputElement = ref}
|
ref={ref => this.inputElement = ref}
|
||||||
style={styles.input}
|
style={styles.input_white}
|
||||||
onChangeText={this.onChangeText}
|
onChangeText={this.onChangeText}
|
||||||
keyboardType='url'
|
keyboardType='url'
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
|
@ -164,12 +128,19 @@ export default class NewServerView extends React.Component {
|
||||||
autoCapitalize='none'
|
autoCapitalize='none'
|
||||||
autoFocus
|
autoFocus
|
||||||
editable={this.state.editable}
|
editable={this.state.editable}
|
||||||
onSubmitEditing={this.submit}
|
|
||||||
placeholder={this.state.defaultServer}
|
placeholder={this.state.defaultServer}
|
||||||
|
underlineColorAndroid='transparent'
|
||||||
/>
|
/>
|
||||||
<View style={styles.spaceView}>
|
<TouchableOpacity
|
||||||
|
disabled={!this.props.validInstance}
|
||||||
|
style={[styles.buttonContainer, this.props.validInstance ? null
|
||||||
|
: styles.disabledButton]}
|
||||||
|
onPress={this.submit}
|
||||||
|
>
|
||||||
|
<Text style={styles.button} accessibilityTraits='button'>Add</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
{this.renderValidation()}
|
{this.renderValidation()}
|
||||||
</View>
|
</ScrollView>
|
||||||
</KeyboardView>
|
</KeyboardView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import styles from './styles';
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
user: state.login.user,
|
user: state.login.user,
|
||||||
|
connected: state.meteor.connected,
|
||||||
baseUrl: state.settings.Site_Url
|
baseUrl: state.settings.Site_Url
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
setSearch: searchText => dispatch(setSearch(searchText))
|
setSearch: searchText => dispatch(setSearch(searchText))
|
||||||
|
@ -23,6 +24,7 @@ export default class extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
navigation: PropTypes.object.isRequired,
|
navigation: PropTypes.object.isRequired,
|
||||||
user: PropTypes.object.isRequired,
|
user: PropTypes.object.isRequired,
|
||||||
|
connected: PropTypes.bool,
|
||||||
baseUrl: PropTypes.string,
|
baseUrl: PropTypes.string,
|
||||||
setSearch: PropTypes.func
|
setSearch: PropTypes.func
|
||||||
}
|
}
|
||||||
|
@ -57,7 +59,7 @@ export default class extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserStatus() {
|
getUserStatus() {
|
||||||
return this.props.user.status || 'offline';
|
return (this.props.connected && this.props.user.status) || 'offline';
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserStatusLabel() {
|
getUserStatusLabel() {
|
||||||
|
|
|
@ -156,5 +156,14 @@ export default StyleSheet.create({
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
justifyContent: 'space-around'
|
justifyContent: 'space-around'
|
||||||
|
},
|
||||||
|
validText: {
|
||||||
|
color: 'green'
|
||||||
|
},
|
||||||
|
invalidText: {
|
||||||
|
color: 'red'
|
||||||
|
},
|
||||||
|
validatingText: {
|
||||||
|
color: '#aaa'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
|
@ -21,16 +21,17 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@storybook/react-native": "^3.2.15",
|
"@storybook/addons": "^3.2.18",
|
||||||
|
"@storybook/react-native": "^3.2.18",
|
||||||
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
"babel-plugin-transform-decorators-legacy": "^1.3.4",
|
||||||
"babel-plugin-transform-remove-console": "^6.8.5",
|
"babel-plugin-transform-remove-console": "^6.8.5",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"ejson": "^2.1.2",
|
"ejson": "^2.1.2",
|
||||||
"moment": "^2.19.3",
|
"moment": "^2.20.1",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.2.0",
|
"react": "^16.2.0",
|
||||||
"react-emojione": "^5.0.0",
|
"react-emojione": "^5.0.0",
|
||||||
"react-native": "^0.50.4",
|
"react-native": "^0.51.0",
|
||||||
"react-native-action-button": "^2.8.3",
|
"react-native-action-button": "^2.8.3",
|
||||||
"react-native-actionsheet": "^2.3.0",
|
"react-native-actionsheet": "^2.3.0",
|
||||||
"react-native-animatable": "^1.2.4",
|
"react-native-animatable": "^1.2.4",
|
||||||
|
@ -59,16 +60,16 @@
|
||||||
"redux-immutable-state-invariant": "^2.1.0",
|
"redux-immutable-state-invariant": "^2.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-saga": "^0.16.0",
|
"redux-saga": "^0.16.0",
|
||||||
"regenerator-runtime": "^0.11.0",
|
"regenerator-runtime": "^0.11.1",
|
||||||
"remote-redux-devtools": "^0.5.12",
|
"remote-redux-devtools": "^0.5.12",
|
||||||
"simple-markdown": "^0.3.1",
|
"simple-markdown": "^0.3.1",
|
||||||
"snyk": "^1.41.1",
|
"snyk": "^1.61.1",
|
||||||
"strip-ansi": "^4.0.0"
|
"strip-ansi": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-storyshots": "^3.2.15",
|
"@storybook/addon-storyshots": "^3.2.18",
|
||||||
"babel-eslint": "^8.0.2",
|
"babel-eslint": "^8.0.2",
|
||||||
"babel-jest": "21.2.0",
|
"babel-jest": "^22.0.3",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.10",
|
"babel-plugin-transform-react-remove-prop-types": "^0.4.10",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"babel-preset-react-native": "4.0.0",
|
"babel-preset-react-native": "4.0.0",
|
||||||
|
@ -76,12 +77,12 @@
|
||||||
"eslint": "^4.12.0",
|
"eslint": "^4.12.0",
|
||||||
"eslint-config-airbnb": "^16.1.0",
|
"eslint-config-airbnb": "^16.1.0",
|
||||||
"eslint-plugin-import": "^2.8.0",
|
"eslint-plugin-import": "^2.8.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.0.2",
|
"eslint-plugin-jsx-a11y": "^6.0.3",
|
||||||
"eslint-plugin-react": "^7.5.1",
|
"eslint-plugin-react": "^7.5.1",
|
||||||
"eslint-plugin-react-native": "^3.2.0",
|
"eslint-plugin-react-native": "^3.2.0",
|
||||||
"identity-obj-proxy": "^3.0.0",
|
"identity-obj-proxy": "^3.0.0",
|
||||||
"jest": "21.2.1",
|
"jest": "^22.0.3",
|
||||||
"jest-cli": "^21.2.1",
|
"jest-cli": "^22.0.3",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.2.0",
|
||||||
"react-test-renderer": "^16.2.0"
|
"react-test-renderer": "^16.2.0"
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue