nutella_framework 0.4.5 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -15
  3. data/VERSION +1 -1
  4. data/framework_components/beacon-cloud-bot/README.md +27 -0
  5. data/framework_components/beacon-cloud-bot/beacon_cloud_bot.rb +154 -0
  6. data/framework_components/beacon-cloud-bot/nutella.json +6 -0
  7. data/framework_components/beacon-cloud-bot/startup +4 -0
  8. data/framework_components/beacon-cloud-interface/LICENSE +21 -0
  9. data/framework_components/beacon-cloud-interface/Readme.md +0 -0
  10. data/framework_components/beacon-cloud-interface/bower.json +29 -0
  11. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/.bower.json +23 -0
  12. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/bower.json +14 -0
  13. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/mqttws31.js +2081 -0
  14. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/readme.md +4 -0
  15. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/.bower.json +37 -0
  16. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/LICENSE +21 -0
  17. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/README.md +15 -0
  18. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/bower.json +28 -0
  19. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/mqtt_client_hello_world.html +23 -0
  20. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/nutella_hello_world.html +52 -0
  21. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/mqtt_client_hello_world.js +14 -0
  22. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/nutella_hello_world.js +38 -0
  23. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/nutella_lib.js +789 -0
  24. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/package.json +30 -0
  25. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/simple-js-mqtt-client.js +428 -0
  26. data/framework_components/beacon-cloud-interface/css/animation.css +17 -0
  27. data/framework_components/beacon-cloud-interface/css/cursor.css +16 -0
  28. data/framework_components/beacon-cloud-interface/css/page_layout.css +73 -0
  29. data/framework_components/beacon-cloud-interface/index.html +157 -0
  30. data/framework_components/beacon-cloud-interface/js/lib/nutella_lib.js +4039 -0
  31. data/framework_components/beacon-cloud-interface/js/react/beacon-add.js +102 -0
  32. data/framework_components/beacon-cloud-interface/js/react/beacon-table.js +73 -0
  33. data/framework_components/beacon-cloud-interface/js/react/beacon.js +97 -0
  34. data/framework_components/beacon-cloud-interface/nutella.json +6 -0
  35. data/framework_components/example_framework_web_interface/index.html +11 -2
  36. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore +10 -0
  37. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml +5 -0
  38. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE +21 -0
  39. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md +27 -0
  40. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js +4039 -0
  41. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map +1 -0
  42. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/browser_hello_world.html +67 -0
  43. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js +51 -0
  44. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js +31 -0
  45. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json +41 -0
  46. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js +19 -0
  47. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js +17 -0
  48. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js +50 -0
  49. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js +279 -0
  50. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js +20 -0
  51. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js +17 -0
  52. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js +50 -0
  53. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js +499 -0
  54. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js +74 -0
  55. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js +130 -0
  56. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js +91 -0
  57. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js +90 -0
  58. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js +51 -0
  59. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js +84 -0
  60. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js +20 -0
  61. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js +327 -0
  62. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js +16 -0
  63. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html +22 -0
  64. data/framework_components/example_framework_web_interface/package.json +15 -0
  65. data/framework_components/{order.json.example → order.json} +0 -0
  66. data/framework_components/runs_list_bot/{app_runs_list_bot.rb → runs_list_bot.rb} +9 -3
  67. data/framework_components/runs_list_bot/startup +1 -1
  68. data/lib/commands/meta/run_command.rb +21 -36
  69. data/lib/commands/start.rb +9 -199
  70. data/lib/commands/util/components_list.rb +68 -0
  71. data/lib/commands/util/components_starter.rb +169 -0
  72. data/nutella_framework.gemspec +109 -47
  73. data/nutella_lib/framework_net.rb +17 -13
  74. metadata +84 -106
