UNPKG

11.4 kBtext/coffeescriptView Raw
1_ = require 'lodash'
2debug = require('debug')('nanocyte-configuration-generator')
3UUID = require 'uuid'
4ChannelConfig = require './channel-config'
5MeshbluHttp = require 'meshblu-http'
6NodeRegistryDownloader = require './node-registry-downloader'
7SimpleBenchmark = require 'simple-benchmark'
8
9# outside the class so cache is maintained
10Downloader = new NodeRegistryDownloader
11
12DEFAULT_REGISTRY_URL = 'https://s3-us-west-2.amazonaws.com/nanocyte-registry/latest/registry.json'
13METRICS_DEVICE_ID = 'f952aacb-5156-4072-bcae-f830334376b1'
14
15VIRTUAL_NODES =
16 'engine-input':
17 config: {}
18 data: {}
19 'engine-output':
20 config: {}
21 data: {}
22 'engine-data':
23 config: {}
24 data: {}
25 'engine-debug':
26 config: {}
27 data: {}
28 'engine-pulse':
29 config: {}
30 data: {}
31 'router':
32 config: {}
33 data: {}
34 'engine-start':
35 config: {}
36 data: {}
37 'engine-stop':
38 config: {}
39 data: {}
40 'subscribe-devices':
41 config: {}
42 data: {}
43
44class ConfigurationGenerator
45 constructor: (options, dependencies={}) ->
46 {@registryUrl, @meshbluJSON, @metricsDeviceId} = options
47 @registryUrl ?= DEFAULT_REGISTRY_URL
48 @metricsDeviceId ?= METRICS_DEVICE_ID
49
50 {@request, @channelConfig} = dependencies
51 @request ?= require 'request'
52 @benchmark = new SimpleBenchmark label: "nanocyte-configuration-generator-#{@meshbluJSON?.uuid}"
53 @channelConfig ?= new ChannelConfig
54 accessKeyId: options.accessKeyId
55 secretAccessKey: options.secretAccessKey
56
57 @meshbluHttp = new MeshbluHttp @meshbluJSON
58
59 Downloader.setOptions {@registryUrl}
60
61 configure: (options, callback=->) =>
62 {flowData, flowToken, deploymentUuid} = options
63
64 @channelConfig.update (error) =>
65 debug 'channelConfig.fetch', @benchmark.toString()
66 return callback error if error?
67
68 @_getNodeRegistry (error, nodeRegistry) =>
69 debug 'getNodeRegistry', @benchmark.toString()
70 return callback error if error?
71
72 flowMetricNode =
73 id: @_generateFlowMetricId()
74 category: 'flow-metrics'
75 flowUuid: flowData.flowId
76 deviceId: @metricsDeviceId
77 deploymentUuid: deploymentUuid
78
79 flowData.nodes ?= []
80 flowData.nodes.push flowMetricNode
81
82 flowNodes = _.indexBy flowData.nodes, 'id'
83
84 flowConfig = _.mapValues flowNodes, (nodeConfig) =>
85 nodeConfig.nanocyte ?= {}
86 nodeConfig.nanocyte.nonce = @_generateNonce()
87
88 config: nodeConfig
89 data: {}
90
91 flowConfig = _.assign flowConfig, _.cloneDeep(VIRTUAL_NODES)
92 instanceMap = @_generateInstances flowData.links, flowConfig, nodeRegistry
93
94 _.each instanceMap, (instanceConfig, instanceId) =>
95 {config,data} = flowConfig[instanceConfig.nodeUuid]
96
97 config = @_legacyConversion _.cloneDeep config # prevent accidental mutation
98 config.templateOriginalMessage = instanceConfig.templateOriginalMessage
99 getSetConfig = @_mutilateGetSetNodes uuid: flowData.flowId, token: flowToken, config
100
101 channelApiMatch = @channelConfig.get config.type
102 defaultConfig = {}
103 defaultConfig.channelApiMatch = channelApiMatch if channelApiMatch?
104 config = _.defaultsDeep defaultConfig, config, getSetConfig
105
106 flowConfig[instanceId] = {config: config, data: data}
107
108 debug 'instanceMap', @benchmark.toString()
109
110 links = @_buildLinks(flowData.links, instanceMap)
111 debug 'buildLinks', @benchmark.toString()
112 flowConfig.router.config = links
113
114 flowConfig['engine-data'].config = @_buildNodeMap instanceMap
115 flowConfig['engine-pulse'].config = @_buildNodeMap instanceMap
116 flowConfig['engine-debug'].config = @_buildNodeMap instanceMap
117 flowConfig['engine-input'].config = @_buildMeshblutoNodeMap flowConfig, instanceMap
118 flowConfig['subscribe-devices'].config = @_getSubscribeDevices flowNodes
119
120 @_buildEngineOutputConfig {flowData, flowToken}, (error, config) =>
121 debug 'buildEngineOutputConfig', @benchmark.toString()
122 return callback error if error?
123 flowConfig['engine-output'].config = config
124
125 flowStopConfig = _.cloneDeep flowConfig
126
127 engineStopLinks = flowConfig['router']['config']['engine-stop']?.linkedTo
128 engineStopLinks ?= []
129
130 stopRouterConfig = _.pick flowConfig['router']['config'], 'engine-stop', 'engine-output', engineStopLinks...
131 flowStopConfig['router']['config'] = stopRouterConfig
132 debug 'calling back', @benchmark.toString()
133
134 callback null, flowConfig, flowStopConfig
135
136 _buildEngineOutputConfig: ({flowData, flowToken}, callback) =>
137 config = _.extend {forwardMetadataTo: []}, @meshbluJSON, uuid: flowData.flowId, token: flowToken
138
139 deviceUuids = @_getDeviceUuids flowData.nodes
140 return callback null, config if _.isEmpty deviceUuids
141 query =
142 uuid: $in: deviceUuids
143 'octoblu.flow.forwardMetadata': true
144
145 @meshbluHttp.search query, {}, (error, devices) =>
146 return callback error if error?
147 config.forwardMetadataTo = _.map devices, 'uuid'
148 callback null, config
149
150 _buildNodeMap: (flowNodeMap) =>
151 _.mapValues flowNodeMap, (flowNode) =>
152 nodeId: flowNode.nodeUuid
153
154 _buildMeshblutoNodeMap: (flowConfig, instanceMap) =>
155 inputInstances = _.where instanceMap, linkedToInput: true
156
157 nodeMap = {}
158 _.each inputInstances, (instance) =>
159 nodeConfig = flowConfig[instance.nodeUuid]
160 nodeMap[nodeConfig.config.uuid] ?= []
161
162 alias = nodeConfig.config.alias
163 aNodeMap = nodeId: instance.nodeUuid
164 aNodeMap.alias = alias if alias?
165
166 nodeMap[nodeConfig.config.uuid].push aNodeMap
167
168 return nodeMap
169
170 _generateInstances: (links, flowNodes, nodeRegistry) =>
171 instanceMap = {}
172 _.each flowNodes, (nodeConfig, nodeUuid) =>
173 config = nodeConfig.config ? {}
174 nanocyteConfig = config.nanocyte ? {}
175
176 type = config.category
177 type = config.type.replace('operation:', '') if type == 'operation'
178 nodeFromRegistry = nodeRegistry[type] ? {}
179
180 composedOf = _.cloneDeep(nodeFromRegistry.composedOf) ? {}
181
182 linkedToData = _.detect composedOf, (value, key) =>
183 value.linkedToData == true
184
185 composedOf = @_addDebug(composedOf) if config.debug?
186
187 transactionGroupId = @_generateTransactionGroupId() if linkedToData?
188
189 _.each composedOf, (template, templateId) =>
190 instanceId = @_generateInstanceId()
191 composedConfig = _.cloneDeep template
192 composedConfig.nodeUuid = nodeUuid
193 composedConfig.templateId = templateId
194 composedConfig.transactionGroupId = transactionGroupId if linkedToData?
195
196 instanceMap[instanceId] = composedConfig
197
198 return instanceMap
199
200 _addDebug: (composedOf) =>
201 composedOf = @_addInputDebug composedOf
202 composedOf = @_addOutputDebug composedOf
203 return composedOf
204
205 _addInputDebug: (composedOf) =>
206 composedOf = _.cloneDeep composedOf
207
208 debugInput =
209 type: "nanocyte-component-pass-through"
210 debug: true
211 linkedTo: []
212 linkedToPrev: true
213
214 composedOf['debug-input'] = debugInput
215
216 composedOf
217
218 _addOutputDebug: (composedOf) =>
219 _.mapValues composedOf, (node) =>
220 node.debug = true if node.linkedToNext
221 node
222
223 _getNodeRegistry: (callback) =>
224 Downloader.update callback
225
226 _getSubscribeDevices: (flowNodes) =>
227 return 'broadcast.sent': @_getDeviceUuids(flowNodes)
228
229 _getDeviceUuids: (flowNodes) =>
230 devices = _.where flowNodes, category: 'device'
231 _.pluck devices, 'uuid'
232
233 _getDevicesThatWantFlowMetadata: (flowConfig) =>
234 devices = _.where flowConfig, category: 'device', 'meshblu.flow.forwardMetadata': true
235 return broadcast: _.pluck devices, 'uuid'
236
237 _buildLinks: (links, instanceMap) =>
238 result = {}
239 _.each instanceMap, (config, instanceId) =>
240 nodeLinks = _.filter links, from: config.nodeUuid
241 templateLinks = config.linkedTo
242 linkedTo = []
243
244 if config.linkedToInput
245 result[config.nodeUuid] ?=
246 type: 'engine-input'
247 linkedTo: []
248 result[config.nodeUuid].linkedTo.push instanceId
249
250 if config.linkedFromStart
251 result['engine-start'] ?=
252 type: 'engine-start'
253 linkedTo: []
254 result['engine-start'].linkedTo.push instanceId
255
256 if config.linkedFromStop
257 result['engine-stop'] ?=
258 type: 'engine-stop'
259 linkedTo: []
260 result['engine-stop'].linkedTo.push instanceId
261
262 if config.linkedToNext
263 linkUuids = _.pluck nodeLinks, 'to'
264 _.each instanceMap, (data, key) =>
265 if _.contains linkUuids, data.nodeUuid
266 linkedTo.push key if data.linkedToPrev
267
268 _.each config.linkedTo, (templateLinkId) =>
269 _.each instanceMap, (data, key) =>
270 if data.nodeUuid == config.nodeUuid && data.templateId == templateLinkId
271 linkedTo.push key
272
273 linkedTo.push 'engine-output' if config.linkedToOutput
274 linkedTo.push 'engine-pulse' if config.linkedToNext || config.linkedToPulse || config.linkedToOutput
275 linkedTo.push 'engine-data' if config.linkedToData
276 linkedTo.push 'engine-debug' if config.debug
277
278 result[instanceId] =
279 type: config.type
280 linkedTo: linkedTo
281 linkedToNext: config.linkedToNext
282
283 result[instanceId].transactionGroupId = config.transactionGroupId if config.transactionGroupId?
284
285 result['engine-output'] =
286 type: 'engine-output'
287 linkedTo: []
288 result['engine-debug'] =
289 type: 'engine-debug'
290 linkedTo: []
291 result['engine-pulse'] =
292 type: 'engine-pulse'
293 linkedTo: []
294 result['engine-data'] =
295 type: 'engine-data'
296 linkedTo: []
297
298 return result
299
300 _legacyConversion: (config) =>
301 if config.type == 'operation:debounce'
302 config.timeout = config.interval
303 delete config.interval
304 if config.type == 'operation:throttle'
305 config.repeat = config.interval
306 delete config.interval
307 if config.type == 'operation:delay'
308 config.fireOnce = true
309 config.noUnsubscribe = true
310
311 return config
312
313 _mutilateGetSetNodes: (options, template) =>
314 return {} unless template.type == 'operation:get-key' || template.type == 'operation:set-key'
315
316 {uuid, token} = options
317
318 bearerToken = new Buffer("#{uuid}:#{token}").toString('base64')
319
320 {host,protocol,port} = @meshbluJSON
321 host ?= 'meshblu.octoblu.com:443'
322 if host == 'meshblu-messages.octoblu.com:443'
323 host = 'meshblu.octoblu.com:443'
324 port ?= 443
325 protocol ?= 'http'
326 protocol = 'https' if parseInt(port) == 443
327
328 config =
329 bodyEncoding: 'json'
330 url: "#{protocol}://#{host}/v2/devices/#{uuid}"
331 method: 'GET'
332 headerKeys: [
333 'Content-Type'
334 'Authorization'
335 ]
336 headerValues: [
337 'application/json'
338 "Bearer #{bearerToken}"
339 ]
340
341 if template.type == 'operation:set-key'
342 config.method = 'PATCH'
343 config.bodyKeys = [ 'data.{{msg.key}}' ]
344 config.bodyValues = [ '{{msg.value}}' ]
345
346 return config
347
348 _generateFlowMetricId: =>
349 UUID.v4()
350
351 _generateInstanceId: =>
352 UUID.v4()
353
354 _generateNonce: =>
355 UUID.v4()
356
357 _generateTransactionGroupId: =>
358 UUID.v4()
359
360module.exports = ConfigurationGenerator