diff --git a/app/api/faye.js b/app/api/faye.js new file mode 100644 index 0000000..54d960a --- /dev/null +++ b/app/api/faye.js @@ -0,0 +1,193 @@ +import Faye from 'react-native-halley' +const noop = () => {} +import {NetInfo} from 'react-native' + +class ClientAuthExt { + constructor(token) { + this._token = token + } + + outgoing(message, cb) { + if (message.channel === '/meta/handshake') { + if (!message.ext) { message.ext = {}; } + message.ext.token = this._token + message.ext.realtimeLibrary = 'halley' + } + + cb(message); + } + + incoming(message, cb) { + if (message.channel === '/meta/handshake') { + if (message.successful) { + console.log('Successfuly subscribed: ', message); + } else { + console.log('Something went wrong: ', message.error); + } + } + + cb(message) + } +} + +class LogExt { + outgoing(message, cb) { + console.log('Log outgoing message: ', message) + cb(message) + } + + incoming(message, cb) { + console.log('Log incoming message: ', message) + cb(message) + } +} + +class SnapshotExt { + constructor(fn) { + this._handler = fn + } + incoming(message, cb) { + if (message.channel === '/meta/subscribe' && message.ext && message.ext.snapshot) { + this._handler(message) + } + + cb(message) + } +} + + +export default class HalleyClient { + constructor({token, snapshotHandler}) { + this._client = new Faye.Client('https://ws.gitter.im/bayeux', { + timeout: 3000, + retry: 3000, + interval: 0, + maxNetworkDelay: 3000, + connectTimeout: 3000, + disconnectTimeout: 1000 + }) + this._token = token + this._snapshotHandler = snapshotHandler + this._subsciptions = [] + this._isConnectedToNetwork = true + this._transport = true + + this.setupNetworkListeners() + this.setupInternetListener() + } + + setToken(token) { + this._token = token + } + + setSnapshotHandler(fn) { + this._snapshotHandler = fn + } + + setup() { + if (!this._token) { + throw new Error('You need to add token') + } + + this._snapshotExt = new SnapshotExt(this._snapshotHandler || noop) + this._authExt = new ClientAuthExt(this._token) + } + + create() { + if (!this._token) { + throw new Error('You need to add token') + } + this._client.addExtension(this._authExt) + this._client.addExtension(this._snapshotExt) + this._client.addExtension(new LogExt()) + } + + subscribe({type, url, handler}) { + return new Promise((res, rej) => { + if (this._checkSubscriptionAlreadyExist({type, url})) { + rej(`Subscription with type ${type} and url ${url} already exist.`) + } + + const subscriptionObject = this._client.subscribe(url, handler) + + subscriptionObject + .then(() => { + this._subsciptions.push({ + type, + url, + handler, + subscriptionObject + }) + res(true) + }) + // .catch(err => { + // rej(err) + // }) + }) + } + + unsubscribe({type, url}) { + return new Promise((res, rej) => { + const subscription = this._findSubscription({type, url}) + if (!subscription) { + rej(`There is no subscription with type ${type} and url ${url}`) + } else { + subscription.subscriptionObject.unsubscribe() + .then(() => { + this._removeSubscription(subscription) + res(true) + }) + // .catch(err => rej(err)) + } + }) + } + + setupNetworkListeners() { + // this._client.on('transport:down', () => { + // this._transport = false + // }) + // + // this._client.on('transport:up', () => { + // this._transport = true + // }) + } + + setupInternetListener() { + // NetInfo.isConnected.addEventListener( + // 'change', + // () => { + // debugger + // NetInfo.isConnected.fetch() + // .then(isConnected => { + // if (isConnected) { + // this._client.connect() + // // this._isConnectedToNetwork = true + // } else { + // // this._isConnectedToNetwork = false + // } + // }) + // } + // ) + } + + _checkSubscriptionAlreadyExist(subscriptionOptions) { + const subscription = this._findSubscription(subscriptionOptions) + return !!subscription + } + + _findSubscription({type, url}) { + return this._subsciptions.find( + item => item.type === type && item.url === url + ) + } + + _removeSubscription({type, url}) { + const index = this._subsciptions.indexOf( + item => item.type === type && item.url === url + ) + + if (index !== -1) { + this._subsciptions.splice(index, 1) + } + } +} diff --git a/app/modules/app.js b/app/modules/app.js index 703a486..cda415b 100644 --- a/app/modules/app.js +++ b/app/modules/app.js @@ -4,7 +4,7 @@ import {getRooms, getSuggestedRooms} from './rooms' import {initializeUi} from './ui' import {NetInfo, AppState} from 'react-native' import { - setupFayeEvents, + // setupFayeEvents, setupFaye, onNetStatusChangeFaye, subscribeToChatMessages, @@ -43,7 +43,7 @@ export function init() { return } - dispatch(setupFayeEvents()) + // dispatch(setupFayeEvents()) dispatch({ type: INITIALIZED, token }) // getting base current user's information @@ -55,6 +55,7 @@ export function init() { ]) rootNavigator.startAppWithScreen({screen: 'gm.Home', showDrawer: true}) + dispatch(subscribeToRooms()) dispatch(setupNetStatusListener()) // if you need debug room screen, just comment nevigation to 'hone' // and uncomment navigation to 'room' @@ -75,7 +76,7 @@ function setupNetStatusListener() { NetInfo.isConnected.addEventListener('change', async status => { dispatch({type: CHANGE_NET_STATUS, payload: status}) - await dispatch(onNetStatusChangeFaye(status)) + // await dispatch(onNetStatusChangeFaye(status)) } ); } @@ -93,15 +94,15 @@ function setupAppStatusListener() { if (!token.length || !netStatus) { return } - const {fayeConnected} = getState().app - if (!fayeConnected) { - await setupFaye() - } - - const {activeRoom} = getState().rooms - if (!!activeRoom) { - dispatch(subscribeToChatMessages(activeRoom)) - } + // const {fayeConnected} = getState().app + // if (!fayeConnected) { + // await setupFaye() + // } + + // const {activeRoom} = getState().rooms + // if (!!activeRoom) { + // dispatch(subscribeToChatMessages(activeRoom)) + // } dispatch(subscribeToRooms()) } catch (error) { // console.log(error) diff --git a/app/modules/realtime.js b/app/modules/realtime.js index 3e79799..574d967 100644 --- a/app/modules/realtime.js +++ b/app/modules/realtime.js @@ -4,6 +4,9 @@ import {updateRoomState, receiveRoomsSnapshot} from './rooms' import {appendMessages, updateMessageRealtime, receiveRoomMessagesSnapshot} from './messages' import {receiveRoomEventsSnapshot} from './activity' import {receiveReadBySnapshot} from './readBy' +import HalleyClient from '../api/faye' + +let client = null /** * Constants @@ -19,9 +22,6 @@ export const SUBSCRIBE_TO_ROOM_EVENTS = 'realtime/SUBSCRIBE_TO_ROOM_EVENTS' export const UNSUBSCRIBE_TO_ROOM_EVENTS = 'realtime/UNSUBSCRIBE_TO_ROOM_EVENTS' export const SUBSCRIBE_TO_READ_BY = 'realtime/SUBSCRIBE_TO_READ_BY' export const UNSUBSCRIBE_FROM_READ_BY = 'realtime/UNSUBSCRIBE_FROM_READ_BY' -export const PUSH_SUBSCRIPTION = 'realtime/PUSH_SUBSCRIPTION' -export const DELETE_SUBSCRIPTION = 'realtime/DELETE_SUBSCRIPTION' -export const SUBSCRIBED_TO_CHANNELS = 'realtime/SUBSCRIBED_TO_CHANNELS' const roomsRegx = /\/api\/v1\/user\/[a-f\d]{24}\/rooms/ const messagesRegx = /\/api\/v1\/rooms\/[a-f\d]{24}\/chatMessages/ @@ -37,33 +37,14 @@ const readByRegxIds = /\/api\/v1\/rooms\/([a-f\d]{24})\/chatMessages\/([a-f\d]{2 */ export function setupFaye() { - return async (dispatch, getState) => { - console.log('RECONNECT TO FAYE') - FayeGitter.setAccessToken(getState().auth.token) - FayeGitter.create() - FayeGitter.logger() - try { - dispatch({type: FAYE_CONNECTING}) - const result = await FayeGitter.connect() - dispatch({type: FAYE_CONNECT, payload: result}) - // dispatch(subscribeToChannels()) - } catch (err) { - console.log(err) // eslint-disable-line no-console - } - } -} - -export function checkFayeConnection() { - return async (dispatch, getState) => { - try { - const connectionStatus = await FayeGitter.checkConnectionStatus() - // console.log('CONNECTION_STATUS', connectionStatus) - if (!connectionStatus) { - await dispatch(setupFaye()) - } - } catch (error) { - console.error(error.message) - } + return (dispatch, getState) => { + console.log('CONNECT TO FAYE') + client = new HalleyClient({ + token: getState().auth.token, + snapshotHandler: (message) => dispatch(dispatchSnapshotEvent(message)) + }) + client.setup() + client.create() } } @@ -72,160 +53,58 @@ export function checkFayeConnection() { */ export function onNetStatusChangeFaye(status) { - return async (dispatch, getState) => { - const {isFayeConnecting} = getState().app - if (isFayeConnecting) { - return - } - const connectionStatus = await FayeGitter.checkConnectionStatus() - if (!status && connectionStatus) { - dispatch({type: FAYE_CONNECT, payload: status}) - } - try { - if (status && !connectionStatus) { - await dispatch(setupFaye()) - } - } catch (error) { - console.warn(error.message) - } - } -} - -/** - * Setup faye events - */ - -export function setupFayeEvents() { return (dispatch, getState) => { - const EventEmitter = Platform.OS === 'ios' ? new NativeEventEmitter(FayeGitter) : DeviceEventEmitter - EventEmitter - .addListener('FayeGitter:Connected', log => { - console.log('CONNECTED') - dispatch(subscribeToChannels()) - }) - EventEmitter - .addListener('FayeGitter:onDisconnected', log => { - console.log(log) // eslint-disable-line no-console - dispatch(setupFaye()) - }) - EventEmitter - .addListener('FayeGitter:onFailedToCreate', log => { - console.log(log) // eslint-disable-line no-console - const {online} = getState().app - if (online === true) { - dispatch(setupFaye()) - } - }) - EventEmitter - .addListener('FayeGitter:Message', event => { - dispatch(parseEvent(event)) - }) - EventEmitter - .addListener('FayeGitter:log', event => { - dispatch(parseSnapshotEvent(event)) - }) - EventEmitter - .addListener('FayeGitter:SubscribtionFailed', log => console.log(log)) // eslint-disable-line no-console - EventEmitter - .addListener('FayeGitter:Subscribed', event => { - console.log('SUBSCRIBED', event) // eslint-disable-line no-console - if (Platform.OS === 'ios') { - try { - const {channel, ext} = event - const snapshot = JSON.parse(ext).snapshot - if (typeof channel !== 'undefined' && typeof snapshot !== 'undefined') { - dispatch(parseSnapshotForChannel(channel, snapshot)) - } - } catch (error) { - console.warn(error.message) - } - } - }) - EventEmitter - .addListener('FayeGitter:Unsubscribed', log => console.log('UNSUBSCRIBED', log)) // eslint-disable-line no-console } } -export function removeFayeEvents() { - return (dispatch) => { - DeviceEventEmitter.removeEventListener('FayeGitter:Connected') - DeviceEventEmitter.removeEventListener('FayeGitter:onDisconnected') - DeviceEventEmitter.removeEventListener('FayeGitter:onFailedToCreate') - DeviceEventEmitter.removeEventListener('FayeGitter:Message') - DeviceEventEmitter.removeEventListener('FayeGitter:log') - DeviceEventEmitter.removeEventListener('FayeGitter:SubscribtionFailed') - DeviceEventEmitter.removeEventListener('FayeGitter:Subscribed') - DeviceEventEmitter.removeEventListener('FayeGitter:Unsubscribed') - } -} /** * Function which parse incoming events and dispatchs needed action */ -function parseEvent(event) { +function dispatchMessageEvent(message) { return (dispatch, getState) => { - // console.log('MESSAGE', event) - const message = JSON.parse(event.json) + console.log('MESSAGE', message) const {id} = getState().viewer.user const {activeRoom} = getState().rooms - const roomsChannel = `/api/v1/user/${id}/rooms` - const chatMessages = `/api/v1/rooms/${activeRoom}/chatMessages` - if (event.channel.match(roomsChannel)) { - dispatch(updateRoomState(message)) + if (!!message.model.fromUser && message.model.fromUser.id !== id && message.operation === 'create') { + dispatch(appendMessages(activeRoom, [message.model])) } - if (event.channel.match(chatMessages)) { - if (!!message.model.fromUser && message.model.fromUser.id !== id && message.operation === 'create') { - dispatch(appendMessages(activeRoom, [message.model])) - } - - if (message.operation === 'update' || message.operation === 'patch') { - dispatch(updateMessageRealtime(activeRoom, message.model)) - } + if (message.operation === 'update' || message.operation === 'patch') { + dispatch(updateMessageRealtime(activeRoom, message.model)) } } } -export function parseSnapshotEvent(event) { - return dispatch => { - if (!event.log.match('Received message: ')) { - return - } - - const message = JSON.parse(event.log.split('Received message: ')[1]) - - if (message.channel !== '/meta/subscribe' || message.successful !== true) { - return - } - - if (message.hasOwnProperty('ext') && message.ext.hasOwnProperty('snapshot')) { - dispatch(parseSnapshotForChannel(message.subscription, message.ext.snapshot)) - } +function dispatchRoomEvent(message) { + return (dispatch, getState) => { + console.log('MESSAGE', message) + dispatch(updateRoomState(message)) } } -export function parseSnapshotForChannel(channel, snapshot) { +export function dispatchSnapshotEvent({subscription, ext: {snapshot}}) { return dispatch => { - if (channel.match(roomsRegx)) { + if (subscription.match(roomsRegx)) { dispatch(receiveRoomsSnapshot(snapshot)) } - if (channel.match(messagesRegx)) { - const id = channel.match(messagesRegxIds)[1] + if (subscription.match(messagesRegx)) { + const id = subscription.match(messagesRegxIds)[1] dispatch(receiveRoomMessagesSnapshot(id, snapshot)) } - if (channel.match(eventsRegx)) { - const id = channel.match(eventsRegxIds)[1] + if (subscription.match(eventsRegx)) { + const id = subscription.match(eventsRegxIds)[1] dispatch(receiveRoomEventsSnapshot(id, snapshot)) } - if (channel.match(readByRegx)) { - const messageId = channel.match(readByRegxIds)[2] + if (subscription.match(readByRegx)) { + const messageId = subscription.match(readByRegxIds)[2] dispatch(receiveReadBySnapshot(messageId, snapshot)) } } @@ -237,12 +116,25 @@ export function parseSnapshotForChannel(channel, snapshot) { export function subscribeToRooms() { return async (dispatch, getState) => { - await checkFayeConnection() - const {id} = getState().viewer.user - const subscription = `/api/v1/user/${id}/rooms` - FayeGitter.subscribe(subscription) - dispatch({type: ROOMS_SUBSCRIBED}) - dispatch(pushSubscription(subscription)) + try { + const {id} = getState().viewer.user + const url = `/api/v1/user/${id}/rooms` + const type = 'userRooms' + // const result = await client.subscribe({ + // url, + // type, + // handler: evt => dispatch(dispatchRoomEvent(evt)) + // }) + dispatch({type: ROOMS_SUBSCRIBED}) + + // await client.subscribe({ + // url: `/api/v1/user/${id}`, + // type, + // handler: evt => console.log('USER: ', evt) + // }) + } catch (err) { + console.log(err) + } } } @@ -251,12 +143,25 @@ export function subscribeToRooms() { */ export function subscribeToChatMessages(roomId) { - return async dispatch => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/chatMessages` - FayeGitter.subscribe(subscription) - dispatch({type: SUBSCRIBE_TO_CHAT_MESSAGES, roomId}) - dispatch(pushSubscription(subscription)) + return async (dispatch, getState) => { + try { + const url = `/api/v1/rooms/${roomId}/chatMessages` + const type = 'chatMessages' + const result = await client.subscribe({ + url, + type, + handler: evt => dispatch(dispatchMessageEvent(evt)) + }) + dispatch({type: SUBSCRIBE_TO_CHAT_MESSAGES, roomId}) + // const {id} = getState().viewer.user + // await client.subscribe({ + // url: `/api/v1/user/${id}/rooms/${roomId}/unreadItems`, + // type: 'ureadItems', + // handler: evt => console.log('UnreadItems: ', evt) + // }) + } catch (err) { + console.log(err) + } } } @@ -265,23 +170,35 @@ export function subscribeToChatMessages(roomId) { */ export function unsubscribeToChatMessages(roomId) { - return async (dispatch) => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/chatMessages` - FayeGitter.unsubscribe(subscription) - dispatch({type: UNSUBSCRIBE_TO_CHAT_MESSAGES, roomId}) - dispatch(deleteSubscription(subscription)) + return async dispatch => { + try { + const url = `/api/v1/rooms/${roomId}/chatMessages` + const type = 'chatMessages' + const result = await client.unsubscribe({ + url, + type + }) + dispatch({type: UNSUBSCRIBE_TO_CHAT_MESSAGES, roomId}) + } catch (err) { + console.log(err) + } } } export function subscribeToRoomEvents(roomId) { return async dispatch => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/events` - FayeGitter.subscribe(subscription) - dispatch({type: SUBSCRIBE_TO_ROOM_EVENTS, roomId}) - dispatch(pushSubscription(subscription)) + try { + const url = `/api/v1/rooms/${roomId}/events` + const type = 'roomEvents' + const result = await client.subscribe({ + url, + type + }) + dispatch({type: SUBSCRIBE_TO_ROOM_EVENTS, roomId}) + } catch (err) { + console.log(err) + } } } @@ -291,21 +208,33 @@ export function subscribeToRoomEvents(roomId) { export function unsubscribeToRoomEvents(roomId) { return async (dispatch) => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/events` - FayeGitter.unsubscribe(subscription) - dispatch({type: UNSUBSCRIBE_TO_ROOM_EVENTS, roomId}) - dispatch(deleteSubscription(subscription)) + try { + const url = `/api/v1/rooms/${roomId}/events` + const type = 'roomEvents' + const result = await client.unsubscribe({ + url, + type + }) + dispatch({type: UNSUBSCRIBE_TO_ROOM_EVENTS, roomId}) + } catch (err) { + console.log(err) + } } } export function subscribeToReadBy(roomId, messageId) { return async dispatch => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/chatMessages/${messageId}/readBy` - FayeGitter.subscribe(subscription) - dispatch({type: SUBSCRIBE_TO_READ_BY, roomId}) - dispatch(pushSubscription(subscription)) + try { + const url = `/api/v1/rooms/${roomId}/chatMessages/${messageId}/readBy` + const type = 'roomEvents' + const result = await client.subscribe({ + url, + type + }) + dispatch({type: SUBSCRIBE_TO_READ_BY, roomId}) + } catch (err) { + console.log(err) + } } } @@ -315,42 +244,16 @@ export function subscribeToReadBy(roomId, messageId) { export function unsubscribeFromReadBy(roomId, messageId) { return async (dispatch) => { - await checkFayeConnection() - const subscription = `/api/v1/rooms/${roomId}/chatMessages/${messageId}/readBy` - FayeGitter.unsubscribe(subscription) - dispatch({type: UNSUBSCRIBE_FROM_READ_BY, roomId}) - dispatch(deleteSubscription(subscription)) - } -} - -export function pushSubscription(subscription) { - return (dispatch, getState) => { - const {subscriptions} = getState().realtime - if (!subscriptions.find(item => item === subscription)) { - dispatch({type: PUSH_SUBSCRIPTION, subscription}) - // console.log('PUSH_SUBSCRIPTION', subscription) - } - } -} - -export function deleteSubscription(subscription) { - return (dispatch, getState) => { - const {subscriptions} = getState().realtime - if (!!subscriptions.find(item => item === subscription)) { - dispatch({type: DELETE_SUBSCRIPTION, subscription}) - // console.log('DELETE_SUBSCRIPTION', subscription) - } - } -} - -export function subscribeToChannels() { - return (dispatch, getState) => { - const {subscriptions} = getState().realtime - if (subscriptions.length === 0) { - dispatch(subscribeToRooms()) - } else { - subscriptions.forEach(subscription => FayeGitter.subscribe(subscription)) - dispatch({type: SUBSCRIBED_TO_CHANNELS, subscriptions}) + try { + const url = `/api/v1/rooms/${roomId}/chatMessages/${messageId}/readBy` + const type = 'roomEvents' + const result = await client.unsubscribe({ + url, + type + }) + dispatch({type: UNSUBSCRIBE_FROM_READ_BY, roomId}) + } catch (err) { + console.log(err) } } } @@ -390,16 +293,6 @@ export default function realtime(state = initialState, action) { roomMessagesSubscription: action.payload } - case PUSH_SUBSCRIPTION: - return {...state, - subscriptions: state.subscriptions.concat(action.subscription) - } - - case DELETE_SUBSCRIPTION: - return {...state, - subscriptions: state.subscriptions.filter(subscription => action.subscription !== subscription) - } - default: return state } diff --git a/app/modules/rooms.js b/app/modules/rooms.js index 354ce20..543a8b5 100644 --- a/app/modules/rooms.js +++ b/app/modules/rooms.js @@ -307,7 +307,7 @@ export function changeNotificationSettings(roomId, index) { export function refreshRooms() { return async (dispatch, getState) => { dispatch(getRooms()) - await checkFayeConnection() + // await checkFayeConnection() } } diff --git a/app/screens/RoomSettings/index.js b/app/screens/RoomSettings/index.js index 08b4df3..7ec05be 100644 --- a/app/screens/RoomSettings/index.js +++ b/app/screens/RoomSettings/index.js @@ -74,10 +74,6 @@ class RoomSettings extends Component { const {dispatch, roomId} = this.props dispatch(changeNotificationSettings(roomId, pickerState)) - - setTimeout(() => { - this.navigateBack() - }, 500) } renderNotificationsSection() { diff --git a/npm-debug.log.1906906186 b/npm-debug.log.1906906186 new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json index 8b89860..adc908e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "android-release": "node node_modules/react-native/local-cli/cli.js run-android --variant=release" }, "dependencies": { + "halley": "git+ssh://git@github.com/terrysahaidak/react-native-halley.git#master", "lodash": "^4.2.1", "moment": "^2.11.2", "prop-types": "^15.5.10", @@ -43,14 +44,15 @@ "remote-redux-devtools": "^0.1.1" }, "devDependencies": { + "babel-eslint": "~4.1.6", "babel-jest": "19.0.0", "babel-preset-react-native": "1.9.1", - "jest": "19.0.2", - "react-test-renderer": "16.0.0-alpha.6", "eslint": "~1.10.3", "eslint-config-airbnb": "~2.1.1", "eslint-plugin-react": "~3.13.1", - "babel-eslint": "~4.1.6" + "faye": "^1.2.4", + "jest": "19.0.2", + "react-test-renderer": "16.0.0-alpha.6" }, "jest": { "preset": "react-native"