UNPKG

meshblu-http

Version:
465 lines (369 loc) 16.9 kB
_ = require 'lodash' url = require 'url' debug = require('debug')('meshblu-http') stableStringify = require 'json-stable-stringify' class MeshbluHttp @SUBSCRIPTION_TYPES = [ 'broadcast' 'sent' 'received' 'config' 'broadcast.received' 'broadcast.sent' 'configure.received' 'configure.sent' 'message.received' 'message.sent' ] constructor: (options={}, @dependencies={}) -> options = _.cloneDeep options { bearerToken uuid token hostname port protocol domain service secure resolveSrv auth @raw @keepAlive @gzip @timeout @serviceName } = options @keepAlive ?= true @gzip ?= true throw new Error 'cannot provide uuid, token, and bearerToken' if (uuid? || token?) && bearerToken? throw new Error 'a uuid is provided but the token is not' if uuid? && !token? && !bearerToken? throw new Error 'a token is provided but the uuid is not' if token? && !uuid? && !bearerToken? auth ?= {username: uuid, password: token} if uuid? && token? auth = { bearer: bearerToken } if bearerToken? {request, @MeshbluRequest, @NodeRSA} = @dependencies @MeshbluRequest ?= require './meshblu-request' @NodeRSA ?= require 'node-rsa' @request = @_buildRequest {request, protocol, hostname, port, service, domain, secure, resolveSrv, auth} authenticate: (callback) => options = @_getDefaultRequestOptions() @request.post "/authenticate", options, (error, response, body) => debug "authenticate", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback createHook: (uuid, type, url, callback) => error = new Error "Hook type not supported. supported types are: #{MeshbluHttp.SUBSCRIPTION_TYPES.join ', '}" return callback error unless type in MeshbluHttp.SUBSCRIPTION_TYPES updateRequest = $addToSet: "meshblu.forwarders.#{type}": type: 'webhook' url: url method: 'POST', generateAndForwardMeshbluCredentials: true @updateDangerously(uuid, updateRequest, callback) createSubscription: ({subscriberUuid, emitterUuid, type}, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback url = @_subscriptionUrl {subscriberUuid, emitterUuid, type} options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.post url, options, (error, response, body) => debug 'createSubscription', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback deleteSubscriptions: ({subscriberUuid, emitterUuid, type}, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback url = "/v2/devices/#{subscriberUuid}/subscriptions" options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers options.json = { emitterUuid, type } @request.delete url, options, (error, response, body) => debug 'deleteSubscriptions', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback deleteSubscription: ({subscriberUuid, emitterUuid, type}, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback url = @_subscriptionUrl {subscriberUuid, emitterUuid, type} options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.delete url, options, (error, response, body) => debug 'deleteSubscription', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback device: (uuid, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_device uuid, metadata, callback _device: (uuid, metadata, callback=->) => options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.get "/v2/devices/#{uuid}", options, (error, response, body) => debug "device", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback devices: (query={}, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_devices query, metadata, callback findAndUpdate: (uuid, params, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} options = @_getDefaultRequestOptions() options.json = params options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.put "/v2/devices/#{uuid}/find-and-update", options, (error, response, body) => debug "update", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback generateAndStoreToken: (uuid, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} params = true @_generateAndStoreToken uuid, params, metadata, callback generateAndStoreTokenWithOptions: (uuid, params, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_generateAndStoreToken uuid, params, metadata, callback _generateAndStoreToken: (uuid, params, metadata, callback=->) => options = @_getDefaultRequestOptions() options.json = params options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.post "/devices/#{uuid}/tokens", options, (error, response, body) => debug "generateAndStoreToken", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback generateKeyPair: => key = new @NodeRSA() key.generateKeyPair() privateKey: key.exportKey('private'), publicKey: key.exportKey('public') getServerPublicKey: (callback) => options = @_getDefaultRequestOptions() @request.get '/publickey', options, (error, response, body) => body = _.get body, 'publicKey' @_handleResponse {error, response, body}, callback healthcheck: (callback=->) => options = @_getDefaultRequestOptions() @request.get '/healthcheck', options, (error, response) => return callback error if error? healthy = response.statusCode == 200 return callback null, healthy, response.statusCode message: (message, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_message message, metadata, callback mydevices: (query={}, callback=->) => options = @_getDefaultRequestOptions() options.qs = query @request.get "/mydevices", options, (error, response, body) => debug "mydevices", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback publicKey: (deviceUuid, callback=->) => options = @_getDefaultRequestOptions() @request.get "/devices/#{deviceUuid}/publickey", options, (error, response, body) => debug "publicKey", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback register: (device, callback=->) => options = @_getDefaultRequestOptions() options.json = device @request.post "/devices", options, (error, response, body={}) => debug "register", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback resetToken: (deviceUuid, callback=->) => options = @_getDefaultRequestOptions() url = "/devices/#{deviceUuid}/token" @request.post url, options, (error, response, body) => debug 'resetToken', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback revokeToken: (deviceUuid, deviceToken, callback=->) => options = @_getDefaultRequestOptions() @request.delete "/devices/#{deviceUuid}/tokens/#{deviceToken}", options, (error, response, body={}) => debug "revokeToken", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback revokeTokenByQuery: (deviceUuid, query, callback=->) => options = @_getDefaultRequestOptions() options.qs = query @request.delete "/devices/#{deviceUuid}/tokens", options, (error, response, body={}) => debug "revokeToken", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback search: (query, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers options.json = query @request.post "/search/devices", options, (error, response, body) => debug 'search', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback searchTokens: (query, metadata, callback) => options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers options.json = query @request.post "/search/tokens", options, (error, response, body) => debug 'searchTokens', error, JSON.stringify(body) @_handleResponse {error, response, body}, callback setPrivateKey: (privateKey) => @privateKey = new @NodeRSA privateKey sign: (data) => @privateKey.sign(stableStringify(data)).toString('base64') subscriptions: (uuid, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_subscriptions uuid, metadata, callback unregister: (device, callback=->) => options = @_getDefaultRequestOptions() @request.delete "/devices/#{device.uuid}", options, (error, response, body) => debug "unregister", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback update: (uuid, params, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_update uuid, params, metadata, callback updateDangerously: (uuid, params, rest...) => [callback] = rest [metadata, callback] = rest if _.isPlainObject callback metadata ?= {} @_updateDangerously uuid, params, metadata, callback _updateDangerously: (uuid, params, metadata, callback=->) => options = @_getDefaultRequestOptions() options.json = params options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.put "/v2/devices/#{uuid}", options, (error, response, body) => debug "updated", uuid, JSON.stringify(params) debug "update", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback verify: (message, signature) => @privateKey.verify stableStringify(message), signature, 'utf8', 'base64' whoami: (callback=->) => options = @_getDefaultRequestOptions() @request.get "/v2/whoami", options, (error, response, body) => debug "whoami", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback _assertNoSrv: ({service, domain, secure}) => throw new Error('domain property only applies when resolveSrv is true') if domain? throw new Error('service property only applies when resolveSrv is true') if service? throw new Error('secure property only applies when resolveSrv is true') if secure? _assertNoUrl: ({protocol, hostname, port}) => throw new Error('protocol property only applies when resolveSrv is false') if protocol? throw new Error('hostname property only applies when resolveSrv is false') if hostname? throw new Error('port property only applies when resolveSrv is false') if port? _buildRequest: ({request, protocol, hostname, port, service, domain, secure, resolveSrv, auth}) => return request if request? return @_buildSrvRequest({protocol, hostname, port, service, domain, secure, auth}) if resolveSrv return @_buildUrlRequest({protocol, hostname, port, service, domain, secure, auth}) _buildSrvRequest: ({protocol, hostname, port, service, domain, secure, auth}) => @_assertNoUrl({protocol, hostname, port}) service ?= 'meshblu' domain ?= 'octoblu.com' secure ?= true request = {} request.auth = auth if auth? request.timeout = @timeout if @timeout? return new @MeshbluRequest {resolveSrv: true, service, domain, secure, request} _buildUrlRequest: ({protocol, hostname, port, service, domain, secure, auth}) => @_assertNoSrv({service, domain, secure}) protocol ?= 'https' hostname ?= 'meshblu.octoblu.com' port ?= 443 try port = parseInt port request = {} request.auth = auth if auth? request.timeout = @timeout if @timeout? return new @MeshbluRequest {resolveSrv: false, protocol, hostname, port, request } _devices: (query, metadata, callback=->) => options = @_getDefaultRequestOptions() options.qs = query options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.get "/v2/devices", options, (error, response, body) => debug "devices", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback _getDefaultHeaders: => return unless @serviceName? return { 'x-meshblu-service-name': @serviceName } _getDefaultRequestOptions: => headers = @_getDefaultHeaders() options = { json: true forever: @keepAlive gzip: @gzip } options.headers = headers if headers? return options _getMetadataHeaders: (metadata) => _.transform metadata, (newMetadata, value, key) => kebabKey = _.kebabCase key newMetadata["x-meshblu-#{kebabKey}"] = @_possiblySerializeHeaderValue value return true , {} _getRawRequestOptions: => headers = @_getDefaultHeaders() ? {} headers['content-type'] = 'application/json' return { json: false headers } _handleError: ({message,code,response}, callback) => message ?= 'Unknown Error Occurred' response = response?.toJSON?() debug 'handling error', JSON.stringify({ message, code, response }, null, 2) error = @_userError code, message, response callback error _handleResponse: ({error, response, body}, callback) => return @_handleError { message: error.message, code: error?.code, response }, callback if error? code = response?.statusCode if response?.headers?['x-meshblu-error']? error = JSON.parse response.headers['x-meshblu-error'] return @_handleError { message: error.message, response, code }, callback if body?.error? return @_handleError { message: body.error, response, code }, callback if code >= 400 message = body if _.isString body message ?= "Invalid Response Code #{code}" return @_handleError { message, response, code }, callback callback null, body _message: (message, metadata, callback=->) => if @raw options = @_getRawRequestOptions() options.body = message else options = @_getDefaultRequestOptions() options.json = message options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.post "/messages", options, (error, response, body) => debug "message", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback # 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 _subscriptions: (uuid, metadata, callback=->) => options = @_getDefaultRequestOptions() options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.get "/v2/devices/#{uuid}/subscriptions", options, (error, response, body) => debug "subscriptions", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback _subscriptionUrl: (options) => {subscriberUuid, emitterUuid, type} = options "/v2/devices/#{subscriberUuid}/subscriptions/#{emitterUuid}/#{type}" _update: (uuid, params, metadata, callback=->) => options = @_getDefaultRequestOptions() options.json = params options.headers = _.extend {}, @_getMetadataHeaders(metadata), options.headers @request.patch "/v2/devices/#{uuid}", options, (error, response, body) => debug "update", error, JSON.stringify(body) @_handleResponse {error, response, body}, callback _userError: (code, message, response) => error = new Error message error.code = code error.response = response if response? error module.exports = MeshbluHttp