1 | _ = require 'lodash'
|
2 | debug = require('debug')('nanocyte-configuration-generator')
|
3 | UUID = require 'uuid'
|
4 | ChannelConfig = require './channel-config'
|
5 | MeshbluHttp = require 'meshblu-http'
|
6 | NodeRegistryDownloader = require './node-registry-downloader'
|
7 | SimpleBenchmark = require 'simple-benchmark'
|
8 |
|
9 |
|
10 | Downloader = new NodeRegistryDownloader
|
11 |
|
12 | DEFAULT_REGISTRY_URL = 'https://s3-us-west-2.amazonaws.com/nanocyte-registry/latest/registry.json'
|
13 | METRICS_DEVICE_ID = 'f952aacb-5156-4072-bcae-f830334376b1'
|
14 |
|
15 | VIRTUAL_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 |
|
44 | class 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
|
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 |
|
360 | module.exports = ConfigurationGenerator
|