diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..46bc5fc --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "airbnb", + "rules": { + "func-names": ["error", "never"] + } +} \ No newline at end of file diff --git a/example/peripheral/src/index.js b/example/peripheral/src/index.js index 6aa9b37..646ccf2 100644 --- a/example/peripheral/src/index.js +++ b/example/peripheral/src/index.js @@ -1,8 +1,8 @@ import { createStore } from 'redux'; -import { startPeripheral } from 'redux-bluetooth/build/peripheral'; +import startPeripheral from 'redux-bluetooth/build/peripheral'; import reducer from './reducer'; -let store = createStore(reducer); +const store = createStore(reducer); -startPeripheral('Counter', store); \ No newline at end of file +startPeripheral('Counter', store); diff --git a/example/peripheral/src/reducer.js b/example/peripheral/src/reducer.js index e9fce73..b0b8952 100644 --- a/example/peripheral/src/reducer.js +++ b/example/peripheral/src/reducer.js @@ -1,13 +1,13 @@ export default function counter(state = 0, { type }) { console.log('Counter: ---------------------------'); - console.log(type) - console.log(state) + console.log(type); + console.log(state); switch (type) { - case 'INCREMENT': - return state + 1 - case 'DECREMENT': - return state - 1 - default: - return state + case 'INCREMENT': + return state + 1; + case 'DECREMENT': + return state - 1; + default: + return state; } -} \ No newline at end of file +} diff --git a/example/webapp/src/actions/index.js b/example/webapp/src/actions/index.js index f2b627d..ccc657e 100644 --- a/example/webapp/src/actions/index.js +++ b/example/webapp/src/actions/index.js @@ -4,6 +4,6 @@ export function increment() { return { type: TYPES.INCREMENT }; } -export function decrement() { +export function decrement() { return { type: TYPES.DECREMENT }; } diff --git a/example/webapp/src/actions/types.js b/example/webapp/src/actions/types.js index abba9db..930d9ef 100644 --- a/example/webapp/src/actions/types.js +++ b/example/webapp/src/actions/types.js @@ -1,2 +1,2 @@ export const INCREMENT = 'INCREMENT'; -export const DECREMENT = 'DECREMENT'; \ No newline at end of file +export const DECREMENT = 'DECREMENT'; diff --git a/example/webapp/src/app/component.js b/example/webapp/src/app/component.jsx similarity index 51% rename from example/webapp/src/app/component.js rename to example/webapp/src/app/component.jsx index a7cc263..4faff3f 100644 --- a/example/webapp/src/app/component.js +++ b/example/webapp/src/app/component.jsx @@ -3,47 +3,47 @@ import PropTypes from 'prop-types'; import './style.css'; -export default class App extends PureComponent { - constructor(props) { +export default class App extends PureComponent { + constructor(props) { super(props); this.handlerConnect = this.handlerConnect.bind(this); } - handlerConnect(){ + handlerConnect() { const { onConnect } = this.props; onConnect('Counter'); } - render() { - const { - store, status, - onIncrement, onDecrement - } = this.props; + render() { + const { store, status, onIncrement, onDecrement } = this.props; return (
- - { (status === 'CONNECTED') && -
{store}
- } -
- { (status !== 'CONNECTED') && - - } - { (status === 'CONNECTED') && - - } - { (status === 'CONNECTED') && - - } + {status === 'CONNECTED' && +
+ {store} +
} +
+ {status !== 'CONNECTED' && + } + {status === 'CONNECTED' && + } + {status === 'CONNECTED' && + }
); } -}; +} -App.propTypes = { +App.propTypes = { store: PropTypes.number, status: PropTypes.string, onConnect: PropTypes.func, @@ -51,8 +51,10 @@ App.propTypes = { onDecrement: PropTypes.func, }; -App.defaultProps = { +App.defaultProps = { + store: 0, + status: '', onConnect: () => true, onIncrement: () => true, - onDecrement: () => true, + onDecrement: () => true, }; diff --git a/example/webapp/src/app/index.js b/example/webapp/src/app/index.js index e4521af..c93cc0b 100644 --- a/example/webapp/src/app/index.js +++ b/example/webapp/src/app/index.js @@ -5,9 +5,7 @@ import { increment, decrement } from '../actions'; import Component from './component'; -const mapState = (state) => { - return state; -} +const mapState = state => state; const mapAction = { onConnect: actions.connectStore, diff --git a/example/webapp/src/index.js b/example/webapp/src/index.js index c45942d..b005c9e 100644 --- a/example/webapp/src/index.js +++ b/example/webapp/src/index.js @@ -1,3 +1,4 @@ +/* global document */ import React from 'react'; import { render } from 'react-dom'; import { Provider } from 'react-redux'; @@ -11,11 +12,12 @@ import App from './app'; const ACTIONS = Object.keys(TYPES); +/* eslint-disable react/jsx-filename-extension */ const store = createSyncStore(ACTIONS); render( - , - document.getElementById('root') + , + document.getElementById('root'), ); - +/* eslint-enable */ diff --git a/package.json b/package.json index 6057e64..524f422 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,8 @@ "main": "build/index.js", "scripts": { "dev": "watch 'npm run build' src", + "eslint": "eslint src", + "prebuild": "npm run eslint", "build": "babel src -d build", "test": "jest", "test:watch": "npm test -- --watch", @@ -39,12 +41,19 @@ "devDependencies": { "babel-cli": "^6.24.1", "babel-preset-latest": "^6.24.1", + "eslint": "^3.19.0", + "eslint-config-airbnb": "^15.0.2", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-jsx-a11y": "^5.1.1", + "eslint-plugin-react": "^7.1.0", "jest": "^20.0.4", "np": "^2.16.0", "watch": "^1.0.2" }, "dependencies": { "bleno": "^0.4.2", + "redux": "^3.7.1", + "redux-thunk": "^2.2.0", "text-encoding": "^0.6.4" } } diff --git a/src/common/config/index.js b/src/common/config/index.js index 7c07765..28a8af2 100644 --- a/src/common/config/index.js +++ b/src/common/config/index.js @@ -1,11 +1,11 @@ -const CONFIG = { +const CONFIG = { SERVICE_UUID: '13333333-3333-3333-3333-333333333337', - CHARACTERISTIC_UUID: '13333333-3333-3333-3333-333333330001' + CHARACTERISTIC_UUID: '13333333-3333-3333-3333-333333330001', }; export const CENTRAL_CONFIG = CONFIG; export const BLENO_CONFIG = { SERVICE_UUID: CONFIG.SERVICE_UUID.replace(/-/g, ''), CHARACTERISTIC_UUID: CONFIG.CHARACTERISTIC_UUID.replace(/-/g, ''), - DESCRIPTOR_UUID: '2901' -}; \ No newline at end of file + DESCRIPTOR_UUID: '2901', +}; diff --git a/src/common/encoder/index.js b/src/common/encoder/index.js index fb755d2..515d435 100644 --- a/src/common/encoder/index.js +++ b/src/common/encoder/index.js @@ -1,17 +1,17 @@ -export default function Encoder({ TextEncoder, TextDecoder}) { +export default function Encoder({ TextEncoder, TextDecoder }) { const encoder = new TextEncoder('utf-8'); const decoder = new TextDecoder('utf-8'); - const encode = (json) => { + const encode = (json) => { const string = JSON.stringify(json); return encoder.encode(string); - } + }; - const decode = (data) => { + const decode = (data) => { const string = decoder.decode(data); const json = JSON.parse(string); return typeof json === 'string' ? JSON.parse(json) : json; - } + }; return { encode, decode }; -} \ No newline at end of file +} diff --git a/src/common/encoder/index.test.js b/src/common/encoder/index.test.js index 4e1d619..623c1c0 100644 --- a/src/common/encoder/index.test.js +++ b/src/common/encoder/index.test.js @@ -1,10 +1,11 @@ +/* global test, expect */ import TextEncoding from 'text-encoding'; import Encoder from '.'; -const { encode , decode } = new Encoder(TextEncoding); +const { encode, decode } = new Encoder(TextEncoding); -test('encode / decode', () => { +test('encode / decode', () => { const data = encode({ type: 'TEST', payload: 'PAYLOAD' }); const result = decode(data); diff --git a/src/index.js b/src/index.js index f96c22a..134b132 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import * as peripheral from './peripheral'; +import peripheral from './peripheral'; import * as webapp from './webapp'; export default { peripheral, webapp }; diff --git a/src/peripheral/bleno/characteristic.js b/src/peripheral/bleno/characteristic.js index 9eab932..03544a3 100644 --- a/src/peripheral/bleno/characteristic.js +++ b/src/peripheral/bleno/characteristic.js @@ -1,15 +1,9 @@ -export default function Characteristic( - uuid, - Parent, - util, - descriptor, - { encode, decode } ) { - +export default function Characteristic(uuid, Parent, util, descriptor, { encode, decode }) { function ReduxCharacteristic() { Parent.call(this, { uuid, properties: ['read', 'write', 'notify'], - descriptors: [ descriptor ] + descriptors: [descriptor], }); this.store = null; @@ -17,51 +11,52 @@ export default function Characteristic( util.inherits(ReduxCharacteristic, Parent); - ReduxCharacteristic.prototype.connect = function(store) { + ReduxCharacteristic.prototype.connect = function (store) { this.store = store; - this.store.subscribe(() => { - if ( this.updateValueCallback && this.store ) { + this.store.subscribe(() => { + if (this.updateValueCallback && this.store) { const state = this.store.getState(); this.updateValueCallback(encode(state)); - } + } }); - } + }; - ReduxCharacteristic.prototype.disconnect = function() { + ReduxCharacteristic.prototype.disconnect = function () { this.store = null; - } + }; - ReduxCharacteristic.prototype.onWriteRequest = function(data, offset, withoutResponse, callback) { + ReduxCharacteristic.prototype.onWriteRequest = + function (data, offset, withoutResponse, callback) { if (offset) { callback(this.RESULT_ATTR_NOT_LONG); return; } - this.store && this.store.dispatch(decode(data)); + if (this.store) this.store.dispatch(decode(data)); callback(this.RESULT_SUCCESS); }; - ReduxCharacteristic.prototype.onReadRequest = function(offset, callback) { + ReduxCharacteristic.prototype.onReadRequest = function (offset, callback) { if (offset) { callback(this.RESULT_ATTR_NOT_LONG, null); return; } - if ( !this.store ) { + if (!this.store) { callback(this.RESULT_SUCCESS, null); return; } callback(this.RESULT_SUCCESS, encode(this.store.getState())); }; - ReduxCharacteristic.prototype.onSubscribe = function(maxValueSize, updateValueCallback) { + ReduxCharacteristic.prototype.onSubscribe = function (maxValueSize, updateValueCallback) { this.updateValueCallback = updateValueCallback; - } + }; - ReduxCharacteristic.prototype.onUnsubscribe = function() { + ReduxCharacteristic.prototype.onUnsubscribe = function () { this.updateValueCallback = null; - } + }; return new ReduxCharacteristic(); -} \ No newline at end of file +} diff --git a/src/peripheral/bleno/index.js b/src/peripheral/bleno/index.js index af7753f..79795bd 100644 --- a/src/peripheral/bleno/index.js +++ b/src/peripheral/bleno/index.js @@ -1,5 +1,5 @@ import util from 'util'; -import bleno from 'bleno'; +import BLENO from 'bleno'; import TextEncoding from 'text-encoding'; import { BLENO_CONFIG } from '../../common/config'; @@ -7,36 +7,26 @@ import Encoder from '../../common/encoder'; import Service from './service'; import Characteristic from './characteristic'; -import Descriptor from './Descriptor'; +import Descriptor from './descriptor'; -export function Bleno( - bleno, - encoder, - { SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID }) { +export function Bleno(bleno, encoder, { SERVICE_UUID, CHARACTERISTIC_UUID, DESCRIPTOR_UUID }) { + const descriptor = Descriptor(DESCRIPTOR_UUID, bleno.Descriptor); - const descriptor = Descriptor( - DESCRIPTOR_UUID, - bleno.Descriptor - ); - - const characteristic = Characteristic( - CHARACTERISTIC_UUID, + const characteristic = Characteristic( + CHARACTERISTIC_UUID, bleno.Characteristic, util, descriptor, - encoder); + encoder, + ); - const service = Service( - SERVICE_UUID, - bleno.PrimaryService, - util, - characteristic); + const service = Service(SERVICE_UUID, bleno.PrimaryService, util, characteristic); const start = (name, store) => { - bleno.on('stateChange', function(state) { + bleno.on('stateChange', (state) => { if (state === 'poweredOn') { - bleno.startAdvertising(name, [SERVICE_UUID], function(err) { - err && console.log('startAdvertising.err: ', err); + bleno.startAdvertising(name, [SERVICE_UUID], (err) => { + if (err) console.log('startAdvertising.err: ', err); }); } else { bleno.stopAdvertising(); @@ -44,19 +34,15 @@ export function Bleno( } }); - bleno.on('advertisingStart', function(err) { + bleno.on('advertisingStart', (err) => { if (!err) { - bleno.setServices([ service ]); + bleno.setServices([service]); characteristic.connect(store); } - }); - } + }); + }; return { start }; } -export default new Bleno( - bleno, - Encoder(TextEncoding), - BLENO_CONFIG -); \ No newline at end of file +export default new Bleno(BLENO, Encoder(TextEncoding), BLENO_CONFIG); diff --git a/src/peripheral/bleno/service.js b/src/peripheral/bleno/service.js index 1b95a67..eca1c27 100644 --- a/src/peripheral/bleno/service.js +++ b/src/peripheral/bleno/service.js @@ -1,12 +1,7 @@ -export default function Service( - uuid, - Parent, - util, - characteristic) { - +export default function Service(uuid, Parent, util, characteristic) { function ReduxService() { - Parent.call(this, { - uuid, + Parent.call(this, { + uuid, characteristics: [characteristic], }); } diff --git a/src/peripheral/index.js b/src/peripheral/index.js index 7840402..97c4eae 100644 --- a/src/peripheral/index.js +++ b/src/peripheral/index.js @@ -1,3 +1,3 @@ import bleno from './bleno'; -export const startPeripheral = (name, store) => bleno.start(name, store); +export default (name, store) => bleno.start(name, store); diff --git a/src/webapp/actions/actions.js b/src/webapp/actions/actions.js index bd5631e..9491ec5 100644 --- a/src/webapp/actions/actions.js +++ b/src/webapp/actions/actions.js @@ -1,31 +1,26 @@ -export default function Actions(central, TYPES) { - - const syncState = (state) => ({ - type: TYPES.BLUETOOTH_SYNC, - payload: state +export default function Actions(central, TYPES) { + const syncState = state => ({ + type: TYPES.BLUETOOTH_SYNC, + payload: state, }); - - const connectStore = (name) => dispatch => { + + const syncStore = () => dispatch => central.read().then(state => dispatch(syncState(state))); + + const connectStore = name => (dispatch) => { dispatch({ type: TYPES.BLUETOOTH_CONNECTING }); - return central.connect(name) - .then(() => central.handler((state) => dispatch(syncState(state)))) + return central + .connect(name) + .then(() => central.handler(state => dispatch(syncState(state)))) .then(() => dispatch({ type: TYPES.BLUETOOTH_CONNECTED })) .then(() => dispatch(syncStore())); }; - const syncStore = () => dispatch => { - return central.read() - .then(state => dispatch(syncState(state))); - }; + const sendAction = action => () => central.write(action); - const sendAction = (action) => _ => { - return central.write(action); - }; - - return { - connectStore, - syncStore, + return { + connectStore, + syncStore, syncState, sendAction, - } + }; } diff --git a/src/webapp/actions/actions.test.js b/src/webapp/actions/actions.test.js index 7352fe9..99d1274 100644 --- a/src/webapp/actions/actions.test.js +++ b/src/webapp/actions/actions.test.js @@ -1,3 +1,4 @@ +/* global jest, beforeEach, afterEach, test, expect */ import * as TYPES from './types'; import Actions from './actions'; @@ -11,7 +12,7 @@ beforeEach(() => { handler: jest.fn(), read: jest.fn().mockReturnValue(Promise.resolve('mockState')), write: jest.fn().mockReturnValue(Promise.resolve()), - }; + }; }); afterEach(() => { @@ -19,25 +20,25 @@ afterEach(() => { central = null; }); -test('syncState', () => { - const { syncState } = Actions(central, TYPES); +test('syncState', () => { + const { syncState } = Actions(central, TYPES); const action = syncState('mockState'); - expect(action).toEqual({ - type: TYPES.BLUETOOTH_SYNC, - payload: 'mockState' + expect(action).toEqual({ + type: TYPES.BLUETOOTH_SYNC, + payload: 'mockState', }); }); -test('connectStore', () => { - const { connectStore } = Actions(central, TYPES); +test('connectStore', () => { + const { connectStore } = Actions(central, TYPES); expect.assertions(6); - const promise = connectStore('mockName')(dispatch).then(_ => { + const promise = connectStore('mockName')(dispatch).then(() => { expect(central.connect).toBeCalled(); expect(central.handler).toBeCalled(); - expect(dispatch.mock.calls.length).toBe(2); + expect(dispatch.mock.calls.length).toBe(2); expect(dispatch.mock.calls[0][0]).toEqual({ type: TYPES.BLUETOOTH_CONNECTING }); expect(dispatch.mock.calls[1][0]).toEqual({ type: TYPES.BLUETOOTH_CONNECTED }); @@ -47,11 +48,11 @@ test('connectStore', () => { return expect(promise).resolves.toBe(true); }); -test('syncStore', () => { +test('syncStore', () => { const { syncStore } = Actions(central, TYPES); expect.assertions(3); - const promise = syncStore()(dispatch).then(_ => { + const promise = syncStore()(dispatch).then(() => { expect(central.read).toBeCalled(); expect(dispatch).toBeCalledWith({ type: TYPES.BLUETOOTH_SYNC, payload: 'mockState' }); @@ -61,11 +62,11 @@ test('syncStore', () => { return expect(promise).resolves.toBe(true); }); -test('sendAction', () => { +test('sendAction', () => { const { sendAction } = Actions(central, TYPES); expect.assertions(2); - const promise = sendAction('mockAction')(dispatch).then(_ => { + const promise = sendAction('mockAction')(dispatch).then(() => { expect(central.write).toBeCalledWith('mockAction'); return true; diff --git a/src/webapp/central/index.js b/src/webapp/central/index.js index 5987547..401800a 100644 --- a/src/webapp/central/index.js +++ b/src/webapp/central/index.js @@ -1,69 +1,59 @@ import { CENTRAL_CONFIG } from '../../common/config'; import Encoder from '../../common/encoder'; -export function Central( - bluetooth, - { encode, decode }, - { SERVICE_UUID, CHARACTERISTIC_UUID }) { - +export function Central(bluetooth, { encode, decode }, { SERVICE_UUID, CHARACTERISTIC_UUID }) { const state = { server: null, characteristic: null, - } - - const connect = (name) => { - return bluetooth.requestDevice({ - filters: [{ services: [ SERVICE_UUID ], name: name }] - }) - .then((device) => device.gatt.connect()) - .then((server) => { - state.server = server; - return server.getPrimaryService(SERVICE_UUID) - }) - .then((service) => service.getCharacteristic(CHARACTERISTIC_UUID)) - .then((characteristic) => { - state.characteristic = characteristic; - }); }; - const handler = ( callback ) => { - return state.characteristic.startNotifications() - .then(() => { - state.characteristic.addEventListener('characteristicvaluechanged', (event) => { - callback(decode(event.target.value)); + const connect = name => bluetooth + .requestDevice({ + filters: [{ services: [SERVICE_UUID], name }], + }) + .then(device => device.gatt.connect()) + .then((server) => { + state.server = server; + return server.getPrimaryService(SERVICE_UUID); + }) + .then(service => service.getCharacteristic(CHARACTERISTIC_UUID)) + .then((characteristic) => { + state.characteristic = characteristic; }); - }); - } - const read = () => { - if ( state.server && state.server.connected && state.characteristic ) { - return state.characteristic.readValue().then(data => { - return decode(data); - }); + const handler = callback => state.characteristic.startNotifications().then(() => { + state.characteristic.addEventListener('characteristicvaluechanged', (event) => { + callback(decode(event.target.value)); + }); + }); + + const read = () => { + if (state.server && state.server.connected && state.characteristic) { + return state.characteristic.readValue().then(data => decode(data)); } return Promise.reject(new Error('Bluetooth: Not Connected')); - } + }; - const write = (action) => { - if ( state.server && state.server.connected && state.characteristic ) { - const stringify = JSON.stringify(action); - const serialized = encode(stringify); + const write = (action) => { + if (!state.server || !state.server.connected || !state.characteristic) return null; + const stringify = JSON.stringify(action); + const serialized = encode(stringify); - return state.characteristic.writeValue(serialized); - } - } + return state.characteristic.writeValue(serialized); + }; - return { + return { connected: state.server && state.server.connected, - connect, + connect, handler, read, - write - } + write, + }; } +/* global navigator TextEncoder TextDecoder */ export default new Central( navigator.bluetooth, Encoder({ TextEncoder, TextDecoder }), - CENTRAL_CONFIG + CENTRAL_CONFIG, ); diff --git a/src/webapp/index.js b/src/webapp/index.js index 6bd71bf..2f39dea 100644 --- a/src/webapp/index.js +++ b/src/webapp/index.js @@ -12,4 +12,4 @@ export const status = STATUS; export const actions = { connectStore, syncStore }; export const reducers = REDUCERS; export const middleware = MIDDLEWARE; -export const createSyncStore = STORE; \ No newline at end of file +export const createSyncStore = STORE; diff --git a/src/webapp/middleware/index.js b/src/webapp/middleware/index.js index 5b72dcb..2e061c8 100644 --- a/src/webapp/middleware/index.js +++ b/src/webapp/middleware/index.js @@ -1,8 +1,9 @@ -import actions from '../actions'; -const { sendAction } = actions; +import ACTIONS from '../actions'; -export default (actions = []) => store => next => action => { +const { sendAction } = ACTIONS; + +export default (actions = []) => store => next => (action) => { const { type } = action; - actions.includes(type) && store.dispatch(sendAction(action)); + if (actions.includes(type)) store.dispatch(sendAction(action)); return next(action); -} +}; diff --git a/src/webapp/reducers/index.js b/src/webapp/reducers/index.js index ca0af8c..4972eb7 100644 --- a/src/webapp/reducers/index.js +++ b/src/webapp/reducers/index.js @@ -1,19 +1,19 @@ import * as TYPES from '../actions/types'; import * as STATUS from '../central/status'; -const initial = { - status: STATUS.INIT +const initial = { + status: STATUS.INIT, }; export default (autosync = true) => (state = initial, { type, payload }) => { - switch (type) { - case TYPES.BLUETOOTH_CONNECTING: + switch (type) { + case TYPES.BLUETOOTH_CONNECTING: return Object.assign({}, state, { status: STATUS.CONNECTING }); case TYPES.BLUETOOTH_CONNECTED: return Object.assign({}, state, { status: STATUS.CONNECTED }); case TYPES.BLUETOOTH_SYNC: return autosync ? Object.assign({}, state, { store: payload }) : state; - default: - return state; + default: + return state; } }; diff --git a/src/webapp/store/index.js b/src/webapp/store/index.js index bd7e605..1ad1af8 100644 --- a/src/webapp/store/index.js +++ b/src/webapp/store/index.js @@ -1,24 +1,19 @@ +/* global window */ import { createStore, applyMiddleware, compose } from 'redux'; import thunk from 'redux-thunk'; import middleware from '../middleware'; import reducers from '../reducers'; -export default (actions) => { - const middlewares = [ - middleware(actions), - thunk - ]; - const enhancers = [ - applyMiddleware(...middlewares), - ]; +export default (actions) => { + const middlewares = [middleware(actions), thunk]; + const enhancers = [applyMiddleware(...middlewares)]; + /* eslint-disable no-underscore-dangle */ if (typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__) { enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); } + /* eslint-enable */ - return createStore( - reducers(), - compose(...enhancers), - ); -} + return createStore(reducers(), compose(...enhancers)); +};