@@ -0,0 +1,789 @@
1
+ /******************
2
+ * nutella_lib.js *
3
+ ******************/
4
+
5
+ (function() {
6
+ "use strict";
7
+
8
+ // Establish the root object, `window` in the browser, or `exports` on the server.
9
+ var root = this;
10
+
11
+ // Save the previous value of the `nutella` variable for noConflict().
12
+ var previousNutella = root.NUTELLA;
13
+
14
+ // Internal reference to this library (used below)
15
+ var nutella = {};
16
+
17
+ // Detect if we are in the browser or in node and
18
+ // load the appropriate dependencies
19
+ var isNode;
20
+ var mqtt_lib;
21
+ if (typeof window === 'undefined') {
22
+ isNode = true; // Node
23
+ mqtt_lib = require('mqtt');
24
+ if( typeof mqtt_lib === 'undefined' )
25
+ throw new Error('This MQTT client requires the mqtt library (https://www.npmjs.com/package/mqtt)');
26
+ } else {
27
+ isNode = false; // Browser
28
+ mqtt_lib = root.Paho.MQTT;
29
+ if( typeof mqtt_lib === 'undefined' )
30
+ throw new Error('This MQTT client requires the mqtt-ws library (https://github.com/M2MConnections/mqtt-ws) a wrapper of Paho.js');
31
+
32
+ }
33
+
34
+
35
+
36
+ /**
37
+ * Runs nutella.js in noConflict mode by
38
+ * returning the NUTELLA variable to its previous owner.
39
+ *
40
+ * @return a reference to the nutella object defined by this library.
41
+ */
42
+ nutella.noConflict = function() {
43
+ root.NUTELLA = previousNutella;
44
+ return nutella;
45
+ };
46
+
47
+
48
+
49
+ /**
50
+ * Parses URL parameters.
51
+ * Note: this function is not defined if we are not in the browser.
52
+ *
53
+ * @return {Object} An object containing all the URL query parameters
54
+ */
55
+ if (!isNode) {
56
+ nutella.parseURLParameters = function () {
57
+ var str = location.search;
58
+ var queries = str.replace(/^\?/, '').split('&');
59
+ var searchObject = {};
60
+ for( var i = 0; i < queries.length; i++ ) {
61
+ var split = queries[i].split('=');
62
+ searchObject[split[0]] = split[1];
63
+ }
64
+ return searchObject;
65
+ }
66
+ }
67
+
68
+
69
+ /**
70
+ * Creates a new instance of nutella
71
+ * and initialize it
72
+ * This is a factory method.
73
+ *
74
+ *
75
+ * @param {string} run_id - the run_id this component is launched in
76
+ * @param {string} broker_hostname - the hostname of the broker.
77
+ * @param {string} component_id - the name of this component
78
+ */
79
+ nutella.init = function(run_id, broker_hostname, component_id) {
80
+ if (run_id===undefined || broker_hostname===undefined || component_id=== undefined) {
81
+ console.warn("Couldn't initialize nutella. Make sure you are setting all three the required parameters (runId, broker_hostname, componentId'");
82
+ }
83
+ return new NutellaInstance(run_id, broker_hostname, component_id);
84
+ };
85
+
86
+
87
+
88
+ /**
89
+ * Defines a nutella instance.
90
+ *
91
+ * @param {string} run_id - the run_id this component is launched in
92
+ * @param {string} broker_hostname - the hostname of the broker.
93
+ * @param {string} component_id - the name of this component
94
+ */
95
+ var NutellaInstance = function (run_id, broker_hostname, component_id) {
96
+
97
+ this.mqtt_client = new SimpleMQTTClient(broker_hostname);
98
+ this.runId = run_id;
99
+ this.componentId = component_id;
100
+
101
+ // Initialized the various sub-modules
102
+ this.net = new NetSubModule(this);
103
+ this.persist = new PersistSubModule(this);
104
+ // ... other sub-modules here
105
+ };
106
+
107
+
108
+
109
+ /**
110
+ * Sets the resource id for this instance of nutella
111
+ *
112
+ * @param {string} resource_id - the resource_id associated to this instance of nutella
113
+ */
114
+ NutellaInstance.prototype.setResourceId = function(resource_id){
115
+ this.resourceId = resource_id;
116
+ };
117
+
118
+
119
+
120
+
121
+ //
122
+ // END OF: main nutella protocol module
123
+ //
124
+
125
+
126
+
127
+ // --------------------------------------------------------------------------------------------
128
+ // net sub-module
129
+ // --------------------------------------------------------------------------------------------
130
+
131
+
132
+ var NetSubModule = function(main_nutella) {
133
+ // Store a reference to the main module
134
+ this.main_nutella = main_nutella;
135
+
136
+ // Store the subscriptions and the relative callbacks
137
+ this.subscriptions = [];
138
+ this.callbacks = [];
139
+ };
140
+
141
+
142
+
143
+ /**
144
+ * Subscribes to a channel or filter.
145
+ *
146
+ * @param channel
147
+ * @param callback
148
+ * @param done_callback
149
+ */
150
+ NetSubModule.prototype.subscribe = function(channel, callback, done_callback) {
151
+ // Prevent multiple subscriptions to the same channel
152
+ if (this.subscriptions.indexOf(channel)!==-1)
153
+ throw new Error('You can`t subscribe twice to the same channel');
154
+ // Pad the channel
155
+ var runId = this.main_nutella.runId;
156
+ var new_channel = runId + '/' + channel;
157
+ // Define callbacks
158
+ var mqtt_cb;
159
+ if (isChannelWildcard(channel))
160
+ mqtt_cb = function(mqtt_message, mqtt_channel) {
161
+ // Ignore anything that is not JSON or
162
+ // doesn't comply to the nutella protocol
163
+ try {
164
+ var f = extractFieldsFromMessage(mqtt_message);
165
+ var clean_channel = mqtt_channel.replace(runId+'/', '');
166
+ if (f.type==='publish')
167
+ callback(f.payload, clean_channel, f.componentId, f.resourceId);
168
+ } catch(err) {
169
+ }
170
+ };
171
+ else
172
+ mqtt_cb = function(mqtt_message) {
173
+ // Ignore anything that is not JSON or
174
+ // doesn't comply to the nutella protocol
175
+ try {
176
+ var f = extractFieldsFromMessage(mqtt_message);
177
+ if (f.type==='publish')
178
+ callback(f.payload, f.componentId, f.resourceId);
179
+ } catch(err) {
180
+ }
181
+ };
182
+ // Update subscriptions, callbacks and subscribe
183
+ this.subscriptions.push(channel);
184
+ this.callbacks.push(mqtt_cb);
185
+ this.main_nutella.mqtt_client.subscribe(new_channel, mqtt_cb, done_callback);
186
+ };
187
+
188
+
189
+
190
+ /**
191
+ * Unsubscribes from a channel
192
+ *
193
+ * @param channel
194
+ * @param done_callback
195
+ */
196
+ NetSubModule.prototype.unsubscribe = function(channel, done_callback) {
197
+ // Find index of subscription and retrieve relative callback
198
+ var idx = this.subscriptions.indexOf(channel);
199
+ var cbAtIdx = this.callbacks[idx];
200
+ // Pad the channel
201
+ var new_channel = this.main_nutella.runId + '/' + channel;
202
+ // Unsubscribe
203
+ this.subscriptions.splice(idx, 1);
204
+ this.callbacks.splice(idx, 1);
205
+ this.main_nutella.mqtt_client.unsubscribe(new_channel, cbAtIdx, done_callback);
206
+ };
207
+
208
+
209
+
210
+ /**
211
+ * Publishes a message to a channel
212
+ *
213
+ * @param channel
214
+ * @param message
215
+ */
216
+ NetSubModule.prototype.publish = function(channel, message) {
217
+ // Pad the channel
218
+ var new_channel = this.main_nutella.runId + '/' + channel;
219
+ var m = prepareMessageForPublish(message, this.main_nutella.componentId, this.main_nutella.resourceId);
220
+ this.main_nutella.mqtt_client.publish(new_channel, m);
221
+ };
222
+
223
+
224
+
225
+ /**
226
+ * Sends a request.
227
+ *
228
+ * @param channel
229
+ * @param message
230
+ * @param callback
231
+ * @param done_callback
232
+ */
233
+ NetSubModule.prototype.request = function(channel, message, callback, done_callback) {
234
+ // Handle optional message parameter
235
+ if (typeof message==='function') {
236
+ if (callback!==undefined)
237
+ done_callback = callback;
238
+ callback = message;
239
+ message = undefined;
240
+ }
241
+ // Pad channel
242
+ var new_channel = this.main_nutella.runId + '/' + channel;
243
+ // Prepare request
244
+ var request_id = Math.floor((Math.random() * 100000) + 1).toString();
245
+ var mqtt_request = prepareRequest(message, request_id, this.main_nutella.componentId, this.main_nutella.resourceId);
246
+ // Prepare callback to handle response
247
+ var mqtt_cb = function(mqtt_response) {
248
+ // Ignore anything that is not JSON or
249
+ // doesn't comply to the nutella protocol
250
+ try {
251
+ var f = extractFieldsFromMessage(mqtt_response);
252
+ var response_id = extractIdFromMessage(mqtt_response);
253
+ // Only handle responses that have proper id set
254
+ if (f.type==='response' && response_id===request_id) {
255
+ // Execute callback
256
+ callback(f.payload);
257
+ }
258
+ } catch(err) {
259
+ }
260
+ };
261
+ // Subscribe to response
262
+ var mqtt_cli = this.main_nutella.mqtt_client;
263
+ mqtt_cli.subscribe(new_channel, mqtt_cb, function() {
264
+ // Once we are subscribed we publish the request
265
+ mqtt_cli.publish(new_channel, mqtt_request);
266
+ // Execute optional done callback
267
+ if (done_callback!==undefined) done_callback();
268
+ });
269
+ };
270
+
271
+
272
+
273
+ /**
274
+ * Handles requests.
275
+ *
276
+ * @param channel
277
+ * @param callback
278
+ * @param done_callback
279
+ */
280
+ NetSubModule.prototype.handle_requests = function(channel, callback, done_callback) {
281
+ // Pad the channel
282
+ var new_channel = this.main_nutella.runId + '/' + channel;
283
+ // Prepare callback
284
+ var c_id = this.main_nutella.componentId;
285
+ var r_id = this.main_nutella.resourceId;
286
+ var mqtt_cli = this.main_nutella.mqtt_client;
287
+ var mqtt_cb = function(mqtt_request) {
288
+ // Ignore anything that is not JSON or
289
+ // doesn't comply to the nutella protocol
290
+ try {
291
+ var f = extractFieldsFromMessage(mqtt_request);
292
+ var id = extractIdFromMessage(mqtt_request);
293
+ } catch(err) {
294
+ return;
295
+ }
296
+ // Only handle requests that have proper id set
297
+ if (f.type!=='request' || id===undefined) return;
298
+ // Execute callback, assemble the response and publish
299
+ var res_json = callback(f.payload, f.componentId, f.resourceId);
300
+ var mqtt_response = prepareResponse(res_json, id, c_id, r_id);
301
+ mqtt_cli.publish(new_channel, mqtt_response);
302
+ };
303
+ // Subscribe
304
+ this.main_nutella.mqtt_client.subscribe(new_channel, mqtt_cb, done_callback);
305
+ };
306
+
307
+
308
+
309
+ //
310
+ // Helper function
311
+ // Extracts nutella parameters from a received message
312
+ //
313
+ function extractFieldsFromMessage(message) {
314
+ var params = JSON.parse(message);
315
+ var s = params.from.split('/');
316
+ delete params.from;
317
+ params.componentId = s[0];
318
+ if (s.length===2)
319
+ params.resourceId = s[1];
320
+ return params;
321
+ }
322
+
323
+
324
+
325
+ //
326
+ // Helper function
327
+ // Extracts request id from a received message
328
+ //
329
+ function extractIdFromMessage(message) {
330
+ var params = JSON.parse(message);
331
+ return params.id;
332
+ }
333
+
334
+
335
+
336
+ //
337
+ // Helper function
338
+ // Pads a message with the nutella protocol fields
339
+ //
340
+ function prepareMessageForPublish(message, componentId, resourceId) {
341
+ var from = resourceId===undefined ? componentId : (componentId + '/' + resourceId);
342
+ if (message===undefined)
343
+ return JSON.stringify({type: 'publish', from: from});
344
+ return JSON.stringify({type: 'publish', from: from, payload: message});
345
+ }
346
+
347
+
348
+
349
+ //
350
+ // Helper function
351
+ // Pads a request with the nutella protocol fields
352
+ //
353
+ function prepareRequest(message, request_id, componentId, resourceId) {
354
+ var from = resourceId===undefined ? componentId : (componentId + '/' + resourceId);
355
+ if (message===undefined)
356
+ return JSON.stringify({id: request_id, type: 'request', from: from});
357
+ return JSON.stringify({id: request_id, type: 'request', from: from, payload: message});
358
+ }
359
+
360
+
361
+
362
+ //
363
+ // Helper function
364
+ // Pads a response with the nutella protocol fields
365
+ //
366
+ function prepareResponse(message, request_id, componentId, resourceId) {
367
+ var from = resourceId===undefined ? componentId : (componentId + '/' + resourceId);
368
+ if (message===undefined)
369
+ return JSON.stringify({id: request_id, type: 'response', from: from});
370
+ return JSON.stringify({id: request_id, type: 'response', from: from, payload: message});
371
+ }
372
+
373
+
374
+
375
+ // Helper function to test if a channel is wildcard or not.
376
+ // Returns true if it is Returns true is.
377
+ // See MQTT specification for wildcard channels
378
+ // {http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718106 here
379
+ //
380
+ function isChannelWildcard(channel) {
381
+ return channel.indexOf('#')!==-1 || channel.indexOf('+')!==-1;
382
+ }
383
+
384
+
385
+ //
386
+ // END OF: net sub-module
387
+ //
388
+
389
+
390
+
391
+ // --------------------------------------------------------------------------------------------
392
+ // persist sub-module
393
+ // --------------------------------------------------------------------------------------------
394
+
395
+
396
+ var PersistSubModule = function(main_nutella) {
397
+ // Store a reference to the main module
398
+ this.main_nutella = main_nutella;
399
+ };
400
+
401
+
402
+
403
+ PersistSubModule.prototype.persist = function () {
404
+ console.log("This is just a test method for the persist sub-module");
405
+ };
406
+
407
+
408
+
409
+
410
+ //
411
+ // END OF: persist sub-module
412
+ //
413
+
414
+
415
+
416
+
417
+ // --------------------------------------------------------------------------------------------
418
+ // Simple MQTT client
419
+ // --------------------------------------------------------------------------------------------
420
+
421
+
422
+ /**
423
+ * Defines a Simple MQTT client.
424
+ *
425
+ * @param {string} host - the hostname of the broker.
426
+ * @param {string} [clientId] - the unique name of this client. If no ID is provided a random one is generated
427
+ */
428
+ var SimpleMQTTClient = function (host, clientId) {
429
+
430
+ // Initializes the object that stores subscriptions
431
+ this.subscriptions = {};
432
+ // Initializes the object that holds the internal client
433
+ this.client = {};
434
+ // Functions backlog
435
+ this.backlog = [];
436
+
437
+ // Handles the optional clientId parameter
438
+ if (arguments.length === 1 || clientId === undefined) {
439
+ clientId = generateRandomClientId();
440
+ }
441
+
442
+ // Connect
443
+ if (isNode)
444
+ this.client = connectNode(this.subscriptions, host, clientId);
445
+ else
446
+ this.client = connectBrowser(this.subscriptions, this.backlog, host, clientId);
447
+ };
448
+
449
+
450
+ //
451
+ // Helper function that connects the MQTT client in node
452
+ //
453
+ function connectNode (subscriptions, host, clientId) {
454
+ // Create client
455
+ var url = "tcp://" + host + ":1883";
456
+ var client = mqtt_lib.connect(url, {clientId : clientId});
457
+ // Register incoming message callback
458
+ client.on('message', function(channel, message) {
459
+ // Executes the appropriate channel callback
460
+ var cbs = findCallbacks(subscriptions, channel);
461
+ if (cbs!==undefined) {
462
+ if (Object.keys(subscriptions).indexOf(channel)!==-1)
463
+ cbs.forEach(function(cb) {
464
+ cb(message);
465
+ });
466
+ else
467
+ cbs.forEach(function(cb) {
468
+ cb(message, channel);
469
+ });
470
+
471
+ }
472
+ });
473
+ return client;
474
+ }
475
+
476
+
477
+ //
478
+ // Helper function that connects the MQTT client in the browser
479
+ //
480
+ function connectBrowser (subscriptions, backlog, host, clientId) {
481
+ // Create client
482
+ var client = new mqtt_lib.Client(host, Number(1884), clientId);
483
+ // Register callback for connection lost
484
+ client.onConnectionLost = function() {
485
+ // TODO try to reconnect
486
+ };
487
+ // Register callback for received message
488
+ client.onMessageArrived = function (message) {
489
+ // Executes the appropriate channel callback
490
+ var cbs = findCallbacks(subscriptions, message.destinationName);
491
+ if (cbs!==undefined) {
492
+ if (Object.keys(subscriptions).indexOf(message.destinationName)!==-1)
493
+ cbs.forEach(function(cb) {
494
+ cb(message.payloadString);
495
+ });
496
+ else
497
+ cbs.forEach(function(cb) {
498
+ cb(message.payloadString, message.destinationName);
499
+ });
500
+ }
501
+ };
502
+ // Connect
503
+ client.connect({onSuccess: function() {
504
+ // Execute the backlog of operations performed while the client wasn't connected
505
+ backlog.forEach(function(e) {
506
+ e.op.apply(this, e.params);
507
+ });
508
+ }});
509
+ return client;
510
+ }
511
+
512
+
513
+
514
+ /**
515
+ * Disconnects from the MQTT client.
516
+ */
517
+ SimpleMQTTClient.prototype.disconnect = function () {
518
+ if (isNode)
519
+ this.client.end();
520
+ else
521
+ this.client.disconnect();
522
+ this.subscriptions = {};
523
+ };
524
+
525
+
526
+
527
+ /**
528
+ * Subscribes to a channel and registers a callback.
529
+ *
530
+ * @param {string} channel - the channel we are subscribing to.
531
+ * @param {callback} callback - A function that is executed every time a message is received on that channel.
532
+ * @param {callback} [done_callback] - A function that is executed once the subscribe operation has completed successfully.
533
+ */
534
+ SimpleMQTTClient.prototype.subscribe = function (channel, callback, done_callback) {
535
+ // Subscribe
536
+ if( isNode )
537
+ subscribeNode(this.client, this.subscriptions, channel, callback, done_callback);
538
+ else
539
+ subscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback);
540
+ };
541
+
542
+
543
+ //
544
+ // Helper function that subscribes to a channel in node
545
+ //
546
+ function subscribeNode (client, subscriptions, channel, callback, done_callback) {
547
+ if (subscriptions[channel]===undefined) {
548
+ subscriptions[channel] = [callback];
549
+ client.subscribe(channel, {qos: 0}, function() {
550
+ // If there is a done_callback defined, execute it
551
+ if (done_callback!==undefined) done_callback();
552
+ });
553
+ } else {
554
+ subscriptions[channel].push(callback);
555
+ }
556
+ }
557
+
558
+
559
+ //
560
+ // Helper function that subscribes to a channel in the browser
561
+ //
562
+ function subscribeBrowser (client, subscriptions, backlog, channel, callback, done_callback) {
563
+ if ( addToBacklog(client, backlog, subscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return;
564
+ if (subscriptions[channel]===undefined) {
565
+ subscriptions[channel] = [callback];
566
+ client.subscribe(channel, {qos: 0, onSuccess: function() {
567
+ // If there is a done_callback defined, execute it
568
+ if (done_callback!==undefined) done_callback();
569
+ }});
570
+ } else {
571
+ subscriptions[channel].push(callback);
572
+ // If there is a done_callback defined, execute it
573
+ if (done_callback!==undefined) done_callback();
574
+ }
575
+ }
576
+
577
+
578
+
579
+ /**
580
+ * Unsubscribe from a channel.
581
+ *
582
+ * @param {string} channel - the channel we are un-subscribing from.
583
+ * @param {function} callback - the callback we are trying to de-register
584
+ * @param {callback} [done_callback] - A function that is executed once the unsubscribe operation has completed successfully.
585
+ */
586
+ SimpleMQTTClient.prototype.unsubscribe = function (channel, callback, done_callback) {
587
+ if( isNode )
588
+ unsubscribeNode(this.client, this.subscriptions, channel, callback, done_callback);
589
+ else
590
+ unsubscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback);
591
+ };
592
+
593
+
594
+ //
595
+ // Helper function that unsubscribes from a channel in node
596
+ //
597
+ var unsubscribeNode = function(client, subscriptions, channel, callback, done_callback) {
598
+ if (subscriptions[channel]===undefined)
599
+ return;
600
+ subscriptions[channel].splice(subscriptions[channel].indexOf(callback), 1);
601
+ if (subscriptions[channel].length===0) {
602
+ delete subscriptions[channel];
603
+ client.unsubscribe(channel, function() {
604
+ // If there is a done_callback defined, execute it
605
+ if (done_callback!==undefined) done_callback();
606
+ });
607
+ }
608
+ };
609
+
610
+
611
+ //
612
+ // Helper function that unsubscribes from a channel in the browser
613
+ //
614
+ var unsubscribeBrowser = function(client, subscriptions, backlog, channel, callback, done_callback) {
615
+ if ( addToBacklog(client, backlog, unsubscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return;
616
+ if (subscriptions[channel]===undefined)
617
+ return;
618
+ subscriptions[channel].splice(subscriptions[channel].indexOf(callback), 1);
619
+ if (subscriptions[channel].length===0) {
620
+ delete subscriptions[channel];
621
+ client.unsubscribe(channel, {onSuccess : function() {
622
+ // If there is a done_callback defined, execute it
623
+ if (done_callback!==undefined) done_callback();
624
+ }});
625
+ }
626
+ };
627
+
628
+
629
+ /**
630
+ * Lists all the channels we are currently subscribed to.
631
+ *
632
+ * @returns {Array} a lists of all the channels we are currently subscribed to.
633
+ */
634
+ SimpleMQTTClient.prototype.getSubscriptions = function () {
635
+ return Object.keys(this.subscriptions);
636
+ };
637
+
638
+
639
+ /**
640
+ * Publishes a message to a channel.
641
+ *
642
+ * @param {string} channel - the channel we are publishing to.
643
+ * @param {string} message - the message we are publishing.
644
+ */
645
+ SimpleMQTTClient.prototype.publish = function (channel, message) {
646
+ if (isNode)
647
+ publishNode(this.client, channel, message);
648
+ else
649
+ publishBrowser(this.client, this.backlog, channel, message)
650
+ };
651
+
652
+
653
+ //
654
+ // Helper function that publishes to a channel in node
655
+ //
656
+ var publishNode = function (client, channel, message) {
657
+ client.publish(channel, message);
658
+ };
659
+
660
+
661
+ //
662
+ // Helper function that publishes to a channel in the browser
663
+ //
664
+ var publishBrowser = function (client, backlog, channel, message) {
665
+ if ( addToBacklog(client, backlog, publishBrowser, [client, backlog, channel, message]) ) return;
666
+ message = new mqtt_lib.Message(message);
667
+ message.destinationName = channel;
668
+ client.send(message);
669
+ };
670
+
671
+
672
+ //
673
+ // Helper functions for Simple MQTT client
674
+ //
675
+
676
+
677
+ //
678
+ // Helper function that generates a random client ID
679
+ //
680
+ function generateRandomClientId () {
681
+ var length = 22;
682
+ var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
683
+ var result = '';
684
+ for (var i = length; i > 0; --i) {
685
+ result += chars[Math.round(Math.random() * (chars.length - 1))];
686
+ }
687
+ return result;
688
+ };
689
+
690
+
691
+ //
692
+ // Helper function that selects the right callback when a message is received
693
+ //
694
+ function findCallbacks (subscriptions, channel) {
695
+ // First try to see if a callback for the exact channel exists
696
+ if(Object.keys(subscriptions).indexOf(channel)!==-1)
697
+ return subscriptions[channel];
698
+ // If it doesn't then let's try to see if the channel matches a wildcard callback
699
+ var pattern = matchesWildcard(subscriptions, channel);
700
+ if (pattern!== undefined) {
701
+ return subscriptions[pattern];
702
+ }
703
+ // If there's no exact match or wildcard we have to return undefined
704
+ return undefined;
705
+ };
706
+
707
+
708
+ //
709
+ // Helper function that tries to match a channel with each subscription
710
+ // it returns undefined if no match is found
711
+ //
712
+ function matchesWildcard (subscriptions, channel) {
713
+ var i;
714
+ var subs = Object.keys(subscriptions);
715
+ for (i=0; i < subs.length; i++) {
716
+ if (matchesFilter(subs[i], channel)) {
717
+ return subs[i];
718
+ }
719
+ }
720
+ return undefined;
721
+ };
722
+
723
+
724
+ //
725
+ // Helper function that checks a certain channel and see if it matches a wildcard pattern
726
+ // Returns true if the channel matches a pattern (including the exact pattern)
727
+ //
728
+ function matchesFilter (pattern, channel) {
729
+ // If multi-level wildcard is the only character in pattern, then any string will match
730
+ if (pattern==="#") {
731
+ return true;
732
+ }
733
+ // Handle all other multi-level wildcards
734
+ // FROM SPEC: The number sign (‘#’ U+0023) is a wildcard character that matches any number of levels within a topic. The multi-level wildcard represents the parent and any number of child levels. The multi-level wildcard character MUST be specified either on its own or following a topic level separator. In either case it MUST be the last character specified in the Topic Filter
735
+ var p_wo_wildcard = pattern.substring(0, pattern.length-2);
736
+ var str_wo_details = channel.substring(0, pattern.length-2);
737
+ if (pattern.slice(-1)=='#' && p_wo_wildcard==str_wo_details) {
738
+ return true;
739
+ }
740
+ // TODO Handle single-level wildcards (+)
741
+ // FROM SPEC: The single-level wildcard can be used at any level in the Topic Filter, including first and last levels. Where it is used it MUST occupy an entire level of the filter [MQTT-4.7.1-3]. It can be used at more than one level in the Topic Filter and can be used in conjunction with the multilevel wildcard.
742
+ // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718107
743
+ return false;
744
+ };
745
+
746
+
747
+ //
748
+ // Helper method that queues operations into the backlog.
749
+ // This method is used to make `connect` "synchronous" by
750
+ // queueing up operations on the client until it is connected.
751
+ //
752
+ // @param {string} method - the method that needs to be added to the backlog
753
+ // @param {Array} parameters - parameters to the method being added to the backlog
754
+ // @returns {boolean} true if the method was successfully added, false otherwise
755
+ //
756
+ function addToBacklog (client, backlog, method, parameters) {
757
+ if (!client.isConnected() ) {
758
+ backlog.push({
759
+ op : method,
760
+ params : parameters
761
+ });
762
+ return true;
763
+ }
764
+ return false;
765
+ };
766
+
767
+
768
+
769
+
770
+ //
771
+ // End of methods definition for SimpleMQTTClient
772
+ //
773
+
774
+
775
+ // Export the nutella object
776
+ // For node.js, also with backwards-compatibility for the old `require()` API.
777
+ // If we're in the browser, add `NUTELLA` as a global object.
778
+ if (typeof exports !== 'undefined') {
779
+ if (typeof module !== 'undefined' && module.exports) {
780
+ exports = module.exports = nutella;
781
+ }
782
+ exports.NUTELLA = nutella;
783
+ } else {
784
+ root.NUTELLA = nutella;
785
+ }
786
+
787
+
788
+
789
+ }.call(this));