etcher/lib/shared/sdk/index.js
Juan Cruz Viotti 63528ce8f3 fix(SDK): disable usbboot adapter on GNU/Linux (#1841)
Various GNU/Linux distributions require root access to be able to open
USB devices. This means that Etcher would need to be ran as root, which
is now not possible on Wayland based systems.

We declare usbboot as unsupported on GNU/Linux from the time being, but
we are currently working on a solution.

Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
2017-11-14 18:00:01 +01:00

271 lines
5.8 KiB
JavaScript

/*
* Copyright 2017 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict'
const EventEmitter = require('events')
const _ = require('lodash')
const SDK = module.exports
const debug = require('debug')('sdk')
const os = require('os')
debug.enabled = true
/**
* @summary The list of loaded adapters
* @type {Object[]}
* @constant
*/
const ADAPTERS = [
require('./standard')
]
// We don't support usbboot on GNU/Linux yet, given
// that some distributions require root permissions
// to open USB devices.
if (os.platform() !== 'linux') {
ADAPTERS.push(require('./usbboot'))
}
/**
* @summary Initialised adapters
* @type {Object<String,Adapter>}
* @constant
*/
SDK.adapters = _.reduce(ADAPTERS, (adapters, Adapter) => {
adapters[Adapter.id] = new Adapter()
return adapters
}, {})
/* eslint-disable lodash/prefer-lodash-method */
/**
* Adapter Scanner
* @class Scanner
*/
SDK.Scanner = class Scanner extends EventEmitter {
/**
* @summary Adapter Scanner constructor
* @param {Object<String,Object>} [options] - device adapter options
* @param {Object} [options.adapters] - map of external device adapters
* @example
* new SDK.Scanner({
* standard: { ... },
* usbboot: { ... }
* })
*/
constructor (options = {}) {
// Inherit from EventEmitter
super()
this.options = options
this.isScanning = false
this.adapters = new Map()
// Bind event handlers to own context to facilitate
// removing listeners by reference
this.onDevices = this.onDevices.bind(this)
this.onError = this.onError.bind(this)
this.init()
}
/**
* @summary Initialize adapters
* @private
* @example
* // Only to be used internally
* this.init()
*/
init () {
debug('scanner:init', this)
_.each(_.keys(this.options), (adapterId) => {
const adapter = SDK.adapters[adapterId] ||
_.get(this.options, [ 'adapters', adapterId ])
if (_.isNil(adapter)) {
console.warn(`Unknown adapter "${adapterId}"`)
return
}
this.subscribe(adapter)
})
}
/**
* @summary Event handler for adapter's "device" events
* @private
* @example
* adapter.on('devices', this.onDevices)
*/
onDevices () {
const devices = []
this.adapters.forEach((adapter) => {
devices.push(...adapter.devices)
})
this.emit('devices', devices)
}
/**
* @summary Event handler for adapter's "error" events
* @param {Error} error - error
* @private
* @example
* adapter.on('error', this.onError)
*/
onError (error) {
this.emit('error', error)
}
/**
* @summary Start scanning for devices
* @public
* @returns {SDK.Scanner}
* @example
* scanner.start()
*/
start () {
debug('start', !this.isScanning)
if (this.isScanning) {
return this
}
this.adapters.forEach((adapter) => {
const options = this.options[adapter.id]
/**
* @summary Run a scan with an adapter
* @function
* @private
* @example
* runScan()
*/
const runScan = () => {
adapter.scan(options, () => {
if (this.isScanning) {
setTimeout(runScan, SDK.Scanner.MIN_SCAN_DELAY)
}
})
}
adapter
.on('devices', this.onDevices)
.on('error', this.onError)
runScan()
})
this.emit('start')
this.isScanning = true
return this
}
/**
* @summary Stop scanning for devices
* @public
* @returns {SDK.Scanner}
* @example
* scanner.stop()
*/
stop () {
debug('stop', this.isScanning)
if (!this.isScanning) {
return this
}
this.adapters.forEach((adapter) => {
adapter.removeListener('devices', this.onDevices)
adapter.removeListener('error', this.onError)
})
this.isScanning = false
this.emit('stop')
return this
}
/**
* @summary Subscribe to an adapter
* @public
* @param {Adapter} adapter - device adapter
* @returns {SDK.Scanner}
* @example
* scanner.subscribe(adapter)
*/
subscribe (adapter) {
debug('subscribe', adapter)
if (this.adapters.get(adapter.id)) {
throw new Error(`Scanner: Already subscribed to ${adapter.id}`)
}
this.adapters.set(adapter.id, adapter)
this.emit('subscribe', adapter)
return this
}
/**
* @summary Unsubscribe from an adapter
* @public
* @param {Adapter} adapter - device adapter
* @returns {SDK.Scanner}
* @example
* scanner.unsubscribe(adapter)
* // OR
* scanner.unsubscribe('adapterName')
*/
unsubscribe (adapter) {
debug('unsubscribe', adapter)
const instance = _.isString(adapter) ? this.adapters.get(adapter) : this.adapters.get(adapter.id)
if (_.isNil(instance)) {
// Not subscribed
return this
}
instance.removeListener('devices', this.onDevices)
instance.removeListener('error', this.onError)
this.adapters.delete(instance.id)
this.emit('unsubscribe', adapter)
return this
}
}
/**
* @summary Minimum delay between scans in ms
* @const
* @type {Number}
*/
SDK.Scanner.MIN_SCAN_DELAY = 500
/**
* @summary Create a new Scanner
* @param {Object} [options] - options
* @returns {SDK.Scanner}
* @example
* SDK.createScanner({
* standard: { ... },
* usbboot: { ... }
* })
*/
SDK.createScanner = (options) => {
return new SDK.Scanner(options)
}