├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .vscode └── launch.json ├── README.md ├── TODO.md ├── api-urls.js ├── examples ├── example-script.js └── formatters.js ├── get-active-dings.js ├── get-devices-list.js ├── get-history-list.js ├── get-live-stream.js ├── main.js ├── package-lock.json ├── package.json ├── parse-ring-json-responses.js ├── poll-for-dings.js ├── propagated-error.js ├── rest-client.js ├── ring-api-workspace.code-workspace ├── top-level-api.js └── wire-up.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | const defaultsDeep = require( 'lodash.defaultsdeep' ); 4 | 5 | module.exports = defaultsDeep( { 6 | parser: 'babel-eslint', 7 | env: { 8 | "node": true 9 | } 10 | }, require( 'jimhigson-my-eslint-rules' ) ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | .vscode/ 4 | *.code-workspace 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea 3 | .vscode/ 4 | *.code-workspace 5 | ringy.js -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Run example script", 11 | "program": "${workspaceFolder}/examples/example-script.js", 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Ring API 3 | === 4 | 5 | An unofficial, friendly Javascript API for [ring](http://ring.com) doorbells, cameras, etc. 6 | 7 | * Happy in node or browsers 8 | * Promised-based 9 | * Glosses over ring's API weirdness 10 | * Hides http polling behind an event-driven interface 11 | 12 | Requires a JS runtime that supports ES6 async/await or else traspilation 13 | 14 | usage 15 | --- 16 | 17 | ```js 18 | const RingApi = require( 'ring-api' ); 19 | 20 | // note that RingApi returns a promise - the promise resolves when you are authenticated/ 21 | // authorised and have a session ready to start interacting with your ring deviecs. This 22 | // promise will reject if for some reason you are not able to log in 23 | const ringApi = await RingApi( { 24 | 25 | // note - that the email and password can also be given by setting the RING_USER 26 | // and RING_PASSWORD environment variables. For example if you want to keep 27 | // passwords out of your source code 28 | email: 'you@example.com', 29 | password: 'password you use on ring.com', 30 | 31 | // the user agent parameter has been removed since Ring have started rejecting 32 | // unrecognised agent strings 33 | //userAgent: 'any string', 34 | 35 | // OPTIONAL: if true, ring-api will poll behind the scenes. 36 | // Listening for events only works if this is on. 37 | // True by default. 38 | poll: true, 39 | 40 | // OPTIONAL 41 | // Set this if you need to run in a browser behind a proxy, for example 42 | // to get around x-origin request restrictions. Ring don't have CORS headers. 43 | // once set, all requests will be made relative to this value 44 | // default is 'https://api.ring.com/clients_api' 45 | // If running in node, you almost certainly want to leave this out 46 | serverRoot: 'http://example.com' 47 | } ); 48 | ``` 49 | 50 | Listening for activity on your ring devices 51 | --- 52 | 53 | ```js 54 | const logActivity = activity => console.log( 'there is a activity', activity ); 55 | 56 | ringApi.events.on('activity', logActivity); 57 | ``` 58 | 59 | The event will be fired on rings and motion detected. To distinguish between then, use the activity.kind 60 | property. 61 | 62 | Where the activity object looks like: 63 | 64 | ```js 65 | { 66 | kind: 'motion', // 'motion' or 'ring', 67 | // note - id will be a string - Javascript Number can't do large integers 68 | id: '6500907085284961754', 69 | id_str: '6500907085284961754', // same as id 70 | state: 'ringing', 71 | protocol: 'sip', 72 | doorbot_id: 3861978, // id of the device that is ringing 73 | doorbot_description: 'Back garden', 74 | device_kind: 'hp_cam_v1', 75 | motion: true, 76 | snapshot_url: '', // seems to always be blank 77 | expires_in: 175, 78 | now: Date, // js Date object 79 | optimization_level: 1, 80 | 81 | // various sip-related fields for the video: 82 | sip_server_ip: '...', 83 | sip_server_port: 15063, 84 | sip_server_tls: true, 85 | sip_session_id: '...', 86 | sip_from: '...', 87 | sip_to: '..', 88 | audio_jitter_buffer_ms: 300, 89 | video_jitter_buffer_ms: 300, 90 | sip_endpoints: null, 91 | sip_token: 'long hex token', 92 | sip_ding_id: '6500907085284961754', // seems to always be the same as the id 93 | } 94 | ``` 95 | 96 | Getting a list of devices 97 | ------------ 98 | 99 | ```js 100 | // returns a promise 101 | ringApi.devices(); 102 | ``` 103 | 104 | Where the promise will resolve to an object like: 105 | 106 | ```js 107 | { 108 | all: [ /* all your devices in one array */ ], 109 | doorbells: [ /* array of doorbells */ ] 110 | authorizedDoorbells: [], // other people's doorbells you are authorised for 111 | chimes: [ /* array of chimes */ ], 112 | cameras: [ /* array of cameras, floodlight cams, spotlight cams etc */ ] ], 113 | baseStations: [] // presumably if you have a chime pro with the wifi hotspot built in 114 | } 115 | ``` 116 | 117 | Turning lights on and off 118 | ---------------- 119 | 120 | ```js 121 | const prompt = require('node-ask').prompt; 122 | 123 | async function lightsOnAndOff() { 124 | 125 | const devices = await ringApi.devices(); 126 | 127 | console.log( `turning on all lights` ); 128 | 129 | // note that .lightOn() returns a promise 130 | // with the magic of promises we can turn them all on asynchronously 131 | await Promise.all( devices.cameras.map( c => c.lightOn() ) ); 132 | 133 | await prompt( 'all your lights are now on, hit return to turn them off' ); 134 | 135 | await Promise.all( devices.cameras.map( c => c.lightOff() ) ); 136 | 137 | console.log( 'they\'re all off again!'); 138 | }; 139 | 140 | lightsOnAndOff(); 141 | ``` 142 | 143 | Getting device history and videos 144 | ----------- 145 | 146 | ```js 147 | async function logMyRingHistory() { 148 | 149 | const history = await ringApi.history(); 150 | const firstVideoUrl = await history[0].videoUrl(); 151 | 152 | console.log( 'latest video is at', firstVideoUrl ); 153 | 154 | const allRecentVideos = Promise.all( history.map( h => h.videoUrl() ) ); 155 | 156 | console.log( 'list of all recent videos:', await allRecentVideos ); 157 | }; 158 | ``` 159 | 160 | starting a livestream 161 | -------------------- 162 | 163 | So far this only works so far as getting the SIP details of the stream. To get this, can do: 164 | ```js 165 | // returns a promise that will resolve to the livestream SIP information: 166 | devices.doorbells[0].liveStream(); 167 | ``` 168 | 169 | 170 | getting device health 171 | --------------------- 172 | 173 | ```js 174 | async function printHealth( device ) { 175 | const strength = (await device.health()).latest_signal_strength; 176 | console.log( `${device.description} wifi strength is ${strength}` ); 177 | } 178 | 179 | // asynchronously print the health of the first of each kind of device, 180 | // without worrying about the order they are printed in: 181 | const devices = await ringApi.devices(); 182 | printHealth( devices.doorbells[0] ); 183 | printHealth( devices.chimes[0] ); 184 | printHealth( devices.cameras[0] ); 185 | ``` 186 | 187 | debugging 188 | --------- 189 | 190 | To get extended debugging info, since ring-api uses [https://www.npmjs.com/package/debug](debug) you 191 | can set the `DEBUG` environment variable to `ring-api` (or add ring-api to it if it is already set) 192 | 193 | For example, to run the [example script](examples/example-script) from bash with debugging you might do: 194 | 195 | ```bash 196 | env DEBUG="$DEBUG ring-api" ./examples/example-script 197 | ``` 198 | 199 | Thanks to 200 | ----- 201 | 202 | * The [doorbot](https://github.com/davglass/doorbot) source for getting me started 203 | * [This python API](https://github.com/tchellomello/python-ring-doorbell) 204 | * [mitm proxy](https://mitmproxy.org) 205 | 206 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | * configurable root of server for browsers 2 | * add linting 3 | x move to Axios 4 | * finer-grained events model 5 | * listen for events from one decice 6 | * more work on streaming 7 | * start SIP 8 | * build a web interface on top of API -------------------------------------------------------------------------------- /api-urls.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const assign = require( 'lodash.assign' ) 4 | 5 | /* 6 | 7 | A path generator made out of strings with methods: 8 | 9 | let u = require('./api-paths')( {serverRoot: 'https://api.ring.com/clients_api'} ); 10 | 11 | > u 12 | -> String 'https://api.ring.com/clients_api' 13 | 14 | > u.doorbots().device( {id: 'foo123'} ) 15 | -> String 'https://api.ring.com/clients_api/doorbots/foo123'] 16 | 17 | > u.doorbots().device( {id: 'foo123'} ).lightsOn() 18 | -> String 'https://api.ring.com/clients_api/doorbots/foo123/floodlight_light_on' 19 | 20 | > u.chimes().device( {id: 'chime2'} ).health() 21 | -> String 'https://api.ring.com/clients_api/chimes/chime2/health 22 | 23 | */ 24 | module.exports = bottle => bottle.service( 'apiUrls', apiUrls, 'options' ) 25 | function apiUrls( options ) { 26 | 27 | return assign( '' + options.serverRoot, { 28 | 29 | auth() { 30 | return 'https://oauth.ring.com/oauth/token' 31 | }, 32 | 33 | session() { 34 | return `${this}/clients_api/session` 35 | }, 36 | 37 | devices() { 38 | return `${this}/clients_api/ring_devices` 39 | }, 40 | 41 | doorbots() { 42 | return assign( `${this}/clients_api/doorbots`, { 43 | 44 | device( device ) { 45 | return assign( `${this}/${device.id}`, { 46 | 47 | lightOn() { 48 | return `${this}/floodlight_light_on` 49 | }, 50 | lightOff() { 51 | return `${this}/floodlight_light_off` 52 | }, 53 | liveStream() { 54 | return `${this}/vod` 55 | }, 56 | health() { 57 | return `${this}/health` 58 | } 59 | }) 60 | }, 61 | 62 | history() { 63 | return `${this}/history` 64 | } 65 | }) 66 | }, 67 | 68 | dings() { 69 | return assign( `${this}/clients_api/dings`, { 70 | 71 | ding( ding ) { 72 | return assign( `${this}/${ding.id}`, { 73 | 74 | recording() { 75 | return `${this}/recording?disable_redirect=true` 76 | } 77 | }) 78 | }, 79 | 80 | active({ burst = false } = { burst: false }) { 81 | return `${this}/active?burst=${burst}` 82 | } 83 | }) 84 | }, 85 | 86 | chimes() { 87 | return assign( `${this}/clients_api/chimes`, { 88 | 89 | device( device ) { 90 | return assign( `${this}/${device.id}`, { 91 | health() { 92 | return `${this}/health` 93 | } 94 | }) 95 | }, 96 | 97 | }) 98 | } 99 | 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /examples/example-script.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | 4 | // if outside of this codebase, use require('ring-api' instaed) 5 | const ringApi = require( '../main' ) 6 | const { healthSummary, historySummary } = require( './formatters' ) 7 | const { inspect } = require( 'util' ) 8 | const colors = require( 'colors/safe' ) 9 | 10 | const prompt = require( 'node-ask' ).prompt 11 | 12 | // edit here, or use the RING_USER and RING_PASSWORD environment variables: 13 | const username = undefined 14 | const password = undefined 15 | 16 | const main = async() => { 17 | 18 | let ring 19 | try { 20 | ring = await ringApi({ 21 | // we'll use the default options for this example. Maks sure you have the 22 | // username and password as RING_USER or RING_PASSWORD or place them above 23 | username, password 24 | }) 25 | } catch ( e ) { 26 | console.error( e ) 27 | console.error( colors.red( 'We couldn\'t create the API instance. This might be because ring.com changed their API again' )) 28 | console.error( colors.red( 'or maybe your password is wrong, in any case, sorry can\'t help you today. Bye!' )) 29 | return 30 | } 31 | 32 | try { 33 | console.log( '🎵active dings now are', await ring.activeDings()) 34 | 35 | const devices = await ring.devices() 36 | 37 | if ( devices.cameras.length ) { 38 | await Promise.all( devices.cameras.map( async c => { 39 | 40 | await c.lightOn() 41 | console.log( `${c.toString()} is now on'` ) 42 | })) 43 | 44 | await prompt( 'your lights should now all be on. Hit return ⏎ to turn them off again' ) 45 | 46 | await Promise.all( devices.cameras.map( async c => { 47 | await c.lightOff() 48 | console.log( `${c.toString()} 💡 is now off'` ) 49 | })) 50 | } else { 51 | console.log( 'you have no devices with lights 💡 that I can turn on' ) 52 | } 53 | 54 | console.log() 55 | console.log( '📹details for latest live stream:\n', inspect( await devices.doorbells[ 0 ].liveStream, { colors: true })) 56 | 57 | const healthSummaries = await Promise.all( devices.all.map( healthSummary )) 58 | console.log( '\nDevice Healths\n===============\n', healthSummaries.join( '\n' )) 59 | 60 | const history = await ring.history() 61 | console.log( historySummary( history )) 62 | 63 | const videos = await Promise.all( history.map( h => h.videoUrl())) 64 | console.log( `your most recent 3 videos 📹 are at...\n\t ${videos.slice( 0, 3 ).join( '\n\t' )}` ) 65 | 66 | ring.events.on( 'activity', ding => console.log( '\t🎵there is a ding', ding )) 67 | console.log() 68 | console.log( 'now listening for dings, they will log here until you kill this script. Go press your doorbell!' ) 69 | } catch ( e ) { 70 | console.error( e ) 71 | } 72 | } 73 | 74 | main() 75 | -------------------------------------------------------------------------------- /examples/formatters.js: -------------------------------------------------------------------------------- 1 | const { inspect } = require( 'util' ) 2 | 3 | const healthSummary = async device => ` 4 | device ${device.toString()} health 5 | ---------------------------------- 6 | 7 | ${inspect( await device.health(), { depth: 1, colors: true })} 8 | ` 9 | 10 | const historySummary = history => ` 11 | history list 12 | ------------ 13 | ${history.map( h => h.toString()).join( '\n' ) } 14 | ` 15 | 16 | module.exports = { 17 | healthSummary, historySummary 18 | } 19 | -------------------------------------------------------------------------------- /get-active-dings.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = bottle => bottle.service( 'getActiveDings', getActiveDings, 4 | 'restClient', 5 | 'apiUrls' 6 | ) 7 | 8 | function getActiveDings( restClient, apiUrls ) { 9 | return async({ burst = false } = { burst: false }) => { 10 | 11 | const dings = await restClient.authenticatedRequest( 12 | 'GET', 13 | apiUrls.dings().active({ burst }) 14 | ) 15 | 16 | const parseDing = ding => { 17 | ding.now = new Date( ding.now * 1000 ) 18 | } 19 | 20 | dings.forEach( parseDing ) 21 | 22 | return dings 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /get-devices-list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // ring's client api has strange names for things, replace them with more intuitive names 4 | // for device types 5 | // ojbect here maps client api names to our exposed name 6 | const keyReplacements = { 7 | doorbots: 'doorbells', 8 | authorized_doorbots: 'authorisedDoorbells', 9 | stickup_cams: 'cameras', 10 | base_stations: 'baseStations', 11 | chimes: 'chimes' 12 | } 13 | 14 | const emojis = { 15 | chime: '🛎', 16 | hp_cam_v1: '📷💡', 17 | hp_cam_v2: '📷💡', 18 | lpd_v1: '🚪', 19 | lpd_v2: '🚪' 20 | } 21 | 22 | module.exports = bottle => bottle.service( 'getDevicesList', getDevicesList, 23 | 'restClient', 24 | 'apiUrls', 25 | 'getLiveStream' 26 | ) 27 | 28 | function getDevicesList( restClient, apiUrls, getLiveStream ) { 29 | 30 | class DeviceHealth { 31 | constructor( jsonFromResponse ) { 32 | // a naieve copy like this could one day create a bug if Ring add property 33 | // names to their client API that shaddow our OOP methods 34 | Object.assign( this, jsonFromResponse ) 35 | 36 | this.updated_at = new Date( jsonFromResponse.updated_at ) 37 | } 38 | } 39 | 40 | class Device { 41 | constructor( jsonFromResponse ) { 42 | // a naieve copy like this could one day create a bug if Ring add property 43 | // names to their client API that shaddow our OOP methods 44 | Object.assign( this, jsonFromResponse ) 45 | } 46 | 47 | toString() { 48 | return `[${emojis[ this.kind ] || this.kind} ${this.description}]` 49 | } 50 | 51 | get apiUri() { 52 | return apiUrls.doorbots().device( this ) 53 | } 54 | 55 | async health() { 56 | const healthResponse = await restClient.authenticatedRequest( 'GET', this.apiUri.health()) 57 | return new DeviceHealth( healthResponse.device_health ) 58 | } 59 | } 60 | 61 | class Chime extends Device { 62 | get apiUri() { 63 | return apiUrls.chimes().device( this ) 64 | } 65 | } 66 | 67 | const lightable = Base => class extends Base { 68 | lightOn() { 69 | return restClient.authenticatedRequest( 'PUT', this.apiUri.lightOn()) 70 | } 71 | lightOff() { 72 | return restClient.authenticatedRequest( 'PUT', this.apiUri.lightOff()) 73 | } 74 | } 75 | 76 | const streamable = Base => class extends Base { 77 | get liveStream() { 78 | return getLiveStream( this ) 79 | } 80 | } 81 | 82 | const Doorbell = streamable( Device ) 83 | const Camera = streamable( lightable( Device )) 84 | 85 | const types = { 86 | doorbells: Doorbell, 87 | cameras: Camera, 88 | chimes: Chime, 89 | baseStations: Device, 90 | authorisedDoorbells: Device 91 | } 92 | 93 | return async() => { 94 | 95 | const rawDeviceList = await restClient.authenticatedRequest( 'GET', apiUrls.devices()) 96 | 97 | const listAsTypes = ( key, list ) => { 98 | if ( types[ key ]) { 99 | const Type = types[ key ] 100 | return list.map( d => new Type( d )) 101 | } else { 102 | return list 103 | } 104 | } 105 | 106 | const devices = Object.entries( rawDeviceList ).reduce(( acc, [ key, devicesList ]) => { 107 | const newKey = keyReplacements[ key ] || key 108 | 109 | return { 110 | ...acc, 111 | [ newKey ]: listAsTypes( newKey, devicesList ) 112 | } 113 | }, {}) 114 | 115 | // convenience property containing an array of all devices 116 | devices.all = [ ...devices.doorbells, ...devices.cameras, ...devices.chimes ] 117 | 118 | return devices 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /get-history-list.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function parseDate( dateStr ) { 4 | 5 | const date = new Date( dateStr ) 6 | 7 | if ( isNaN( date.getTime())) { 8 | throw new Error( `"${dateStr}" could not be parsed using Date constructor` ) 9 | } 10 | 11 | return date 12 | } 13 | 14 | const emojis = { 15 | motion: '🏃', 16 | ding: '🛎', 17 | on_demand: '📱' 18 | } 19 | 20 | module.exports = bottle => bottle.service( 'getHistoryList', getHistoryList, 21 | 'restClient', 22 | 'apiUrls' 23 | ) 24 | function getHistoryList( restClient, apiUrls ) { 25 | 26 | class HistoryItem { 27 | 28 | constructor( jsonFromResponse ) { 29 | // a naieve copy like this could one day create a bug if Ring add property 30 | // names to their client API that shaddow our OOP methods 31 | Object.assign( this, jsonFromResponse ) 32 | 33 | this.created_at = parseDate( jsonFromResponse.created_at ) 34 | } 35 | 36 | get apiUri() { 37 | return apiUrls.dings().ding( this ) 38 | } 39 | 40 | async videoUrl() { 41 | const response = await restClient.authenticatedRequest( 'GET', this.apiUri.recording()) 42 | return response.url 43 | } 44 | 45 | toString() { 46 | return `[${this.kind} ${emojis[ this.kind ] || ''} at "${this.doorbot.description}" ${this.created_at}]` 47 | } 48 | } 49 | 50 | return async() => { 51 | const historyListUrl = apiUrls.doorbots().history() 52 | const historyItems = await restClient.authenticatedRequest( 'GET', historyListUrl ) 53 | 54 | return historyItems.map( h => new HistoryItem( h )) 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /get-live-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = bottle => bottle.service( 'getLiveStream', getLiveStream, 4 | 'restClient', 5 | 'apiUrls', 6 | 'getActiveDings', 7 | 'logger' 8 | ) 9 | 10 | function getLiveStream( restClient, apiUrls, getActiveDings, logger ) { 11 | return async device => { 12 | 13 | const first = require( 'lodash.first' ) 14 | const maxTries = 10 15 | 16 | const waitForDing = async() => { 17 | 18 | // poll until the livestream is ready up to a maximum number of times 19 | for ( let tries = 0; tries < maxTries; tries++ ) { 20 | 21 | logger( `waiting for ding, attempt ${tries}` ) 22 | 23 | const dings = await getActiveDings({ burst: true }) 24 | 25 | const liveStreamDing = first( dings ) 26 | 27 | if ( liveStreamDing ) { 28 | return liveStreamDing 29 | } 30 | } 31 | 32 | throw new Error( `could not get a ding for this livestream after ${maxTries} attempts` ) 33 | } 34 | 35 | // create a new live stream: 36 | const liveStreamUrl = apiUrls.doorbots().device( device ).liveStream() 37 | await restClient.authenticatedRequest( 'POST', liveStreamUrl ) 38 | 39 | return waitForDing() 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const propagatedError = require( './propagated-error' ) 4 | const wireUp = require( './wire-up' ) 5 | 6 | module.exports = async({ 7 | email = process.env.RING_USER, 8 | password = process.env.RING_PASSWORD, 9 | poll = true, 10 | serverRoot = 'https://api.ring.com' }) => { 11 | 12 | if ( !email || !password ) { 13 | throw new Error( 14 | 'no username or password given. Either pass this in when creating ' + 15 | 'a ring-api instance, or set the RING_USER and RING_PASSWORD environment variables' 16 | ) 17 | } 18 | 19 | const container = wireUp({ email, password, poll, serverRoot }) 20 | 21 | // wait until we have a session before going any further 22 | try { 23 | await container.restClient.session 24 | } catch ( e ) { 25 | throw propagatedError( 'session failed to initialise, cannot create ring-api instance', e ) 26 | } 27 | 28 | // Ring will sometimes return a token, but then later say the user is not authorised. 29 | // Make a canary request to verify that the token actually works: 30 | try { 31 | await container.getDevicesList() 32 | } catch ( e ) { 33 | throw propagatedError( 'ring gave a seemingly valid authentication/authorisation, but the failed', e ) 34 | } 35 | 36 | container.pollForDings.start() 37 | 38 | return container.api 39 | } 40 | 41 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ring-api", 3 | "version": "3.0.6", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.0.0-beta.44", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", 10 | "integrity": "sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "7.0.0-beta.44" 14 | } 15 | }, 16 | "@babel/generator": { 17 | "version": "7.0.0-beta.44", 18 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz", 19 | "integrity": "sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ==", 20 | "dev": true, 21 | "requires": { 22 | "@babel/types": "7.0.0-beta.44", 23 | "jsesc": "^2.5.1", 24 | "lodash": "^4.2.0", 25 | "source-map": "^0.5.0", 26 | "trim-right": "^1.0.1" 27 | } 28 | }, 29 | "@babel/helper-function-name": { 30 | "version": "7.0.0-beta.44", 31 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz", 32 | "integrity": "sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg==", 33 | "dev": true, 34 | "requires": { 35 | "@babel/helper-get-function-arity": "7.0.0-beta.44", 36 | "@babel/template": "7.0.0-beta.44", 37 | "@babel/types": "7.0.0-beta.44" 38 | } 39 | }, 40 | "@babel/helper-get-function-arity": { 41 | "version": "7.0.0-beta.44", 42 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz", 43 | "integrity": "sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw==", 44 | "dev": true, 45 | "requires": { 46 | "@babel/types": "7.0.0-beta.44" 47 | } 48 | }, 49 | "@babel/helper-split-export-declaration": { 50 | "version": "7.0.0-beta.44", 51 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", 52 | "integrity": "sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA==", 53 | "dev": true, 54 | "requires": { 55 | "@babel/types": "7.0.0-beta.44" 56 | } 57 | }, 58 | "@babel/highlight": { 59 | "version": "7.0.0-beta.44", 60 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz", 61 | "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", 62 | "dev": true, 63 | "requires": { 64 | "chalk": "^2.0.0", 65 | "esutils": "^2.0.2", 66 | "js-tokens": "^3.0.0" 67 | } 68 | }, 69 | "@babel/template": { 70 | "version": "7.0.0-beta.44", 71 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", 72 | "integrity": "sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng==", 73 | "dev": true, 74 | "requires": { 75 | "@babel/code-frame": "7.0.0-beta.44", 76 | "@babel/types": "7.0.0-beta.44", 77 | "babylon": "7.0.0-beta.44", 78 | "lodash": "^4.2.0" 79 | } 80 | }, 81 | "@babel/traverse": { 82 | "version": "7.0.0-beta.44", 83 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz", 84 | "integrity": "sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA==", 85 | "dev": true, 86 | "requires": { 87 | "@babel/code-frame": "7.0.0-beta.44", 88 | "@babel/generator": "7.0.0-beta.44", 89 | "@babel/helper-function-name": "7.0.0-beta.44", 90 | "@babel/helper-split-export-declaration": "7.0.0-beta.44", 91 | "@babel/types": "7.0.0-beta.44", 92 | "babylon": "7.0.0-beta.44", 93 | "debug": "^3.1.0", 94 | "globals": "^11.1.0", 95 | "invariant": "^2.2.0", 96 | "lodash": "^4.2.0" 97 | } 98 | }, 99 | "@babel/types": { 100 | "version": "7.0.0-beta.44", 101 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz", 102 | "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", 103 | "dev": true, 104 | "requires": { 105 | "esutils": "^2.0.2", 106 | "lodash": "^4.2.0", 107 | "to-fast-properties": "^2.0.0" 108 | } 109 | }, 110 | "acorn": { 111 | "version": "5.7.1", 112 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", 113 | "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", 114 | "dev": true 115 | }, 116 | "acorn-jsx": { 117 | "version": "4.1.1", 118 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-4.1.1.tgz", 119 | "integrity": "sha512-JY+iV6r+cO21KtntVvFkD+iqjtdpRUpGqKWgfkCdZq1R+kbreEl8EcdcJR4SmiIgsIQT33s6QzheQ9a275Q8xw==", 120 | "dev": true, 121 | "requires": { 122 | "acorn": "^5.0.3" 123 | } 124 | }, 125 | "ajv": { 126 | "version": "6.5.2", 127 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.2.tgz", 128 | "integrity": "sha512-hOs7GfvI6tUI1LfZddH82ky6mOMyTuY0mk7kE2pWpmhhUSkumzaTO5vbVwij39MdwPQWCV4Zv57Eo06NtL/GVA==", 129 | "dev": true, 130 | "requires": { 131 | "fast-deep-equal": "^2.0.1", 132 | "fast-json-stable-stringify": "^2.0.0", 133 | "json-schema-traverse": "^0.4.1", 134 | "uri-js": "^4.2.1" 135 | } 136 | }, 137 | "ajv-keywords": { 138 | "version": "3.2.0", 139 | "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", 140 | "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", 141 | "dev": true 142 | }, 143 | "ansi-escapes": { 144 | "version": "3.1.0", 145 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", 146 | "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", 147 | "dev": true 148 | }, 149 | "ansi-regex": { 150 | "version": "2.1.1", 151 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 152 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", 153 | "dev": true 154 | }, 155 | "ansi-styles": { 156 | "version": "2.2.1", 157 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 158 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", 159 | "dev": true 160 | }, 161 | "argparse": { 162 | "version": "1.0.10", 163 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 164 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 165 | "dev": true, 166 | "requires": { 167 | "sprintf-js": "~1.0.2" 168 | } 169 | }, 170 | "array-union": { 171 | "version": "1.0.2", 172 | "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", 173 | "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", 174 | "dev": true, 175 | "requires": { 176 | "array-uniq": "^1.0.1" 177 | } 178 | }, 179 | "array-uniq": { 180 | "version": "1.0.3", 181 | "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", 182 | "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", 183 | "dev": true 184 | }, 185 | "arrify": { 186 | "version": "1.0.1", 187 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 188 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 189 | "dev": true 190 | }, 191 | "axios": { 192 | "version": "0.18.0", 193 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", 194 | "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", 195 | "requires": { 196 | "follow-redirects": "^1.3.0", 197 | "is-buffer": "^1.1.5" 198 | } 199 | }, 200 | "babel-code-frame": { 201 | "version": "6.26.0", 202 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 203 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 204 | "dev": true, 205 | "requires": { 206 | "chalk": "^1.1.3", 207 | "esutils": "^2.0.2", 208 | "js-tokens": "^3.0.2" 209 | }, 210 | "dependencies": { 211 | "chalk": { 212 | "version": "1.1.3", 213 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 214 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 215 | "dev": true, 216 | "requires": { 217 | "ansi-styles": "^2.2.1", 218 | "escape-string-regexp": "^1.0.2", 219 | "has-ansi": "^2.0.0", 220 | "strip-ansi": "^3.0.0", 221 | "supports-color": "^2.0.0" 222 | } 223 | }, 224 | "strip-ansi": { 225 | "version": "3.0.1", 226 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 227 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 228 | "dev": true, 229 | "requires": { 230 | "ansi-regex": "^2.0.0" 231 | } 232 | } 233 | } 234 | }, 235 | "babel-eslint": { 236 | "version": "8.2.6", 237 | "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.6.tgz", 238 | "integrity": "sha512-aCdHjhzcILdP8c9lej7hvXKvQieyRt20SF102SIGyY4cUIiw6UaAtK4j2o3dXX74jEmy0TJ0CEhv4fTIM3SzcA==", 239 | "dev": true, 240 | "requires": { 241 | "@babel/code-frame": "7.0.0-beta.44", 242 | "@babel/traverse": "7.0.0-beta.44", 243 | "@babel/types": "7.0.0-beta.44", 244 | "babylon": "7.0.0-beta.44", 245 | "eslint-scope": "3.7.1", 246 | "eslint-visitor-keys": "^1.0.0" 247 | } 248 | }, 249 | "babylon": { 250 | "version": "7.0.0-beta.44", 251 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", 252 | "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", 253 | "dev": true 254 | }, 255 | "balanced-match": { 256 | "version": "1.0.0", 257 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 258 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 259 | "dev": true 260 | }, 261 | "bignumber.js": { 262 | "version": "7.2.1", 263 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", 264 | "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" 265 | }, 266 | "bottlejs": { 267 | "version": "1.7.1", 268 | "resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-1.7.1.tgz", 269 | "integrity": "sha512-OUex4/uBgIyanzPpMzie5NfMfjJ23Z1mUy8ZC6omFBPwExMeoSrwyndlaZW0tci0unMH2LVfNn+jwDPRQLPncw==" 270 | }, 271 | "brace-expansion": { 272 | "version": "1.1.11", 273 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 274 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 275 | "dev": true, 276 | "requires": { 277 | "balanced-match": "^1.0.0", 278 | "concat-map": "0.0.1" 279 | } 280 | }, 281 | "caller-path": { 282 | "version": "0.1.0", 283 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 284 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 285 | "dev": true, 286 | "requires": { 287 | "callsites": "^0.2.0" 288 | } 289 | }, 290 | "callsites": { 291 | "version": "0.2.0", 292 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 293 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 294 | "dev": true 295 | }, 296 | "chalk": { 297 | "version": "2.3.0", 298 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", 299 | "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", 300 | "dev": true, 301 | "requires": { 302 | "ansi-styles": "^3.1.0", 303 | "escape-string-regexp": "^1.0.5", 304 | "supports-color": "^4.0.0" 305 | }, 306 | "dependencies": { 307 | "ansi-styles": { 308 | "version": "3.2.0", 309 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", 310 | "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", 311 | "dev": true, 312 | "requires": { 313 | "color-convert": "^1.9.0" 314 | } 315 | }, 316 | "supports-color": { 317 | "version": "4.5.0", 318 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", 319 | "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", 320 | "dev": true, 321 | "requires": { 322 | "has-flag": "^2.0.0" 323 | } 324 | } 325 | } 326 | }, 327 | "chardet": { 328 | "version": "0.4.2", 329 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", 330 | "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", 331 | "dev": true 332 | }, 333 | "circular-json": { 334 | "version": "0.3.3", 335 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 336 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 337 | "dev": true 338 | }, 339 | "cli-cursor": { 340 | "version": "2.1.0", 341 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 342 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 343 | "dev": true, 344 | "requires": { 345 | "restore-cursor": "^2.0.0" 346 | } 347 | }, 348 | "cli-width": { 349 | "version": "2.2.0", 350 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 351 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 352 | "dev": true 353 | }, 354 | "color-convert": { 355 | "version": "1.9.1", 356 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", 357 | "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", 358 | "dev": true, 359 | "requires": { 360 | "color-name": "^1.1.1" 361 | } 362 | }, 363 | "color-name": { 364 | "version": "1.1.3", 365 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 366 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 367 | "dev": true 368 | }, 369 | "colors": { 370 | "version": "1.3.1", 371 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.1.tgz", 372 | "integrity": "sha512-jg/vxRmv430jixZrC+La5kMbUWqIg32/JsYNZb94+JEmzceYbWKTsv1OuTp+7EaqiaWRR2tPcykibwCRgclIsw==" 373 | }, 374 | "concat-map": { 375 | "version": "0.0.1", 376 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 377 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 378 | "dev": true 379 | }, 380 | "content-type": { 381 | "version": "1.0.4", 382 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 383 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 384 | }, 385 | "cross-spawn": { 386 | "version": "6.0.5", 387 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 388 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 389 | "dev": true, 390 | "requires": { 391 | "nice-try": "^1.0.4", 392 | "path-key": "^2.0.1", 393 | "semver": "^5.5.0", 394 | "shebang-command": "^1.2.0", 395 | "which": "^1.2.9" 396 | } 397 | }, 398 | "crypto-js": { 399 | "version": "3.1.9-1", 400 | "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.9-1.tgz", 401 | "integrity": "sha1-/aGedh/Ad+Af+/3G6f38WeiAbNg=" 402 | }, 403 | "debug": { 404 | "version": "3.1.0", 405 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 406 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 407 | "requires": { 408 | "ms": "2.0.0" 409 | } 410 | }, 411 | "deep-is": { 412 | "version": "0.1.3", 413 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 414 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 415 | "dev": true 416 | }, 417 | "define-properties": { 418 | "version": "1.1.3", 419 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 420 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 421 | "dev": true, 422 | "requires": { 423 | "object-keys": "^1.0.12" 424 | } 425 | }, 426 | "del": { 427 | "version": "2.2.2", 428 | "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", 429 | "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", 430 | "dev": true, 431 | "requires": { 432 | "globby": "^5.0.0", 433 | "is-path-cwd": "^1.0.0", 434 | "is-path-in-cwd": "^1.0.0", 435 | "object-assign": "^4.0.1", 436 | "pify": "^2.0.0", 437 | "pinkie-promise": "^2.0.0", 438 | "rimraf": "^2.2.8" 439 | } 440 | }, 441 | "doctrine": { 442 | "version": "2.1.0", 443 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 444 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 445 | "dev": true, 446 | "requires": { 447 | "esutils": "^2.0.2" 448 | } 449 | }, 450 | "es-abstract": { 451 | "version": "1.12.0", 452 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 453 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 454 | "dev": true, 455 | "requires": { 456 | "es-to-primitive": "^1.1.1", 457 | "function-bind": "^1.1.1", 458 | "has": "^1.0.1", 459 | "is-callable": "^1.1.3", 460 | "is-regex": "^1.0.4" 461 | } 462 | }, 463 | "es-to-primitive": { 464 | "version": "1.1.1", 465 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 466 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 467 | "dev": true, 468 | "requires": { 469 | "is-callable": "^1.1.1", 470 | "is-date-object": "^1.0.1", 471 | "is-symbol": "^1.0.1" 472 | } 473 | }, 474 | "escape-string-regexp": { 475 | "version": "1.0.5", 476 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 477 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 478 | "dev": true 479 | }, 480 | "eslint": { 481 | "version": "5.3.0", 482 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.3.0.tgz", 483 | "integrity": "sha512-N/tCqlMKkyNvAvLu+zI9AqDasnSLt00K+Hu8kdsERliC9jYEc8ck12XtjvOXrBKu8fK6RrBcN9bat6Xk++9jAg==", 484 | "dev": true, 485 | "requires": { 486 | "ajv": "^6.5.0", 487 | "babel-code-frame": "^6.26.0", 488 | "chalk": "^2.1.0", 489 | "cross-spawn": "^6.0.5", 490 | "debug": "^3.1.0", 491 | "doctrine": "^2.1.0", 492 | "eslint-scope": "^4.0.0", 493 | "eslint-utils": "^1.3.1", 494 | "eslint-visitor-keys": "^1.0.0", 495 | "espree": "^4.0.0", 496 | "esquery": "^1.0.1", 497 | "esutils": "^2.0.2", 498 | "file-entry-cache": "^2.0.0", 499 | "functional-red-black-tree": "^1.0.1", 500 | "glob": "^7.1.2", 501 | "globals": "^11.7.0", 502 | "ignore": "^4.0.2", 503 | "imurmurhash": "^0.1.4", 504 | "inquirer": "^5.2.0", 505 | "is-resolvable": "^1.1.0", 506 | "js-yaml": "^3.11.0", 507 | "json-stable-stringify-without-jsonify": "^1.0.1", 508 | "levn": "^0.3.0", 509 | "lodash": "^4.17.5", 510 | "minimatch": "^3.0.4", 511 | "mkdirp": "^0.5.1", 512 | "natural-compare": "^1.4.0", 513 | "optionator": "^0.8.2", 514 | "path-is-inside": "^1.0.2", 515 | "pluralize": "^7.0.0", 516 | "progress": "^2.0.0", 517 | "regexpp": "^2.0.0", 518 | "require-uncached": "^1.0.3", 519 | "semver": "^5.5.0", 520 | "string.prototype.matchall": "^2.0.0", 521 | "strip-ansi": "^4.0.0", 522 | "strip-json-comments": "^2.0.1", 523 | "table": "^4.0.3", 524 | "text-table": "^0.2.0" 525 | }, 526 | "dependencies": { 527 | "eslint-scope": { 528 | "version": "4.0.0", 529 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", 530 | "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", 531 | "dev": true, 532 | "requires": { 533 | "esrecurse": "^4.1.0", 534 | "estraverse": "^4.1.1" 535 | } 536 | }, 537 | "lodash": { 538 | "version": "4.17.10", 539 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 540 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 541 | "dev": true 542 | } 543 | } 544 | }, 545 | "eslint-config-idiomatic": { 546 | "version": "4.0.0", 547 | "resolved": "https://registry.npmjs.org/eslint-config-idiomatic/-/eslint-config-idiomatic-4.0.0.tgz", 548 | "integrity": "sha1-D/K1Q1ycKQ2SIjvlvZfEzxnU0NQ=", 549 | "dev": true 550 | }, 551 | "eslint-scope": { 552 | "version": "3.7.1", 553 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", 554 | "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", 555 | "dev": true, 556 | "requires": { 557 | "esrecurse": "^4.1.0", 558 | "estraverse": "^4.1.1" 559 | } 560 | }, 561 | "eslint-utils": { 562 | "version": "1.3.1", 563 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", 564 | "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", 565 | "dev": true 566 | }, 567 | "eslint-visitor-keys": { 568 | "version": "1.0.0", 569 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", 570 | "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", 571 | "dev": true 572 | }, 573 | "espree": { 574 | "version": "4.0.0", 575 | "resolved": "https://registry.npmjs.org/espree/-/espree-4.0.0.tgz", 576 | "integrity": "sha512-kapdTCt1bjmspxStVKX6huolXVV5ZfyZguY1lcfhVVZstce3bqxH9mcLzNn3/mlgW6wQ732+0fuG9v7h0ZQoKg==", 577 | "dev": true, 578 | "requires": { 579 | "acorn": "^5.6.0", 580 | "acorn-jsx": "^4.1.1" 581 | } 582 | }, 583 | "esprima": { 584 | "version": "4.0.1", 585 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 586 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 587 | "dev": true 588 | }, 589 | "esquery": { 590 | "version": "1.0.1", 591 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 592 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 593 | "dev": true, 594 | "requires": { 595 | "estraverse": "^4.0.0" 596 | } 597 | }, 598 | "esrecurse": { 599 | "version": "4.2.0", 600 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", 601 | "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", 602 | "dev": true, 603 | "requires": { 604 | "estraverse": "^4.1.0", 605 | "object-assign": "^4.0.1" 606 | } 607 | }, 608 | "estraverse": { 609 | "version": "4.2.0", 610 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 611 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 612 | "dev": true 613 | }, 614 | "esutils": { 615 | "version": "2.0.2", 616 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 617 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 618 | "dev": true 619 | }, 620 | "external-editor": { 621 | "version": "2.2.0", 622 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", 623 | "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", 624 | "dev": true, 625 | "requires": { 626 | "chardet": "^0.4.0", 627 | "iconv-lite": "^0.4.17", 628 | "tmp": "^0.0.33" 629 | } 630 | }, 631 | "fast-deep-equal": { 632 | "version": "2.0.1", 633 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 634 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 635 | "dev": true 636 | }, 637 | "fast-json-stable-stringify": { 638 | "version": "2.0.0", 639 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 640 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 641 | "dev": true 642 | }, 643 | "fast-levenshtein": { 644 | "version": "2.0.6", 645 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 646 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 647 | "dev": true 648 | }, 649 | "figures": { 650 | "version": "2.0.0", 651 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 652 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 653 | "dev": true, 654 | "requires": { 655 | "escape-string-regexp": "^1.0.5" 656 | } 657 | }, 658 | "file-entry-cache": { 659 | "version": "2.0.0", 660 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 661 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 662 | "dev": true, 663 | "requires": { 664 | "flat-cache": "^1.2.1", 665 | "object-assign": "^4.0.1" 666 | } 667 | }, 668 | "flat-cache": { 669 | "version": "1.3.0", 670 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", 671 | "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", 672 | "dev": true, 673 | "requires": { 674 | "circular-json": "^0.3.1", 675 | "del": "^2.0.2", 676 | "graceful-fs": "^4.1.2", 677 | "write": "^0.2.1" 678 | } 679 | }, 680 | "follow-redirects": { 681 | "version": "1.5.5", 682 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.5.tgz", 683 | "integrity": "sha512-GHjtHDlY/ehslqv0Gr5N0PUJppgg/q0rOBvX0na1s7y1A3LWxPqCYU76s3Z1bM4+UZB4QF0usaXLT5wFpof5PA==", 684 | "requires": { 685 | "debug": "^3.1.0" 686 | } 687 | }, 688 | "fs.realpath": { 689 | "version": "1.0.0", 690 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 691 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 692 | "dev": true 693 | }, 694 | "function-bind": { 695 | "version": "1.1.1", 696 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 697 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 698 | "dev": true 699 | }, 700 | "functional-red-black-tree": { 701 | "version": "1.0.1", 702 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 703 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 704 | "dev": true 705 | }, 706 | "glob": { 707 | "version": "7.1.2", 708 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 709 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 710 | "dev": true, 711 | "requires": { 712 | "fs.realpath": "^1.0.0", 713 | "inflight": "^1.0.4", 714 | "inherits": "2", 715 | "minimatch": "^3.0.4", 716 | "once": "^1.3.0", 717 | "path-is-absolute": "^1.0.0" 718 | } 719 | }, 720 | "globals": { 721 | "version": "11.7.0", 722 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz", 723 | "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==", 724 | "dev": true 725 | }, 726 | "globby": { 727 | "version": "5.0.0", 728 | "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", 729 | "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", 730 | "dev": true, 731 | "requires": { 732 | "array-union": "^1.0.1", 733 | "arrify": "^1.0.0", 734 | "glob": "^7.0.3", 735 | "object-assign": "^4.0.1", 736 | "pify": "^2.0.0", 737 | "pinkie-promise": "^2.0.0" 738 | } 739 | }, 740 | "graceful-fs": { 741 | "version": "4.1.11", 742 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", 743 | "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", 744 | "dev": true 745 | }, 746 | "has": { 747 | "version": "1.0.3", 748 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 749 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 750 | "dev": true, 751 | "requires": { 752 | "function-bind": "^1.1.1" 753 | } 754 | }, 755 | "has-ansi": { 756 | "version": "2.0.0", 757 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 758 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 759 | "dev": true, 760 | "requires": { 761 | "ansi-regex": "^2.0.0" 762 | } 763 | }, 764 | "has-flag": { 765 | "version": "2.0.0", 766 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 767 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 768 | "dev": true 769 | }, 770 | "has-symbols": { 771 | "version": "1.0.0", 772 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", 773 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", 774 | "dev": true 775 | }, 776 | "iconv-lite": { 777 | "version": "0.4.23", 778 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", 779 | "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", 780 | "dev": true, 781 | "requires": { 782 | "safer-buffer": ">= 2.1.2 < 3" 783 | } 784 | }, 785 | "ignore": { 786 | "version": "4.0.6", 787 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 788 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 789 | "dev": true 790 | }, 791 | "imurmurhash": { 792 | "version": "0.1.4", 793 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 794 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 795 | "dev": true 796 | }, 797 | "inflight": { 798 | "version": "1.0.6", 799 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 800 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 801 | "dev": true, 802 | "requires": { 803 | "once": "^1.3.0", 804 | "wrappy": "1" 805 | } 806 | }, 807 | "inherits": { 808 | "version": "2.0.3", 809 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 810 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 811 | "dev": true 812 | }, 813 | "inquirer": { 814 | "version": "5.2.0", 815 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", 816 | "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", 817 | "dev": true, 818 | "requires": { 819 | "ansi-escapes": "^3.0.0", 820 | "chalk": "^2.0.0", 821 | "cli-cursor": "^2.1.0", 822 | "cli-width": "^2.0.0", 823 | "external-editor": "^2.1.0", 824 | "figures": "^2.0.0", 825 | "lodash": "^4.3.0", 826 | "mute-stream": "0.0.7", 827 | "run-async": "^2.2.0", 828 | "rxjs": "^5.5.2", 829 | "string-width": "^2.1.0", 830 | "strip-ansi": "^4.0.0", 831 | "through": "^2.3.6" 832 | } 833 | }, 834 | "invariant": { 835 | "version": "2.2.4", 836 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 837 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 838 | "dev": true, 839 | "requires": { 840 | "loose-envify": "^1.0.0" 841 | } 842 | }, 843 | "is-buffer": { 844 | "version": "1.1.6", 845 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 846 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" 847 | }, 848 | "is-callable": { 849 | "version": "1.1.4", 850 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", 851 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", 852 | "dev": true 853 | }, 854 | "is-date-object": { 855 | "version": "1.0.1", 856 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 857 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 858 | "dev": true 859 | }, 860 | "is-fullwidth-code-point": { 861 | "version": "2.0.0", 862 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 863 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 864 | "dev": true 865 | }, 866 | "is-path-cwd": { 867 | "version": "1.0.0", 868 | "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", 869 | "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", 870 | "dev": true 871 | }, 872 | "is-path-in-cwd": { 873 | "version": "1.0.1", 874 | "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", 875 | "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", 876 | "dev": true, 877 | "requires": { 878 | "is-path-inside": "^1.0.0" 879 | } 880 | }, 881 | "is-path-inside": { 882 | "version": "1.0.1", 883 | "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", 884 | "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", 885 | "dev": true, 886 | "requires": { 887 | "path-is-inside": "^1.0.1" 888 | } 889 | }, 890 | "is-promise": { 891 | "version": "2.1.0", 892 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 893 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 894 | "dev": true 895 | }, 896 | "is-regex": { 897 | "version": "1.0.4", 898 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 899 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 900 | "dev": true, 901 | "requires": { 902 | "has": "^1.0.1" 903 | } 904 | }, 905 | "is-resolvable": { 906 | "version": "1.1.0", 907 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", 908 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", 909 | "dev": true 910 | }, 911 | "is-symbol": { 912 | "version": "1.0.1", 913 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 914 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 915 | "dev": true 916 | }, 917 | "isexe": { 918 | "version": "2.0.0", 919 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 920 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 921 | "dev": true 922 | }, 923 | "jimhigson-my-eslint-rules": { 924 | "version": "1.0.4", 925 | "resolved": "https://registry.npmjs.org/jimhigson-my-eslint-rules/-/jimhigson-my-eslint-rules-1.0.4.tgz", 926 | "integrity": "sha512-pc1MnYNNIZ9MjBAZsg9bvbQhxY/C3lDQIZVBcPTk0dR/cZ98xVBKkDaX8NIjTMOXlNgufBB24q5hKHHPvPl9sQ==", 927 | "dev": true 928 | }, 929 | "js-tokens": { 930 | "version": "3.0.2", 931 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 932 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", 933 | "dev": true 934 | }, 935 | "js-yaml": { 936 | "version": "3.12.0", 937 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", 938 | "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", 939 | "dev": true, 940 | "requires": { 941 | "argparse": "^1.0.7", 942 | "esprima": "^4.0.0" 943 | } 944 | }, 945 | "jsesc": { 946 | "version": "2.5.1", 947 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", 948 | "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=", 949 | "dev": true 950 | }, 951 | "json-bigint": { 952 | "version": "0.3.0", 953 | "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", 954 | "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", 955 | "requires": { 956 | "bignumber.js": "^7.0.0" 957 | } 958 | }, 959 | "json-schema-traverse": { 960 | "version": "0.4.1", 961 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 962 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 963 | "dev": true 964 | }, 965 | "json-stable-stringify-without-jsonify": { 966 | "version": "1.0.1", 967 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 968 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 969 | "dev": true 970 | }, 971 | "keychain": { 972 | "version": "1.3.0", 973 | "resolved": "https://registry.npmjs.org/keychain/-/keychain-1.3.0.tgz", 974 | "integrity": "sha1-zLjdxkpi801UGsJeYSGGRCpDJBA=" 975 | }, 976 | "levn": { 977 | "version": "0.3.0", 978 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 979 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 980 | "dev": true, 981 | "requires": { 982 | "prelude-ls": "~1.1.2", 983 | "type-check": "~0.3.2" 984 | } 985 | }, 986 | "lodash": { 987 | "version": "4.17.10", 988 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", 989 | "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", 990 | "dev": true 991 | }, 992 | "lodash.assign": { 993 | "version": "4.2.0", 994 | "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", 995 | "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=" 996 | }, 997 | "lodash.compose": { 998 | "version": "2.4.1", 999 | "resolved": "https://registry.npmjs.org/lodash.compose/-/lodash.compose-2.4.1.tgz", 1000 | "integrity": "sha1-oj9Bb9kc7oLGeHn2OgcNPLrDFSU=", 1001 | "requires": { 1002 | "lodash.isfunction": "~2.4.1" 1003 | } 1004 | }, 1005 | "lodash.defaultsdeep": { 1006 | "version": "4.6.0", 1007 | "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.0.tgz", 1008 | "integrity": "sha1-vsECT4WxvZbL6kBbI8FK1kQ6b4E=", 1009 | "dev": true 1010 | }, 1011 | "lodash.first": { 1012 | "version": "3.0.0", 1013 | "resolved": "https://registry.npmjs.org/lodash.first/-/lodash.first-3.0.0.tgz", 1014 | "integrity": "sha1-Xa4YDX+BjuZfxbIQsQSnu++YoWo=" 1015 | }, 1016 | "lodash.flatten": { 1017 | "version": "4.4.0", 1018 | "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", 1019 | "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" 1020 | }, 1021 | "lodash.isfunction": { 1022 | "version": "2.4.1", 1023 | "resolved": "https://registry.npmjs.org/lodash.isfunction/-/lodash.isfunction-2.4.1.tgz", 1024 | "integrity": "sha1-LP1XXHPkmKtX4xm3f6Aq3vE6lNE=" 1025 | }, 1026 | "lodash.isobject": { 1027 | "version": "3.0.2", 1028 | "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", 1029 | "integrity": "sha1-PI+41bW/S/kK4G4U8qUwpO2TXh0=" 1030 | }, 1031 | "lodash.mapkeys": { 1032 | "version": "4.6.0", 1033 | "resolved": "https://registry.npmjs.org/lodash.mapkeys/-/lodash.mapkeys-4.6.0.tgz", 1034 | "integrity": "sha1-3yz6Ix18V8eorQA6va1dc9PqUZU=" 1035 | }, 1036 | "loose-envify": { 1037 | "version": "1.4.0", 1038 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 1039 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 1040 | "dev": true, 1041 | "requires": { 1042 | "js-tokens": "^3.0.0 || ^4.0.0" 1043 | } 1044 | }, 1045 | "mimic-fn": { 1046 | "version": "1.2.0", 1047 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 1048 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", 1049 | "dev": true 1050 | }, 1051 | "minimatch": { 1052 | "version": "3.0.4", 1053 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1054 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1055 | "dev": true, 1056 | "requires": { 1057 | "brace-expansion": "^1.1.7" 1058 | } 1059 | }, 1060 | "minimist": { 1061 | "version": "0.0.8", 1062 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1063 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 1064 | "dev": true 1065 | }, 1066 | "mkdirp": { 1067 | "version": "0.5.1", 1068 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1069 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1070 | "dev": true, 1071 | "requires": { 1072 | "minimist": "0.0.8" 1073 | } 1074 | }, 1075 | "ms": { 1076 | "version": "2.0.0", 1077 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1078 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1079 | }, 1080 | "mute-stream": { 1081 | "version": "0.0.7", 1082 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 1083 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 1084 | "dev": true 1085 | }, 1086 | "natural-compare": { 1087 | "version": "1.4.0", 1088 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 1089 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 1090 | "dev": true 1091 | }, 1092 | "nice-try": { 1093 | "version": "1.0.4", 1094 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.4.tgz", 1095 | "integrity": "sha512-2NpiFHqC87y/zFke0fC0spBXL3bBsoh/p5H1EFhshxjCR5+0g2d6BiXbUFz9v1sAcxsk2htp2eQnNIci2dIYcA==", 1096 | "dev": true 1097 | }, 1098 | "node-ask": { 1099 | "version": "1.0.1", 1100 | "resolved": "https://registry.npmjs.org/node-ask/-/node-ask-1.0.1.tgz", 1101 | "integrity": "sha1-yqoQdsxY4DZCZ6CQPj6t+sFYOWs=", 1102 | "dev": true 1103 | }, 1104 | "object-assign": { 1105 | "version": "4.1.1", 1106 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1107 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1108 | "dev": true 1109 | }, 1110 | "object-keys": { 1111 | "version": "1.0.12", 1112 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", 1113 | "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", 1114 | "dev": true 1115 | }, 1116 | "once": { 1117 | "version": "1.4.0", 1118 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1119 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1120 | "dev": true, 1121 | "requires": { 1122 | "wrappy": "1" 1123 | } 1124 | }, 1125 | "onetime": { 1126 | "version": "2.0.1", 1127 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 1128 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 1129 | "dev": true, 1130 | "requires": { 1131 | "mimic-fn": "^1.0.0" 1132 | } 1133 | }, 1134 | "optionator": { 1135 | "version": "0.8.2", 1136 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 1137 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 1138 | "dev": true, 1139 | "requires": { 1140 | "deep-is": "~0.1.3", 1141 | "fast-levenshtein": "~2.0.4", 1142 | "levn": "~0.3.0", 1143 | "prelude-ls": "~1.1.2", 1144 | "type-check": "~0.3.2", 1145 | "wordwrap": "~1.0.0" 1146 | } 1147 | }, 1148 | "os-tmpdir": { 1149 | "version": "1.0.2", 1150 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1151 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1152 | "dev": true 1153 | }, 1154 | "path-is-absolute": { 1155 | "version": "1.0.1", 1156 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1157 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1158 | "dev": true 1159 | }, 1160 | "path-is-inside": { 1161 | "version": "1.0.2", 1162 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1163 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1164 | "dev": true 1165 | }, 1166 | "path-key": { 1167 | "version": "2.0.1", 1168 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1169 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1170 | "dev": true 1171 | }, 1172 | "pify": { 1173 | "version": "2.3.0", 1174 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", 1175 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", 1176 | "dev": true 1177 | }, 1178 | "pinkie": { 1179 | "version": "2.0.4", 1180 | "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", 1181 | "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", 1182 | "dev": true 1183 | }, 1184 | "pinkie-promise": { 1185 | "version": "2.0.1", 1186 | "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", 1187 | "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", 1188 | "dev": true, 1189 | "requires": { 1190 | "pinkie": "^2.0.0" 1191 | } 1192 | }, 1193 | "pluralize": { 1194 | "version": "7.0.0", 1195 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", 1196 | "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", 1197 | "dev": true 1198 | }, 1199 | "prelude-ls": { 1200 | "version": "1.1.2", 1201 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1202 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1203 | "dev": true 1204 | }, 1205 | "progress": { 1206 | "version": "2.0.0", 1207 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", 1208 | "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", 1209 | "dev": true 1210 | }, 1211 | "promiscuous": { 1212 | "version": "0.6.0", 1213 | "resolved": "https://registry.npmjs.org/promiscuous/-/promiscuous-0.6.0.tgz", 1214 | "integrity": "sha1-VAFM09Ysr+gx4zVJkMBf9beMiJI=", 1215 | "optional": true 1216 | }, 1217 | "punycode": { 1218 | "version": "2.1.1", 1219 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1220 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1221 | "dev": true 1222 | }, 1223 | "regexp.prototype.flags": { 1224 | "version": "1.2.0", 1225 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.2.0.tgz", 1226 | "integrity": "sha512-ztaw4M1VqgMwl9HlPpOuiYgItcHlunW0He2fE6eNfT6E/CF2FtYi9ofOYe4mKntstYk0Fyh/rDRBdS3AnxjlrA==", 1227 | "dev": true, 1228 | "requires": { 1229 | "define-properties": "^1.1.2" 1230 | } 1231 | }, 1232 | "regexpp": { 1233 | "version": "2.0.0", 1234 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.0.tgz", 1235 | "integrity": "sha512-g2FAVtR8Uh8GO1Nv5wpxW7VFVwHcCEr4wyA8/MHiRkO8uHoR5ntAA8Uq3P1vvMTX/BeQiRVSpDGLd+Wn5HNOTA==", 1236 | "dev": true 1237 | }, 1238 | "require-uncached": { 1239 | "version": "1.0.3", 1240 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 1241 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 1242 | "dev": true, 1243 | "requires": { 1244 | "caller-path": "^0.1.0", 1245 | "resolve-from": "^1.0.0" 1246 | } 1247 | }, 1248 | "resolve-from": { 1249 | "version": "1.0.1", 1250 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 1251 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 1252 | "dev": true 1253 | }, 1254 | "restore-cursor": { 1255 | "version": "2.0.0", 1256 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 1257 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 1258 | "dev": true, 1259 | "requires": { 1260 | "onetime": "^2.0.0", 1261 | "signal-exit": "^3.0.2" 1262 | } 1263 | }, 1264 | "rimraf": { 1265 | "version": "2.6.2", 1266 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1267 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1268 | "dev": true, 1269 | "requires": { 1270 | "glob": "^7.0.5" 1271 | } 1272 | }, 1273 | "run-async": { 1274 | "version": "2.3.0", 1275 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 1276 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 1277 | "dev": true, 1278 | "requires": { 1279 | "is-promise": "^2.1.0" 1280 | } 1281 | }, 1282 | "rxjs": { 1283 | "version": "5.5.11", 1284 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", 1285 | "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", 1286 | "dev": true, 1287 | "requires": { 1288 | "symbol-observable": "1.0.1" 1289 | } 1290 | }, 1291 | "safer-buffer": { 1292 | "version": "2.1.2", 1293 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1294 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1295 | "dev": true 1296 | }, 1297 | "semver": { 1298 | "version": "5.5.0", 1299 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", 1300 | "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", 1301 | "dev": true 1302 | }, 1303 | "shebang-command": { 1304 | "version": "1.2.0", 1305 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1306 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1307 | "dev": true, 1308 | "requires": { 1309 | "shebang-regex": "^1.0.0" 1310 | } 1311 | }, 1312 | "shebang-regex": { 1313 | "version": "1.0.0", 1314 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1315 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1316 | "dev": true 1317 | }, 1318 | "signal-exit": { 1319 | "version": "3.0.2", 1320 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1321 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1322 | "dev": true 1323 | }, 1324 | "sip.js": { 1325 | "version": "0.11.3", 1326 | "resolved": "https://registry.npmjs.org/sip.js/-/sip.js-0.11.3.tgz", 1327 | "integrity": "sha512-yShB/0Sr0igUPbQvF5xY8uyfftB6oYJraJhyRSEjY+HAh9kCN+JLOxgDGO+7P7QT1jn8nrZVJyidH0wGrql41A==", 1328 | "requires": { 1329 | "crypto-js": "^3.1.9-1", 1330 | "promiscuous": "^0.6.0" 1331 | } 1332 | }, 1333 | "slice-ansi": { 1334 | "version": "1.0.0", 1335 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", 1336 | "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", 1337 | "dev": true, 1338 | "requires": { 1339 | "is-fullwidth-code-point": "^2.0.0" 1340 | } 1341 | }, 1342 | "source-map": { 1343 | "version": "0.5.7", 1344 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 1345 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", 1346 | "dev": true 1347 | }, 1348 | "sprintf-js": { 1349 | "version": "1.0.3", 1350 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1351 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1352 | "dev": true 1353 | }, 1354 | "string-width": { 1355 | "version": "2.1.1", 1356 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1357 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1358 | "dev": true, 1359 | "requires": { 1360 | "is-fullwidth-code-point": "^2.0.0", 1361 | "strip-ansi": "^4.0.0" 1362 | } 1363 | }, 1364 | "string.prototype.matchall": { 1365 | "version": "2.0.0", 1366 | "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-2.0.0.tgz", 1367 | "integrity": "sha512-WoZ+B2ypng1dp4iFLF2kmZlwwlE19gmjgKuhL1FJfDgCREWb3ye3SDVHSzLH6bxfnvYmkCxbzkmWcQZHA4P//Q==", 1368 | "dev": true, 1369 | "requires": { 1370 | "define-properties": "^1.1.2", 1371 | "es-abstract": "^1.10.0", 1372 | "function-bind": "^1.1.1", 1373 | "has-symbols": "^1.0.0", 1374 | "regexp.prototype.flags": "^1.2.0" 1375 | } 1376 | }, 1377 | "strip-ansi": { 1378 | "version": "4.0.0", 1379 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1380 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1381 | "dev": true, 1382 | "requires": { 1383 | "ansi-regex": "^3.0.0" 1384 | }, 1385 | "dependencies": { 1386 | "ansi-regex": { 1387 | "version": "3.0.0", 1388 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 1389 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 1390 | "dev": true 1391 | } 1392 | } 1393 | }, 1394 | "strip-json-comments": { 1395 | "version": "2.0.1", 1396 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1397 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1398 | "dev": true 1399 | }, 1400 | "supports-color": { 1401 | "version": "2.0.0", 1402 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 1403 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", 1404 | "dev": true 1405 | }, 1406 | "symbol-observable": { 1407 | "version": "1.0.1", 1408 | "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", 1409 | "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", 1410 | "dev": true 1411 | }, 1412 | "table": { 1413 | "version": "4.0.3", 1414 | "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", 1415 | "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", 1416 | "dev": true, 1417 | "requires": { 1418 | "ajv": "^6.0.1", 1419 | "ajv-keywords": "^3.0.0", 1420 | "chalk": "^2.1.0", 1421 | "lodash": "^4.17.4", 1422 | "slice-ansi": "1.0.0", 1423 | "string-width": "^2.1.1" 1424 | } 1425 | }, 1426 | "text-table": { 1427 | "version": "0.2.0", 1428 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1429 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1430 | "dev": true 1431 | }, 1432 | "through": { 1433 | "version": "2.3.8", 1434 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1435 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1436 | "dev": true 1437 | }, 1438 | "time-constants": { 1439 | "version": "1.0.2", 1440 | "resolved": "https://registry.npmjs.org/time-constants/-/time-constants-1.0.2.tgz", 1441 | "integrity": "sha1-7CZnsFQIFMWrXoIpfLDzvAaRR2Q=" 1442 | }, 1443 | "timeout-as-promise": { 1444 | "version": "1.0.0", 1445 | "resolved": "https://registry.npmjs.org/timeout-as-promise/-/timeout-as-promise-1.0.0.tgz", 1446 | "integrity": "sha1-c2foEfyZKs/Nzaq/LlDfr4shV28=" 1447 | }, 1448 | "tmp": { 1449 | "version": "0.0.33", 1450 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 1451 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 1452 | "dev": true, 1453 | "requires": { 1454 | "os-tmpdir": "~1.0.2" 1455 | } 1456 | }, 1457 | "to-fast-properties": { 1458 | "version": "2.0.0", 1459 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", 1460 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", 1461 | "dev": true 1462 | }, 1463 | "trim-right": { 1464 | "version": "1.0.1", 1465 | "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", 1466 | "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", 1467 | "dev": true 1468 | }, 1469 | "type-check": { 1470 | "version": "0.3.2", 1471 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1472 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1473 | "dev": true, 1474 | "requires": { 1475 | "prelude-ls": "~1.1.2" 1476 | } 1477 | }, 1478 | "uri-js": { 1479 | "version": "4.2.2", 1480 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1481 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1482 | "dev": true, 1483 | "requires": { 1484 | "punycode": "^2.1.0" 1485 | } 1486 | }, 1487 | "which": { 1488 | "version": "1.3.1", 1489 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1490 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1491 | "dev": true, 1492 | "requires": { 1493 | "isexe": "^2.0.0" 1494 | } 1495 | }, 1496 | "wordwrap": { 1497 | "version": "1.0.0", 1498 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1499 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1500 | "dev": true 1501 | }, 1502 | "wrappy": { 1503 | "version": "1.0.2", 1504 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1505 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1506 | "dev": true 1507 | }, 1508 | "write": { 1509 | "version": "0.2.1", 1510 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1511 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1512 | "dev": true, 1513 | "requires": { 1514 | "mkdirp": "^0.5.1" 1515 | } 1516 | } 1517 | } 1518 | } 1519 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ring-api", 3 | "version": "3.0.7", 4 | "description": "Unofficial API for ring doorbells and other devices", 5 | "main": "main.js", 6 | "dependencies": { 7 | "axios": "^0.18.0", 8 | "bottlejs": "^1.7.1", 9 | "colors": "^1.3.1", 10 | "content-type": "^1.0.4", 11 | "debug": "^3.1.0", 12 | "json-bigint": "^0.3.0", 13 | "keychain": "^1.3.0", 14 | "lodash.assign": "^4.2.0", 15 | "lodash.compose": "^2.4.1", 16 | "lodash.first": "^3.0.0", 17 | "lodash.flatten": "^4.4.0", 18 | "lodash.isobject": "^3.0.2", 19 | "lodash.mapkeys": "^4.6.0", 20 | "sip.js": "^0.11.3", 21 | "time-constants": "^1.0.2", 22 | "timeout-as-promise": "^1.0.0" 23 | }, 24 | "repository": "github:jimhigson/ring-api", 25 | "scripts": { 26 | "lint": "eslint .", 27 | "lint:fix": "eslint --fix ." 28 | }, 29 | "author": "Jim Higson", 30 | "license": "ISC", 31 | "devDependencies": { 32 | "babel-eslint": "^8.2.6", 33 | "eslint": "^5.3.0", 34 | "eslint-config-idiomatic": "^4.0.0", 35 | "jimhigson-my-eslint-rules": "^1.0.4", 36 | "lodash.defaultsdeep": "^4.6.0", 37 | "node-ask": "^1.0.1" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /parse-ring-json-responses.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const contentType = require( 'content-type' ) 4 | const jsonBigIntParse = require( 'json-bigint' )({ storeAsString: true }).parse 5 | 6 | const propagatedError = require( './propagated-error' ) 7 | 8 | const isJson = headers => { 9 | try { 10 | 11 | const contentTypeHeader = headers[ 'content-type' ] 12 | 13 | if ( !contentTypeHeader ) { 14 | return false 15 | } 16 | 17 | const type = ( contentType.parse( contentTypeHeader )).type 18 | 19 | return ( type === 'application/json' ) 20 | 21 | } catch ( e ) { 22 | throw propagatedError( 'could not tell if response is json', e ) 23 | } 24 | } 25 | 26 | module.exports = ( responseBody, headers ) => { 27 | 28 | if ( isJson( headers )) { 29 | 30 | try { 31 | // Some of the ring endpoints return an empty response (but not a 204 status code) 32 | // while claiming to be JSON. In this case, trying to parse the empty string as 33 | // json will fail. To avoid the failure, return an empty object 34 | if ( !responseBody.length ) { 35 | return {} 36 | } 37 | 38 | return jsonBigIntParse( responseBody ) 39 | } catch ( e ) { 40 | throw propagatedError( `invalid json in response: ${responseBody}`, e ) 41 | } 42 | } else { 43 | return responseBody 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /poll-for-dings.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SECOND = require( 'time-constants' ).SECOND 4 | 5 | // polling every five seconds seems to be the rate the ring app uses: 6 | const POLL_FREQUENCY = 5 * SECOND 7 | 8 | module.exports = bottle => bottle.service( 'pollForDings', pollForDings, 9 | 'getActiveDings', 10 | 'events', 11 | 'logger' 12 | ) 13 | 14 | function pollForDings( getActiveDings, events, logger ) { 15 | 16 | const poll = async() => { 17 | const dings = await getActiveDings() 18 | 19 | logger( `polling found ${dings.length} active dings`, dings ) 20 | 21 | dings.forEach( ding => events.emit( 'activity', ding )) 22 | } 23 | 24 | let interval 25 | 26 | return { 27 | start: () => { 28 | logger( `will poll for dings every ${POLL_FREQUENCY}ms` ) 29 | interval = setInterval( poll, POLL_FREQUENCY ) 30 | }, 31 | stop: () => clearInterval( interval ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /propagated-error.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { yellow } = require( 'colors/safe' ) 4 | const causePointer = yellow( '--caused-by->' ) 5 | 6 | const combinedMessage = error => { 7 | if ( error.isPropagatedError ) { 8 | return `${error.ownMessage} ${causePointer} ${combinedMessage( error.causedBy )}` 9 | } else { 10 | return error.message 11 | } 12 | } 13 | 14 | module.exports = ( message, causedBy ) => { 15 | 16 | const nativeMessage = combinedMessage({ 17 | isPropagatedError: true, 18 | ownMessage: message, 19 | causedBy 20 | }) 21 | 22 | const error = new Error( nativeMessage ) 23 | 24 | error.ownMessage = message 25 | error.causedBy = causedBy 26 | error.isPropagatedError = true 27 | 28 | return error 29 | } 30 | -------------------------------------------------------------------------------- /rest-client.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const API_VERSION = 11 4 | const hardware_id = require( 'crypto' ).randomBytes( 16 ).toString( 'hex' ) 5 | 6 | const axios = require( 'axios' ) 7 | const delay = require( 'timeout-as-promise' ) 8 | const isObject = require( 'lodash.isobject' ) 9 | const compose = require( 'lodash.compose' ) 10 | const propagatedError = require( './propagated-error' ) 11 | const colors = require( 'colors/safe' ) 12 | 13 | const loggableObject = color => object => colors[ color ]( JSON.stringify( object, null, 4 )) 14 | 15 | module.exports = bottle => bottle.service( 'restClient', restClient, 16 | 'apiUrls', 17 | 'options', 18 | 'logger' 19 | ) 20 | function restClient( apiUrls, { email, password }, logger ) { 21 | 22 | // axios responses are too verbose to log, make a smaller format by 23 | // extracting some properties 24 | const loggableResponseFields = ({ status, statusText, headers, data }) => ({ 25 | status, statusText, headers, data 26 | }) 27 | const loggableResponse = compose( loggableObject( 'cyan' ), loggableResponseFields ) 28 | const loggableRequest = loggableObject( 'yellow' ) 29 | 30 | const ringRequest = async({ method, url, headers = {}, data, params = {} }) => { 31 | 32 | const axiosParams = { 33 | transformResponse: [ require( './parse-ring-json-responses' ) ], 34 | method, 35 | url, 36 | params, 37 | data: isObject( data ) 38 | ? JSON.stringify( data ) 39 | : data, 40 | headers: isObject( data ) 41 | ? { 42 | 'content-type': 'application/json', 43 | 'content-length': JSON.stringify( data ).length, 44 | ...headers 45 | } 46 | : headers 47 | } 48 | 49 | try { 50 | logger( 'making request:', loggableRequest( axiosParams )) 51 | 52 | const axiosResponse = await axios( axiosParams ) 53 | 54 | logger( 'got response:', loggableResponse( axiosResponse ), null, 4 ) 55 | return axiosResponse.data 56 | } catch ( e ) { 57 | const response = e.response 58 | 59 | logger( colors.red( 'http request errored' ), e.response ? e.response.data : 'without response' ) 60 | 61 | let message = `Request to ring API at ${url} failed` 62 | 63 | if ( response.data && response.data.error_description ) { 64 | // oauth errors 65 | message = `${message} with "${response.data.error_description}"` 66 | } else if ( response.data && response.data.error ) { 67 | // ring client api errors 68 | message = `${message} with "${response.data.error}"` 69 | } 70 | 71 | throw propagatedError( message, e ) 72 | } 73 | } 74 | 75 | const authenticate = async() => { 76 | 77 | const authReqBody = { 78 | client_id: 'ring_official_android', 79 | grant_type: 'password', 80 | password: password, 81 | scope: 'client', 82 | username: email 83 | } 84 | const reqData = { 85 | url: apiUrls.auth(), 86 | data: authReqBody, 87 | method: 'POST' 88 | } 89 | 90 | let authToken 91 | try { 92 | authToken = ( await ringRequest( reqData )).access_token 93 | } catch ( requestError ) { 94 | throw propagatedError( `could not get auth token for user ${email}`, requestError ) 95 | } 96 | 97 | logger( 'got auth token', colors.green( authToken )) 98 | 99 | return authToken 100 | } 101 | 102 | const establishSession = async authToken => { 103 | const sessionReqBody = { 104 | device: { 105 | hardware_id: hardware_id, 106 | metadata: { 107 | api_version: API_VERSION, 108 | }, 109 | os: 'android' 110 | } 111 | } 112 | const sessionReqHeaders = { 113 | Authorization: `Bearer ${authToken}` 114 | } 115 | const requestData = { 116 | url: apiUrls.session(), 117 | data: sessionReqBody, 118 | headers: sessionReqHeaders, 119 | method: 'POST', 120 | params: { api_version: API_VERSION } 121 | } 122 | 123 | let sessionToken 124 | try { 125 | sessionToken = ( await ringRequest( requestData )).profile.authentication_token 126 | } catch ( requestError ) { 127 | throw propagatedError( `could not get a session token given auth token ${authToken}`, requestError ) 128 | } 129 | 130 | // delay copied from npm module doorbot - not sure what it is for 131 | await delay( 1500 ) 132 | 133 | logger( 'got session token', colors.green( sessionToken )) 134 | 135 | return sessionToken 136 | } 137 | 138 | const authenticatedSession = async() => establishSession( await authenticate()) 139 | 140 | const session = authenticatedSession() 141 | 142 | return { 143 | // await on .session to be sure that are authenticated 144 | session, 145 | 146 | authenticatedRequest: async( method, url ) => { 147 | const sessionToken = await session 148 | 149 | const authFields = { 150 | api_version: API_VERSION, 151 | auth_token: sessionToken 152 | } 153 | 154 | const reqData = { 155 | method, 156 | url, 157 | data: authFields, 158 | params: authFields, 159 | headers: { 'user-agent': 'android:com.ringapp:2.0.67(423)' } 160 | } 161 | 162 | let responseJson 163 | try { 164 | responseJson = await ringRequest( reqData ) 165 | } catch ( e ) { 166 | throw propagatedError( `problem ${method}ing endpoint ${url}`, e ) 167 | } 168 | 169 | if ( responseJson && responseJson.error ) { 170 | throw new Error( `error in API response ${responseJson.error}` ) 171 | } 172 | 173 | return responseJson 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /ring-api-workspace.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | }, 6 | { 7 | "path": "/Users/jimhigson/dev/my-eslint-rules" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /top-level-api.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = bottle => { 4 | 5 | bottle.service( 'api', api, 6 | 'getDevicesList', 7 | 'getHistoryList', 8 | 'getActiveDings', 9 | 'events' 10 | ) 11 | 12 | function api( getDevicesList, getHistoryList, getActiveDings, events ) { 13 | return { 14 | devices: getDevicesList, 15 | history: getHistoryList, 16 | activeDings: getActiveDings, 17 | events 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /wire-up.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require( 'events' ) 2 | 3 | module.exports = options => { 4 | 5 | const bottle = require( 'bottlejs' )() 6 | 7 | bottle.service( 'options', function() { 8 | return options 9 | }) 10 | 11 | require( './api-urls' )( bottle ) 12 | require( './rest-client' )( bottle ) 13 | require( './get-history-list' )( bottle ) 14 | require( './get-live-stream' )( bottle ) 15 | require( './get-devices-list' )( bottle ) 16 | require( './poll-for-dings' )( bottle ) 17 | require( './get-active-dings' )( bottle ) 18 | 19 | bottle.service( 'events', function() { 20 | return new EventEmitter() 21 | }) 22 | bottle.service( 'logger', function() { 23 | return require( 'debug' )( 'ring-api' ) 24 | }) 25 | 26 | require( './top-level-api' )( bottle ) 27 | 28 | return bottle.container 29 | } 30 | --------------------------------------------------------------------------------