meshblu-http
Version:
Meshblu HTTP API
302 lines (231 loc) • 11.9 kB
text/coffeescript
_ = require 'lodash'
debug = require('debug')('meshblu-http')
stableStringify = require 'json-stable-stringify'
class MeshbluHttp
constructor: (options={}, @dependencies={}) ->
options = _.defaults(_.cloneDeep(options), port: 443, server: 'meshblu.octoblu.com')
{@uuid, @token, @server, @port, @protocol, @auth, @raw} = options
@protocol = null if @protocol == 'websocket'
try
@port = parseInt @port
catch e
@protocol ?= 'http'
@protocol = 'https' if @port == 443
@urlBase = "#{@protocol}://#{@server}:#{@port}"
@request = @dependencies.request ? require 'request'
@NodeRSA = @dependencies.NodeRSA ? require 'node-rsa'
getDefaultRequestOptions: =>
_.extend json: true, @getAuthRequestOptions()
getRawRequestOptions: =>
headers = 'content-type' : 'application/json'
_.extend json: false, headers: headers, @getAuthRequestOptions()
getAuthRequestOptions: =>
return auth: @auth if @auth?
return {} unless @uuid && @token
auth:
user: @uuid
pass: @token
createSubscription: (options, callback) =>
url = @_subscriptionUrl options
requestOptions = @getDefaultRequestOptions()
@request.post url, requestOptions, (error, response, body) =>
return callback @_userError(response.statusCode, body?.error ? 'Unknown Error Occurred') if response.statusCode != 204
callback()
deleteSubscription: (options, callback) =>
url = @_subscriptionUrl options
requestOptions = @getDefaultRequestOptions()
@request.delete url, requestOptions, (error, response, body) =>
return callback @_userError(response.statusCode, body?.error ? 'Unknown Error Occurred') if response.statusCode != 204
callback()
_subscriptionUrl: (options) =>
{subscriberUuid, emitterUuid, type} = options
"#{@urlBase}/v2/devices/#{subscriberUuid}/subscriptions/#{emitterUuid}/#{type}"
device: (deviceUuid, callback=->) =>
options = @getDefaultRequestOptions()
@request.get "#{@urlBase}/v2/devices/#{deviceUuid}", options, (error, response, body) =>
debug "device", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode != 200
callback null, body
search: (query, metadata, callback) =>
options = @getDefaultRequestOptions()
options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers
options.json = query
@request.post "#{@urlBase}/search/devices", options, (error, response, body) =>
return callback @_userError(500, error.message) if error?
return callback null, body
devices: (query={}, rest...) =>
[callback] = rest
[metadata, callback] = rest if _.isPlainObject callback
metadata ?= {}
@_devices query, metadata, callback
_devices: (query, metadata, callback=->) =>
options = @getDefaultRequestOptions()
options.qs = query
options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers
@request.get "#{@urlBase}/devices", options, (error, response, body) =>
debug "devices", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) if body?.error?
callback null, body
mydevices: (query={}, callback=->) =>
options = @getDefaultRequestOptions()
options.qs = query
@request.get "#{@urlBase}/mydevices", options, (error, response, body) =>
debug "mydevices", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) if body?.error?
return callback @_userError(response.statusCode, body) if response.statusCode >= 400
callback null, body
generateAndStoreToken: (deviceUuid, callback=->) =>
options = @getDefaultRequestOptions()
@request.post "#{@urlBase}/devices/#{deviceUuid}/tokens", options, (error, response, body) =>
debug "generateAndStoreToken", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body) if response.statusCode >= 400
callback null, body
generateAndStoreTokenWithOptions: (deviceUuid, tokenOptions, callback=->) =>
options = @getDefaultRequestOptions()
options.json = tokenOptions if tokenOptions?
@request.post "#{@urlBase}/devices/#{deviceUuid}/tokens", options, (error, response, body) =>
debug "generateAndStoreToken", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body) if response.statusCode >= 400
callback null, body
generateKeyPair: =>
key = new @NodeRSA()
key.generateKeyPair()
privateKey: key.exportKey('private'), publicKey: key.exportKey('public')
message: (message, rest...) =>
[callback] = rest
[metadata, callback] = rest if _.isPlainObject callback
metadata ?= {}
@_message message, metadata, callback
_message: (message, metadata, callback=->) =>
if @raw
options = @getRawRequestOptions()
options.body = message
else
options = @getDefaultRequestOptions()
options.json = message
options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers
debug 'POST', "#{@urlBase}/messages", options
@request.post "#{@urlBase}/messages", options, (error, response, body) =>
debug "message", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) if body?.error?
callback null, body
_getMetadataHeaders: (metadata) =>
_.transform metadata, (newMetadata, value, key) =>
newMetadata["x-meshblu-#{key}"] = @_possiblySerializeHeaderValue value
return true
#because request doesn't serialize arrays correctly for headers.
_possiblySerializeHeaderValue: (value) =>
return value if _.isString value
return value if _.isBoolean value
return value if _.isNumber value
return JSON.stringify value
publicKey: (deviceUuid, callback=->) =>
options = @getDefaultRequestOptions()
@request.get "#{@urlBase}/devices/#{deviceUuid}/publickey", options, (error, response, body) =>
debug "publicKey", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response?.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode != 200
callback null, body
register: (device, callback=->) =>
options = @getDefaultRequestOptions()
options.json = device
@request.post "#{@urlBase}/devices", options, (error, response, body={}) =>
debug "register", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode >= 400
callback null, body
resetToken: (deviceUuid, callback=->) =>
options = @getDefaultRequestOptions()
url = "#{@urlBase}/devices/#{deviceUuid}/token"
@request.post url, options, (error, response, body) =>
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) unless response.statusCode == 201
callback null, body
revokeToken: (deviceUuid, deviceToken, callback=->) =>
options = @getDefaultRequestOptions()
@request.del "#{@urlBase}/devices/#{deviceUuid}/tokens/#{deviceToken}", options, (error, response, body={}) =>
debug "revokeToken", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode >= 400
callback null
revokeTokenByQuery: (deviceUuid, query, callback=->) =>
options = @getDefaultRequestOptions()
options.qs = query
@request.del "#{@urlBase}/devices/#{deviceUuid}/tokens", options, (error, response, body={}) =>
debug "revokeToken", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode >= 400
callback null
setPrivateKey: (privateKey) =>
@privateKey = new @NodeRSA privateKey
sign: (data) =>
@privateKey.sign(stableStringify(data)).toString('base64')
unregister: (device, callback=->) =>
options = @getDefaultRequestOptions()
@request.del "#{@urlBase}/devices/#{device.uuid}", options, (error, response, body) =>
debug "unregister", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode >= 400
callback null
update: (uuid, params, rest...) =>
[callback] = rest
[metadata, callback] = rest if _.isPlainObject callback
metadata ?= {}
@_update uuid, params, metadata, callback
_update: (uuid, params, metadata, callback=->) =>
options = @getDefaultRequestOptions()
options.json = params
options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers
@request.patch "#{@urlBase}/v2/devices/#{uuid}", options, (error, response, body) =>
debug "update", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) unless response.statusCode == 204
callback null, body
updateDangerously: (uuid, params, callback=->) =>
options = @getDefaultRequestOptions()
options.json = params
@request.put "#{@urlBase}/v2/devices/#{uuid}", options, (error, response, body) =>
debug "update", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body?.error) unless response.statusCode == 204
callback null, body
verify: (message, signature) =>
@privateKey.verify stableStringify(message), signature, 'utf8', 'base64'
whoami: (callback=->) =>
options = @getDefaultRequestOptions()
@request.get "#{@urlBase}/v2/whoami", options, (error, response, body) =>
debug "whoami", error, body
return callback @_userError(500, error.message) if error?
return callback @_userError(response.statusCode, body.error.message) if body?.error?
return callback @_userError(response.statusCode, body?.message || body) if response.statusCode != 200
callback null, body
createHook: (uuid, type, url, callback) =>
allowedTypes = ['broadcast', 'sent', 'received', 'config']
return callback new Error "Hook type not supported. supported types are: #{allowedTypes.join ', '}" unless type in allowedTypes
updateRequest =
$addToSet:
"meshblu.forwarders.#{type}":
type: 'webhook'
url: url
method: 'POST',
generateAndForwardMeshbluCredentials: true
@updateDangerously(uuid, updateRequest, callback)
_userError: (code, message) =>
error = new Error message
error.code = code
error
module.exports = MeshbluHttp