diff --git a/package.json b/package.json index 578e06d..d221a70 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "react-dom": "^15.6.1", "react-redux": "^5.0.5", "redux": "^3.7.2", + "redux-thunk": "^2.2.0", "style-loader": "^0.18.2", "webpack": "^3.1.0", "webpack-dev-middleware": "^1.12.0", diff --git a/src/actions/filter_subscribe.js b/src/actions/filter_subscribe.js new file mode 100644 index 0000000..c99cc40 --- /dev/null +++ b/src/actions/filter_subscribe.js @@ -0,0 +1,18 @@ +import ws_send from '../socket'; + +export const FILTER_SUBSCRIBE = 'FILTER_SUBSCRIBE'; +export const FILTER_UNSUBSCRIBE = 'FILTER_UNSUBSCRIBE'; + +export function filter_subscribe(kind='torrent', criteria=[]) { + return dispatch => { + const serial = ws_send(FILTER_SUBSCRIBE, { criteria, kind }); + dispatch({ type: FILTER_SUBSCRIBE, serial }); + }; +} + +export function filter_unsubscribe(serial) { + return dispatch => { + ws_send(FILTER_UNSUBSCRIBE, { serial }); + dispatch({ type: FILTER_UNSUBSCRIBE, serial }); + }; +} diff --git a/src/actions/subscribe.js b/src/actions/subscribe.js new file mode 100644 index 0000000..ce77c5f --- /dev/null +++ b/src/actions/subscribe.js @@ -0,0 +1,25 @@ +import ws_send from '../socket'; + +export const SUBSCRIBE = 'SUBSCRIBE'; +export const UNSUBSCRIBE = 'UNSUBSCRIBE'; + +export function subscribe(...ids) { + return dispatch => { + const serial = ws_send(SUBSCRIBE, { ids }); + dispatch({ + type: SUBSCRIBE, + serial, + ids + }); + }; +} + +export function unsubscribe(...ids) { + return dispatch => { + ws_send(UNSUBSCRIBE, { ids }); + dispatch({ + type: UNSUBSCRIBE, + ids + }); + }; +} diff --git a/src/index.js b/src/index.js index f29ac0a..07c8669 100644 --- a/src/index.js +++ b/src/index.js @@ -4,6 +4,13 @@ import { Provider } from 'react-redux'; import store from './store'; import scss from '../scss/base.scss'; +import { ws_init } from './socket'; +import { filter_subscribe } from './actions/filter_subscribe'; + +ws_init(() => { + store.dispatch(filter_subscribe()); + store.dispatch(filter_subscribe('server')); +}); render( diff --git a/src/reducers/filter_subscribe.js b/src/reducers/filter_subscribe.js new file mode 100644 index 0000000..1d02d1b --- /dev/null +++ b/src/reducers/filter_subscribe.js @@ -0,0 +1,18 @@ +import { + FILTER_SUBSCRIBE, + FILTER_UNSUBSCRIBE +} from '../actions/filter_subscribe'; + +export default function filter_subscribe(state = [], action) { + switch (action.type) { + case FILTER_SUBSCRIBE: { + const { serial } = action; + return [...state, serial]; + } + case FILTER_UNSUBSCRIBE: { + const { serial } = action; + return state.filter(s => s !== serial); + } + } + return state; +} diff --git a/src/reducers/index.js b/src/reducers/index.js index 0ec8ab2..1cc260d 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -1,7 +1,11 @@ import { combineReducers } from 'redux'; +import subscribe from './subscribe'; +import filter_subscribe from './filter_subscribe'; import torrents from './torrents'; const root = combineReducers({ + subscribe, + filter_subscribe, torrents }); diff --git a/src/reducers/subscribe.js b/src/reducers/subscribe.js new file mode 100644 index 0000000..af4c475 --- /dev/null +++ b/src/reducers/subscribe.js @@ -0,0 +1,15 @@ +import { SUBSCRIBE, UNSUBSCRIBE } from '../actions/subscribe'; + +export default function subscribe(state = [], action) { + switch (action.type) { + case SUBSCRIBE: { + const { ids, serial } = action; + return [ ...state, ...ids.map(id => ({ serial, id })) ]; + } + case UNSUBSCRIBE: { + const { ids } = action; + return state.filter(sub => ids.indexOf(sub.id) === -1); + } + } + return state; +} diff --git a/src/reducers/torrents.js b/src/reducers/torrents.js index 4aba48e..10ddeaa 100644 --- a/src/reducers/torrents.js +++ b/src/reducers/torrents.js @@ -2,13 +2,13 @@ import { UPDATE_RESOURCES } from '../actions/resources'; export default function torrents(state = {}, action) { switch (action.type) { - case UPDATE_RESOURCES: - return { - ...state, - ...action.resources - .filter(r => r.type === "torrent") - .reduce((s, r) => s[r.id] = r, {}) - }; + case UPDATE_RESOURCES: + return { + ...state, + ...action.resources + .filter(r => r.type === "torrent") + .reduce((s, r) => ({ ...s, [r.id]: r }), {}) + }; } return state; } diff --git a/src/socket.js b/src/socket.js new file mode 100644 index 0000000..0072dda --- /dev/null +++ b/src/socket.js @@ -0,0 +1,41 @@ +import { dispatch } from './store'; +import { subscribe } from './actions/subscribe'; + +let ws; +let serial = 0; +let transactions = {}; + +export default function ws_send(type, body, callback = null) { + const _serial = serial++; + if (callback) { + transactions[_serial] = callback; + } + const msg = JSON.stringify({ + type, + serial: _serial, + ...body + }); + console.log("->", msg); + ws.send(msg); + return _serial; +} + +const handlers = { + RESOURCES_EXTANT: msg => dispatch(subscribe(...msg.ids)), + UPDATE_RESOURCES: msg => dispatch(msg) +}; + +function ws_recv(e) { + const msg = JSON.parse(e.data); + console.log("<-", msg); + const cb = transactions[msg.serial]; + cb && cb(msg); + const handler = handlers[msg.type]; + handler && handler(msg); +} + +export function ws_init(cb) { + ws = new WebSocket("ws://127.0.0.1:8412"); + ws.addEventListener("open", cb); + ws.addEventListener("message", ws_recv); +} diff --git a/src/store.js b/src/store.js index 30ae752..faf3c90 100644 --- a/src/store.js +++ b/src/store.js @@ -1,10 +1,19 @@ -import { createStore } from 'redux'; - +import { + applyMiddleware, + createStore, + compose +} from 'redux'; +import thunk from 'redux-thunk'; import reducer from './reducers'; +const _compose = + window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ + || compose; const store = createStore( reducer, - window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() + _compose(applyMiddleware(thunk)), ); +export const dispatch = action => store.dispatch(action); + export default store;