├── .babelrc ├── .circleci └── config.yml ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .npmignore ├── README.md ├── __tests__ └── index.test.js ├── example ├── App.js ├── index.js └── ios │ ├── Podfile │ ├── Podfile.lock │ ├── SocketMobileExample.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── SocketMobileExample-tvOS.xcscheme │ │ └── SocketMobileExample.xcscheme │ ├── SocketMobileExample.xcworkspace │ └── contents.xcworkspacedata │ ├── SocketMobileExample │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ └── LaunchScreen.xib │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ └── main.m │ └── SocketMobileExampleTests │ ├── Info.plist │ └── SocketMobileExampleTests.m ├── flow-typed └── npm │ └── jest_v22.x.x.js ├── index.js ├── ios ├── ReactNativeSocketMobile.h ├── ReactNativeSocketMobile.m └── ReactNativeSocketMobile.podspec ├── package.json └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } 4 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:7.10 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: yarn install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: yarn lint 38 | - run: yarn flow 39 | - run: yarn test:coverage 40 | 41 | 42 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | flow-typed/ 4 | ios/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "callstack-io", 3 | "rules": { 4 | "no-nested-ternary": 0 5 | } 6 | } -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | 16 | ; Ignore polyfills 17 | .*/Libraries/polyfills/.* 18 | 19 | ; Ignore metro 20 | .*/node_modules/metro/.* 21 | 22 | [include] 23 | 24 | [libs] 25 | node_modules/react-native/Libraries/react-native/react-native-interface.js 26 | node_modules/react-native/flow/ 27 | node_modules/react-native/flow-github/ 28 | 29 | [options] 30 | emoji=true 31 | 32 | module.system=haste 33 | 34 | munge_underscores=true 35 | 36 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 37 | 38 | module.file_ext=.js 39 | module.file_ext=.jsx 40 | module.file_ext=.json 41 | module.file_ext=.native.js 42 | 43 | suppress_type=$FlowIssue 44 | suppress_type=$FlowFixMe 45 | suppress_type=$FlowFixMeProps 46 | suppress_type=$FlowFixMeState 47 | 48 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 49 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 50 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 51 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 52 | 53 | [version] 54 | ^0.65.0 55 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Pods 6 | # 7 | *Pods 8 | 9 | # Xcode 10 | # 11 | build/ 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata 21 | *.xccheckout 22 | *.moved-aside 23 | DerivedData 24 | *.hmap 25 | *.ipa 26 | *.xcuserstate 27 | project.xcworkspace 28 | 29 | # Android/IntelliJ 30 | # 31 | build/ 32 | .idea 33 | .gradle 34 | local.properties 35 | *.iml 36 | 37 | # node.js 38 | # 39 | node_modules/ 40 | npm-debug.log 41 | yarn-error.log 42 | 43 | # BUCK 44 | buck-out/ 45 | \.buckd/ 46 | *.keystore 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://docs.fastlane.tools/best-practices/source-control/ 54 | 55 | */fastlane/report.xml 56 | */fastlane/Preview.html 57 | */fastlane/screenshots 58 | 59 | # Jest 60 | coverage/ -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | .babelrc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-socket-mobile 2 | 3 | [![CircleCI](https://circleci.com/gh/callstack/react-native-socket-mobile.svg?style=shield)](https://circleci.com/gh/callstack/react-native-socket-mobile) [![Coverage Status](https://coveralls.io/repos/github/Traede/react-native-socket-mobile/badge.svg?branch=master)](https://coveralls.io/github/Traede/react-native-socket-mobile?branch=master) 4 | [![npm version](https://badge.fury.io/js/react-native-socket-mobile.svg)](https://badge.fury.io/js/react-native-socket-mobile) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 5 | 6 | This native module is a wrapper over the [Socket Mobile SDK](https://www.socketmobile.com/developer/portal/welcome). If you have a developer account and valid credentials, you will be able to use your scanner within your React Native application. 7 | 8 | ## Table of contents 9 | - [Installation](#installation) 10 | - [API Reference](#api-reference) 11 | 12 | ## Installation 13 | 14 | Add the library using `yarn` or `npm`: 15 | 16 | ```bash 17 | yarn add react-native-socket-mobile 18 | ``` 19 | 20 | ### iOS 21 | 22 | This library depends on the [SKTCaptureObjC SDK](https://github.com/SocketMobile/cocoapods-capture-obj-c). We will be using Cocoapods. 23 | 24 | 1. Install [CocoaPods](https://cocoapods.org/) on your marchine. 25 | 2. Within you application, go to the `ios/` directory and run `pod init` 26 | 3. Replace the content within your brand-new `Podfile` with: 27 | 28 | ```ruby 29 | source 'https://github.com/CocoaPods/Specs.git' 30 | 31 | platform :ios, '9.0' 32 | 33 | target 'YourAppName' do 34 | node_modules_path = '../node_modules' 35 | 36 | pod 'yoga', path: "#{node_modules_path}/react-native/ReactCommon/yoga/yoga.podspec" 37 | pod 'React', path: "#{node_modules_path}/react-native" 38 | 39 | pod 'ReactNativeSocketMobile', path: "#{node_modules_path}/react-native-socket-mobile/ios" 40 | end 41 | 42 | post_install do |installer| 43 | installer.pods_project.targets.each do |target| 44 | if target.name == "React" 45 | target.remove_from_project 46 | end 47 | end 48 | end 49 | ``` 50 | 4. Run `pod install`. 51 | 5. Open .xcworkspace file (you'll need to use it as a starting file from now on). 52 | 6. **Important**: You need to add the following to your XCode project: 53 | 54 | | Info | | 55 | | ------------- |:-------------:| 56 | | Supported external accessory protocols | com.socketmobile.chs | 57 | 58 | ### Android 59 | 60 | No support for now. 61 | 62 | ## API Reference 63 | 64 | #### `start({ bundleId: string, developerId: string, appKey: string }): Promise` 65 | Start listening for a scanner to be connected. 66 | ```js 67 | start = async () => { 68 | try { 69 | await SocketMobile.start({ 70 | bundleId, 71 | developerId, 72 | appKey, 73 | }); 74 | } catch (e) { 75 | // Handle the error 76 | } 77 | }; 78 | ``` 79 | 80 | #### `stop() : Promise` 81 | Stop connection to the scanner. 82 | ```js 83 | stop = async () => { 84 | try { 85 | await SocketMobile.stop(); 86 | } catch (e) { 87 | // Handle the error 88 | } 89 | }; 90 | ``` 91 | 92 | #### `updateStatusFromDevices() : Promise` 93 | Get current list of connected devices. 94 | ```js 95 | updateStatusFromDevices = async () => { 96 | const status = await SocketMobile.updateStatusFromDevices(); 97 | }; 98 | ``` 99 | 100 | #### `setDataListener(callback: (result: { data: string }): void` 101 | Listens for scanning results (after we got connected to the device). 102 | ```js 103 | SocketMobile.setDataListener(({ data }) => { 104 | this.setState({ lastScan: data }); 105 | }); 106 | ``` 107 | 108 | #### `setDeviceStatusListener(callback: (status: 'connected'|'disconnected'): void` 109 | Listen for scanner connection changes. 110 | ```js 111 | SocketMobile.setDeviceStatusListener(status => { 112 | if (status === 'connected') { 113 | this.setState({ status: 'connected' }); 114 | } else { 115 | this.setState({ status: 'disconnected' }); 116 | } 117 | }); 118 | ``` 119 | 120 | #### `clearAllListeners() : void` 121 | Remove all listeners, typically called in `componentWillUmmount`. 122 | ```js 123 | componentWillUnmount() { 124 | SocketMobile.clearAllListeners(); 125 | } 126 | ``` 127 | -------------------------------------------------------------------------------- /__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { NativeModules } from 'react-native'; 4 | import SocketMobile, { emitter } from '../index'; 5 | 6 | const { ReactNativeSocketMobile } = NativeModules; 7 | 8 | jest.mock('react-native', () => { 9 | const realReactNative = jest.requireActual('react-native'); 10 | return { 11 | NativeEventEmitter: () => { 12 | const _listeners = []; 13 | return { 14 | addListener: jest.fn((name, callback) => 15 | _listeners.push({ name, callback }) 16 | ), 17 | removeAllListeners: jest.fn(), 18 | testTrigger: (name, param) => { 19 | _listeners.forEach(listener => { 20 | if (listener.name === name) { 21 | listener.callback(param); 22 | } 23 | }); 24 | }, 25 | }; 26 | }, 27 | NativeModules: { 28 | ...realReactNative.NativeModules, 29 | ReactNativeSocketMobile: { 30 | start: jest.fn(() => Promise.resolve(true)), 31 | stop: jest.fn(() => Promise.resolve(true)), 32 | updateStatusFromDevices: jest.fn(() => 33 | Promise.resolve('Waiting for a scanner...') 34 | ), 35 | }, 36 | }, 37 | }; 38 | }); 39 | 40 | describe('Socket Mobile module', () => { 41 | beforeEach(() => { 42 | jest.clearAllMocks(); 43 | }); 44 | 45 | test('calls starts and pass the right options', async () => { 46 | const bundleId = 'package.com'; 47 | const developerId = '1234'; 48 | const appKey = '5678'; 49 | 50 | expect(ReactNativeSocketMobile.start).not.toBeCalled(); 51 | 52 | const response = await SocketMobile.start({ 53 | bundleId, 54 | developerId, 55 | appKey, 56 | }); 57 | 58 | expect(response).toBe(true); 59 | expect(ReactNativeSocketMobile.start).toHaveBeenCalledTimes(1); 60 | expect(ReactNativeSocketMobile.start).toBeCalledWith( 61 | bundleId, 62 | developerId, 63 | appKey 64 | ); 65 | }); 66 | 67 | test('calls stop', async () => { 68 | expect(ReactNativeSocketMobile.stop).not.toBeCalled(); 69 | 70 | const response = await SocketMobile.stop(); 71 | 72 | expect(response).toBe(true); 73 | expect(ReactNativeSocketMobile.stop).toHaveBeenCalledTimes(1); 74 | }); 75 | 76 | test('calls updateStatusFromDevices', async () => { 77 | expect(ReactNativeSocketMobile.updateStatusFromDevices).not.toBeCalled(); 78 | 79 | const data = await SocketMobile.updateStatusFromDevices(); 80 | 81 | expect(data).toEqual('Waiting for a scanner...'); 82 | expect( 83 | ReactNativeSocketMobile.updateStatusFromDevices 84 | ).toHaveBeenCalledTimes(1); 85 | }); 86 | 87 | test('sets data listener', async () => { 88 | expect(emitter.addListener).not.toBeCalled(); 89 | 90 | const callback = jest.fn(); 91 | SocketMobile.setDataListener(callback); 92 | 93 | expect(emitter.addListener).toHaveBeenCalledTimes(1); 94 | expect(emitter.addListener).toBeCalledWith('DecodedData', callback); 95 | }); 96 | 97 | test('sets device status listener', async () => { 98 | expect(emitter.addListener).not.toBeCalled(); 99 | 100 | const callback = jest.fn(); 101 | SocketMobile.setDeviceStatusListener(callback); 102 | 103 | expect(emitter.removeAllListeners).toHaveBeenCalledTimes(0); 104 | 105 | emitter.testTrigger('StatusDeviceChanged', { status: 'disconnected' }); 106 | 107 | expect(callback).toHaveBeenCalledTimes(1); 108 | expect(callback).toBeCalledWith('disconnected'); 109 | 110 | emitter.testTrigger('StatusDeviceChanged', { status: 'connected' }); 111 | 112 | expect(callback).toHaveBeenCalledTimes(2); 113 | expect(callback).toBeCalledWith('connected'); 114 | }); 115 | 116 | it('removes all listeners', () => { 117 | expect(emitter.removeAllListeners).toHaveBeenCalledTimes(0); 118 | 119 | SocketMobile.clearAllListeners(); 120 | 121 | expect(emitter.removeAllListeners).toHaveBeenCalledTimes(2); 122 | expect(emitter.removeAllListeners).toBeCalledWith('DecodedData'); 123 | expect(emitter.removeAllListeners).toBeCalledWith('StatusDeviceChanged'); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /example/App.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import React, { Component } from 'react'; 4 | import { Alert, Button, StyleSheet, Text, View } from 'react-native'; 5 | // eslint-disable-next-line import/no-unresolved 6 | import SocketMobile, { STATUS_WAITING } from 'react-native-socket-mobile'; 7 | 8 | const bundleId = 'ADD_YOUR_BUNDLE_ID'; 9 | const appKey = 'ADD_YOUR_APP_KEY'; 10 | const developerId = 'ADD_YOUR_DEVELOPER_ID'; 11 | 12 | type Props = {}; 13 | 14 | type State = { 15 | isBusy: boolean, 16 | lastScan: string, 17 | status: string, 18 | }; 19 | 20 | export default class App extends Component { 21 | state = { 22 | isBusy: false, 23 | lastScan: '--', 24 | status: 'disconnected', 25 | }; 26 | 27 | componentDidMount() { 28 | this.initStatus(); 29 | } 30 | 31 | componentWillUnmount() { 32 | SocketMobile.clearAllListeners(); 33 | } 34 | 35 | initStatus = async () => { 36 | const status = await SocketMobile.updateStatusFromDevices(); 37 | if (status !== STATUS_WAITING) { 38 | this.setState({ status: 'connected ' }); 39 | this.setListeners(); 40 | } 41 | }; 42 | 43 | startListening = async () => { 44 | this.setListeners(); 45 | 46 | this.setState({ isBusy: true }); 47 | try { 48 | await SocketMobile.start({ 49 | bundleId, 50 | developerId, 51 | appKey, 52 | }); 53 | } catch (e) { 54 | // Handle error here 55 | } 56 | }; 57 | 58 | stopListening = async () => { 59 | this.setState({ isBusy: true }); 60 | 61 | try { 62 | await SocketMobile.stop(); 63 | } catch (e) { 64 | // Handle error here 65 | } finally { 66 | SocketMobile.clearAllListeners(); 67 | this.setState({ isBusy: false, lastScan: '--' }); 68 | } 69 | }; 70 | 71 | setListeners = () => { 72 | SocketMobile.setDeviceStatusListener(status => { 73 | if (status === 'connected') { 74 | this.setState({ status: 'connected', isBusy: false }); 75 | } else { 76 | this.setState({ status: 'disconnected' }); 77 | } 78 | }); 79 | SocketMobile.setDataListener(({ data }) => { 80 | this.setState({ lastScan: data }); 81 | }); 82 | }; 83 | 84 | checkStatus = async () => { 85 | const status = await SocketMobile.updateStatusFromDevices(); 86 | Alert.alert('Status', status); 87 | }; 88 | 89 | render() { 90 | return ( 91 | 92 |