nutella_framework 0.4.5 → 0.4.8

Sign up to get free protection for your applications and to get access to all the features.
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,4039 @@
1
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.NUTELLA = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
2
+ /******************
3
+ * nutella_lib.js *
4
+ ******************/
5
+
6
+ "use strict";
7
+
8
+ /**
9
+ * Entry point for nutella_lib in the browser
10
+ */
11
+
12
+ var nutella_i = require('./nutella_i_browser');
13
+
14
+ // Internal reference to this library (used below)
15
+ var nutella = {};
16
+
17
+
18
+ /**
19
+ * Creates a new instance of nutella
20
+ * and initialize it. This is a factory method.
21
+ *
22
+ * @param {string} broker_hostname - the hostname of the broker.*
23
+ * @param {string} app_id - the app_id this component belongs to
24
+ * @param {string} run_id - the run_id this component is launched in
25
+ * @param {string} component_id - the name of this component
26
+ */
27
+ nutella.init = function(broker_hostname, app_id, run_id, component_id) {
28
+ if (broker_hostname===undefined || app_id===undefined || run_id===undefined || component_id=== undefined) {
29
+ console.warn("Couldn't initialize nutella. Make sure you are setting all four required parameters (broker_hostname, app_id, run_id, component_id)");
30
+ }
31
+ return new nutella_i.RunNutellaInstance(broker_hostname, app_id, run_id, component_id);
32
+ };
33
+
34
+
35
+ /**
36
+ * Creates a new instance of nutella
37
+ * and initialize it for an app-level component.
38
+ * This is a factory method.
39
+ *
40
+ * @param {string} broker_hostname - the hostname of the broker.*
41
+ * @param {string} app_id - the app_id this component belongs to
42
+ * @param {string} component_id - the name of this component
43
+ */
44
+ nutella.initApp = function(broker_hostname, app_id, component_id) {
45
+ if (broker_hostname===undefined || app_id===undefined || component_id=== undefined) {
46
+ console.warn("Couldn't initialize nutella. Make sure you are setting all three required parameters (broker_hostname, app_id, component_id)");
47
+ }
48
+ return new nutella_i.AppNutellaInstance(broker_hostname, app_id, component_id);
49
+ };
50
+
51
+
52
+ /**
53
+ * Creates a new instance of nutella
54
+ * and initialize it for a framework-level component.
55
+ * This is a factory method.
56
+ *
57
+ * @param {string} broker_hostname - the hostname of the broker.*
58
+ * @param {string} component_id - the name of this component
59
+ */
60
+ nutella.initFramework = function(broker_hostname, component_id) {
61
+ if (broker_hostname===undefined || component_id=== undefined) {
62
+ console.warn("Couldn't initialize nutella. Make sure you are setting all two required parameters (broker_hostname, component_id)");
63
+ }
64
+ return new nutella_i.FrNutellaInstance(broker_hostname, component_id);
65
+ };
66
+
67
+
68
+
69
+ /**
70
+ * Utility method that parses URL parameters from the URL.
71
+ * It is obviously only available in the browser.
72
+ *
73
+ * @return {Object} An object containing all the URL query parameters
74
+ */
75
+ nutella.parseURLParameters = function () {
76
+ var str = location.search;
77
+ var queries = str.replace(/^\?/, '').split('&');
78
+ var searchObject = {};
79
+ for( var i = 0; i < queries.length; i++ ) {
80
+ var split = queries[i].split('=');
81
+ searchObject[split[0]] = split[1];
82
+ }
83
+ return searchObject;
84
+ };
85
+
86
+
87
+ nutella.version = '0.4.3';
88
+
89
+
90
+ // Exports nutella object
91
+ module.exports = nutella;
92
+ },{"./nutella_i_browser":10}],2:[function(require,module,exports){
93
+ /**********************
94
+ * Simple MQTT client *
95
+ **********************/
96
+
97
+ "use strict";
98
+
99
+ var mqtt_lib = require('./paho/mqttws31');
100
+ //var mqtt_lib = require('./paho/mqttws31-min'); TODO
101
+
102
+
103
+ /**
104
+ * Defines a Simple MQTT client.
105
+ *
106
+ * @param {string} host - the hostname of the broker.
107
+ * @param {string} [clientId] - the unique name of this client. If no ID is provided a random one is generated
108
+ */
109
+ var SimpleMQTTClient = function (host, clientId) {
110
+ // Initializes the object that stores subscriptions
111
+ this.subscriptions = {};
112
+ // Initializes the object that holds the internal client
113
+ this.client = {};
114
+ // Functions backlog
115
+ this.backlog = [];
116
+ // Handles the optional clientId parameter
117
+ if (arguments.length === 1 || clientId === undefined) {
118
+ clientId = generateRandomClientId();
119
+ }
120
+ // Connect
121
+ this.client = connectBrowser(this.subscriptions, this.backlog, host, clientId);
122
+ };
123
+
124
+ //
125
+ // Helper function that generates a random client ID
126
+ //
127
+ function generateRandomClientId () {
128
+ var length = 22;
129
+ var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
130
+ var result = '';
131
+ for (var i = length; i > 0; --i) {
132
+ result += chars[Math.round(Math.random() * (chars.length - 1))];
133
+ }
134
+ return result;
135
+ };
136
+
137
+ //
138
+ // Helper function that connects the MQTT client in the browser
139
+ //
140
+ function connectBrowser (subscriptions, backlog, host, clientId) {
141
+ // Create client
142
+ var client = new mqtt_lib.Client(host, Number(1884), clientId);
143
+ // Register callback for connection lost
144
+ client.onConnectionLost = function() {
145
+ // TODO try to reconnect
146
+ };
147
+ // Register callback for received message
148
+ client.onMessageArrived = function (message) {
149
+ // Execute all the appropriate callbacks:
150
+ // the ones specific to this channel with a single parameter (message)
151
+ // the ones associated to a wildcard channel, with two parameters (message and channel)
152
+ var cbs = findCallbacks(subscriptions, message.destinationName);
153
+ if (cbs!==undefined) {
154
+ cbs.forEach(function(cb) {
155
+ if (Object.keys(subscriptions).indexOf(message.destinationName)!==-1)
156
+ cb(message.payloadString);
157
+ else
158
+ cb(message.payloadString, message.destinationName);
159
+ });
160
+ }
161
+ };
162
+ // Connect
163
+ client.connect({onSuccess: function() {
164
+ // Execute the backlog of operations performed while the client wasn't connected
165
+ backlog.forEach(function(e) {
166
+ e.op.apply(this, e.params);
167
+ });
168
+ }});
169
+ return client;
170
+ }
171
+
172
+
173
+
174
+ /**
175
+ * Disconnects from the MQTT client.
176
+ */
177
+ SimpleMQTTClient.prototype.disconnect = function () {
178
+ this.client.disconnect();
179
+ this.subscriptions = {};
180
+ };
181
+
182
+
183
+
184
+ /**
185
+ * Subscribes to a channel and registers a callback.
186
+ *
187
+ * @param {string} channel - the channel we are subscribing to.
188
+ * @param {callback} callback - A function that is executed every time a message is received on that channel.
189
+ * @param {callback} [done_callback] - A function that is executed once the subscribe operation has completed successfully.
190
+ */
191
+ SimpleMQTTClient.prototype.subscribe = function (channel, callback, done_callback) {
192
+ subscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback);
193
+ };
194
+
195
+
196
+ //
197
+ // Helper function that subscribes to a channel in the browser
198
+ //
199
+ function subscribeBrowser (client, subscriptions, backlog, channel, callback, done_callback) {
200
+ if ( addToBacklog(client, backlog, subscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return;
201
+ if (subscriptions[channel]===undefined) {
202
+ subscriptions[channel] = [callback];
203
+ client.subscribe(channel, {qos: 0, onSuccess: function() {
204
+ // If there is a done_callback defined, execute it
205
+ if (done_callback!==undefined) done_callback();
206
+ }});
207
+ } else {
208
+ subscriptions[channel].push(callback);
209
+ // If there is a done_callback defined, execute it
210
+ if (done_callback!==undefined) done_callback();
211
+ }
212
+ }
213
+
214
+
215
+
216
+ /**
217
+ * Unsubscribe from a channel.
218
+ *
219
+ * @param {string} channel - the channel we are unsubscribing from.
220
+ * @param {function} callback - the callback we are trying to unregister
221
+ * @param {callback} [done_callback] - A function that is executed once the unsubscribe operation has completed successfully.
222
+ */
223
+ SimpleMQTTClient.prototype.unsubscribe = function (channel, callback, done_callback) {
224
+ unsubscribeBrowser(this.client, this.subscriptions, this.backlog, channel, callback, done_callback);
225
+ };
226
+
227
+
228
+
229
+
230
+ //
231
+ // Helper function that unsubscribes from a channel in the browser
232
+ //
233
+ var unsubscribeBrowser = function(client, subscriptions, backlog, channel, callback, done_callback) {
234
+ if ( addToBacklog(client, backlog, unsubscribeBrowser, [client, subscriptions, backlog, channel, callback, done_callback]) ) return;
235
+ if (subscriptions[channel]===undefined)
236
+ return;
237
+ subscriptions[channel].splice(subscriptions[channel].indexOf(callback), 1);
238
+ if (subscriptions[channel].length===0) {
239
+ delete subscriptions[channel];
240
+ client.unsubscribe(channel, {onSuccess : function() {
241
+ // If there is a done_callback defined, execute it
242
+ if (done_callback!==undefined) done_callback();
243
+ }});
244
+ }
245
+ };
246
+
247
+
248
+ /**
249
+ * Lists all the channels we are currently subscribed to.
250
+ *
251
+ * @returns {Array} a lists of all the channels we are currently subscribed to.
252
+ */
253
+ SimpleMQTTClient.prototype.getSubscriptions = function () {
254
+ return Object.keys(this.subscriptions);
255
+ };
256
+
257
+
258
+ /**
259
+ * Publishes a message to a channel.
260
+ *
261
+ * @param {string} channel - the channel we are publishing to.
262
+ * @param {string} message - the message we are publishing.
263
+ */
264
+ SimpleMQTTClient.prototype.publish = function (channel, message) {
265
+ publishBrowser(this.client, this.backlog, channel, message)
266
+ };
267
+
268
+
269
+ //
270
+ // Helper function that publishes to a channel in the browser
271
+ //
272
+ var publishBrowser = function (client, backlog, channel, message) {
273
+ if ( addToBacklog(client, backlog, publishBrowser, [client, backlog, channel, message]) ) return;
274
+ message = new mqtt_lib.Message(message);
275
+ message.destinationName = channel;
276
+ client.send(message);
277
+ };
278
+
279
+
280
+
281
+ SimpleMQTTClient.prototype.isChannelWildcard = function(channel) {
282
+ return channel.indexOf('#')>-1 || channel.indexOf('+')>-1 ;
283
+ }
284
+
285
+
286
+
287
+
288
+
289
+
290
+ //
291
+ // Helper function that selects the right callback when a message is received
292
+ //
293
+ function findCallbacks (subscriptions, channel) {
294
+ // First try to see if a callback for the exact channel exists
295
+ if(Object.keys(subscriptions).indexOf(channel)!==-1)
296
+ return subscriptions[channel];
297
+ // If it doesn't then let's try to see if the channel matches a wildcard callback
298
+ var pattern = matchesWildcard(subscriptions, channel);
299
+ if (pattern!== undefined) {
300
+ return subscriptions[pattern];
301
+ }
302
+ // If there's no exact match or wildcard we have to return undefined
303
+ return undefined;
304
+ };
305
+
306
+
307
+ //
308
+ // Helper function that tries to match a channel with each subscription
309
+ // it returns undefined if no match is found
310
+ //
311
+ function matchesWildcard (subscriptions, channel) {
312
+ var i;
313
+ var subs = Object.keys(subscriptions);
314
+ for (i=0; i < subs.length; i++) {
315
+ if (matchesFilter(subs[i], channel)) {
316
+ return subs[i];
317
+ }
318
+ }
319
+ return undefined;
320
+ };
321
+
322
+
323
+ //
324
+ // Helper function that checks a certain channel and see if it matches a wildcard pattern
325
+ // Returns true if the channel matches a pattern (including the exact pattern)
326
+ //
327
+ function matchesFilter (pattern, channel) {
328
+ // If multi-level wildcard is the only character in pattern, then any string will match
329
+ if (pattern==="#") {
330
+ return true;
331
+ }
332
+ // Handle all other multi-level wildcards
333
+ // 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
334
+ var p_wo_wildcard = pattern.substring(0, pattern.length-2);
335
+ var str_wo_details = channel.substring(0, pattern.length-2);
336
+ if (pattern.slice(-1)=='#' && p_wo_wildcard==str_wo_details) {
337
+ return true;
338
+ }
339
+ // TODO Handle single-level wildcards (+)
340
+ // 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.
341
+ // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718107
342
+ return false;
343
+ };
344
+
345
+
346
+ //
347
+ // Helper method that queues operations into the backlog.
348
+ // This method is used to make `connect` "synchronous" by
349
+ // queueing up operations on the client until it is connected.
350
+ //
351
+ // @param {string} method - the method that needs to be added to the backlog
352
+ // @param {Array} parameters - parameters to the method being added to the backlog
353
+ // @returns {boolean} true if the method was successfully added, false otherwise
354
+ //
355
+ function addToBacklog (client, backlog, method, parameters) {
356
+ if (!client.isConnected() ) {
357
+ backlog.push({
358
+ op : method,
359
+ params : parameters
360
+ });
361
+ return true;
362
+ }
363
+ return false;
364
+ };
365
+
366
+
367
+
368
+
369
+ //
370
+ // Exports SimpleMQTTClient class for other modules
371
+ //
372
+ module.exports = SimpleMQTTClient;
373
+
374
+ },{"./paho/mqttws31":3}],3:[function(require,module,exports){
375
+ /*******************************************************************************
376
+ * Copyright (c) 2013 IBM Corp.
377
+ *
378
+ * All rights reserved. This program and the accompanying materials
379
+ * are made available under the terms of the Eclipse Public License v1.0
380
+ * and Eclipse Distribution License v1.0 which accompany this distribution.
381
+ *
382
+ * The Eclipse Public License is available at
383
+ * http://www.eclipse.org/legal/epl-v10.html
384
+ * and the Eclipse Distribution License is available at
385
+ * http://www.eclipse.org/org/documents/edl-v10.php.
386
+ *
387
+ * Contributors:
388
+ * Andrew Banks - initial API and implementation and initial documentation
389
+ *******************************************************************************/
390
+
391
+
392
+ // Only expose a single object name in the global namespace.
393
+ // Everything must go through this module. Global Paho.MQTT module
394
+ // only has a single public function, client, which returns
395
+ // a Paho.MQTT client object given connection details.
396
+
397
+ /**
398
+ * Send and receive messages using web browsers.
399
+ * <p>
400
+ * This programming interface lets a JavaScript client application use the MQTT V3.1 or
401
+ * V3.1.1 protocol to connect to an MQTT-supporting messaging server.
402
+ *
403
+ * The function supported includes:
404
+ * <ol>
405
+ * <li>Connecting to and disconnecting from a server. The server is identified by its host name and port number.
406
+ * <li>Specifying options that relate to the communications link with the server,
407
+ * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required.
408
+ * <li>Subscribing to and receiving messages from MQTT Topics.
409
+ * <li>Publishing messages to MQTT Topics.
410
+ * </ol>
411
+ * <p>
412
+ * The API consists of two main objects:
413
+ * <dl>
414
+ * <dt><b>{@link Paho.MQTT.Client}</b></dt>
415
+ * <dd>This contains methods that provide the functionality of the API,
416
+ * including provision of callbacks that notify the application when a message
417
+ * arrives from or is delivered to the messaging server,
418
+ * or when the status of its connection to the messaging server changes.</dd>
419
+ * <dt><b>{@link Paho.MQTT.Message}</b></dt>
420
+ * <dd>This encapsulates the payload of the message along with various attributes
421
+ * associated with its delivery, in particular the destination to which it has
422
+ * been (or is about to be) sent.</dd>
423
+ * </dl>
424
+ * <p>
425
+ * The programming interface validates parameters passed to it, and will throw
426
+ * an Error containing an error message intended for developer use, if it detects
427
+ * an error with any parameter.
428
+ * <p>
429
+ * Example:
430
+ *
431
+ * <code><pre>
432
+ client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
433
+ client.onConnectionLost = onConnectionLost;
434
+ client.onMessageArrived = onMessageArrived;
435
+ client.connect({onSuccess:onConnect});
436
+
437
+ function onConnect() {
438
+ // Once a connection has been made, make a subscription and send a message.
439
+ console.log("onConnect");
440
+ client.subscribe("/World");
441
+ message = new Paho.MQTT.Message("Hello");
442
+ message.destinationName = "/World";
443
+ client.send(message);
444
+ };
445
+ function onConnectionLost(responseObject) {
446
+ if (responseObject.errorCode !== 0)
447
+ console.log("onConnectionLost:"+responseObject.errorMessage);
448
+ };
449
+ function onMessageArrived(message) {
450
+ console.log("onMessageArrived:"+message.payloadString);
451
+ client.disconnect();
452
+ };
453
+ * </pre></code>
454
+ * @namespace Paho.MQTT
455
+ */
456
+
457
+ if (typeof Paho === "undefined") {
458
+ Paho = {};
459
+ }
460
+
461
+ Paho.MQTT = (function (global) {
462
+
463
+ // Private variables below, these are only visible inside the function closure
464
+ // which is used to define the module.
465
+
466
+ var version = "1.0.1";
467
+ var buildLevel = "2014-11-18T11:57:44Z";
468
+
469
+ /**
470
+ * Unique message type identifiers, with associated
471
+ * associated integer values.
472
+ * @private
473
+ */
474
+ var MESSAGE_TYPE = {
475
+ CONNECT: 1,
476
+ CONNACK: 2,
477
+ PUBLISH: 3,
478
+ PUBACK: 4,
479
+ PUBREC: 5,
480
+ PUBREL: 6,
481
+ PUBCOMP: 7,
482
+ SUBSCRIBE: 8,
483
+ SUBACK: 9,
484
+ UNSUBSCRIBE: 10,
485
+ UNSUBACK: 11,
486
+ PINGREQ: 12,
487
+ PINGRESP: 13,
488
+ DISCONNECT: 14
489
+ };
490
+
491
+ // Collection of utility methods used to simplify module code
492
+ // and promote the DRY pattern.
493
+
494
+ /**
495
+ * Validate an object's parameter names to ensure they
496
+ * match a list of expected variables name for this option
497
+ * type. Used to ensure option object passed into the API don't
498
+ * contain erroneous parameters.
499
+ * @param {Object} obj - User options object
500
+ * @param {Object} keys - valid keys and types that may exist in obj.
501
+ * @throws {Error} Invalid option parameter found.
502
+ * @private
503
+ */
504
+ var validate = function(obj, keys) {
505
+ for (var key in obj) {
506
+ if (obj.hasOwnProperty(key)) {
507
+ if (keys.hasOwnProperty(key)) {
508
+ if (typeof obj[key] !== keys[key])
509
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));
510
+ } else {
511
+ var errorStr = "Unknown property, " + key + ". Valid properties are:";
512
+ for (var key in keys)
513
+ if (keys.hasOwnProperty(key))
514
+ errorStr = errorStr+" "+key;
515
+ throw new Error(errorStr);
516
+ }
517
+ }
518
+ }
519
+ };
520
+
521
+ /**
522
+ * Return a new function which runs the user function bound
523
+ * to a fixed scope.
524
+ * @param {function} User function
525
+ * @param {object} Function scope
526
+ * @return {function} User function bound to another scope
527
+ * @private
528
+ */
529
+ var scope = function (f, scope) {
530
+ return function () {
531
+ return f.apply(scope, arguments);
532
+ };
533
+ };
534
+
535
+ /**
536
+ * Unique message type identifiers, with associated
537
+ * associated integer values.
538
+ * @private
539
+ */
540
+ var ERROR = {
541
+ OK: {code:0, text:"AMQJSC0000I OK."},
542
+ CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."},
543
+ SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."},
544
+ UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."},
545
+ PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."},
546
+ INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"},
547
+ CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."},
548
+ SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."},
549
+ SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."},
550
+ MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."},
551
+ UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."},
552
+ INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."},
553
+ INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."},
554
+ INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."},
555
+ UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."},
556
+ INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."},
557
+ INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."},
558
+ MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."},
559
+ };
560
+
561
+ /** CONNACK RC Meaning. */
562
+ var CONNACK_RC = {
563
+ 0:"Connection Accepted",
564
+ 1:"Connection Refused: unacceptable protocol version",
565
+ 2:"Connection Refused: identifier rejected",
566
+ 3:"Connection Refused: server unavailable",
567
+ 4:"Connection Refused: bad user name or password",
568
+ 5:"Connection Refused: not authorized"
569
+ };
570
+
571
+ /**
572
+ * Format an error message text.
573
+ * @private
574
+ * @param {error} ERROR.KEY value above.
575
+ * @param {substitutions} [array] substituted into the text.
576
+ * @return the text with the substitutions made.
577
+ */
578
+ var format = function(error, substitutions) {
579
+ var text = error.text;
580
+ if (substitutions) {
581
+ var field,start;
582
+ for (var i=0; i<substitutions.length; i++) {
583
+ field = "{"+i+"}";
584
+ start = text.indexOf(field);
585
+ if(start > 0) {
586
+ var part1 = text.substring(0,start);
587
+ var part2 = text.substring(start+field.length);
588
+ text = part1+substitutions[i]+part2;
589
+ }
590
+ }
591
+ }
592
+ return text;
593
+ };
594
+
595
+ //MQTT protocol and version 6 M Q I s d p 3
596
+ var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03];
597
+ //MQTT proto/version for 311 4 M Q T T 4
598
+ var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04];
599
+
600
+ /**
601
+ * Construct an MQTT wire protocol message.
602
+ * @param type MQTT packet type.
603
+ * @param options optional wire message attributes.
604
+ *
605
+ * Optional properties
606
+ *
607
+ * messageIdentifier: message ID in the range [0..65535]
608
+ * payloadMessage: Application Message - PUBLISH only
609
+ * connectStrings: array of 0 or more Strings to be put into the CONNECT payload
610
+ * topics: array of strings (SUBSCRIBE, UNSUBSCRIBE)
611
+ * requestQoS: array of QoS values [0..2]
612
+ *
613
+ * "Flag" properties
614
+ * cleanSession: true if present / false if absent (CONNECT)
615
+ * willMessage: true if present / false if absent (CONNECT)
616
+ * isRetained: true if present / false if absent (CONNECT)
617
+ * userName: true if present / false if absent (CONNECT)
618
+ * password: true if present / false if absent (CONNECT)
619
+ * keepAliveInterval: integer [0..65535] (CONNECT)
620
+ *
621
+ * @private
622
+ * @ignore
623
+ */
624
+ var WireMessage = function (type, options) {
625
+ this.type = type;
626
+ for (var name in options) {
627
+ if (options.hasOwnProperty(name)) {
628
+ this[name] = options[name];
629
+ }
630
+ }
631
+ };
632
+
633
+ WireMessage.prototype.encode = function() {
634
+ // Compute the first byte of the fixed header
635
+ var first = ((this.type & 0x0f) << 4);
636
+
637
+ /*
638
+ * Now calculate the length of the variable header + payload by adding up the lengths
639
+ * of all the component parts
640
+ */
641
+
642
+ var remLength = 0;
643
+ var topicStrLength = new Array();
644
+ var destinationNameLength = 0;
645
+
646
+ // if the message contains a messageIdentifier then we need two bytes for that
647
+ if (this.messageIdentifier != undefined)
648
+ remLength += 2;
649
+
650
+ switch(this.type) {
651
+ // If this a Connect then we need to include 12 bytes for its header
652
+ case MESSAGE_TYPE.CONNECT:
653
+ switch(this.mqttVersion) {
654
+ case 3:
655
+ remLength += MqttProtoIdentifierv3.length + 3;
656
+ break;
657
+ case 4:
658
+ remLength += MqttProtoIdentifierv4.length + 3;
659
+ break;
660
+ }
661
+
662
+ remLength += UTF8Length(this.clientId) + 2;
663
+ if (this.willMessage != undefined) {
664
+ remLength += UTF8Length(this.willMessage.destinationName) + 2;
665
+ // Will message is always a string, sent as UTF-8 characters with a preceding length.
666
+ var willMessagePayloadBytes = this.willMessage.payloadBytes;
667
+ if (!(willMessagePayloadBytes instanceof Uint8Array))
668
+ willMessagePayloadBytes = new Uint8Array(payloadBytes);
669
+ remLength += willMessagePayloadBytes.byteLength +2;
670
+ }
671
+ if (this.userName != undefined)
672
+ remLength += UTF8Length(this.userName) + 2;
673
+ if (this.password != undefined)
674
+ remLength += UTF8Length(this.password) + 2;
675
+ break;
676
+
677
+ // Subscribe, Unsubscribe can both contain topic strings
678
+ case MESSAGE_TYPE.SUBSCRIBE:
679
+ first |= 0x02; // Qos = 1;
680
+ for ( var i = 0; i < this.topics.length; i++) {
681
+ topicStrLength[i] = UTF8Length(this.topics[i]);
682
+ remLength += topicStrLength[i] + 2;
683
+ }
684
+ remLength += this.requestedQos.length; // 1 byte for each topic's Qos
685
+ // QoS on Subscribe only
686
+ break;
687
+
688
+ case MESSAGE_TYPE.UNSUBSCRIBE:
689
+ first |= 0x02; // Qos = 1;
690
+ for ( var i = 0; i < this.topics.length; i++) {
691
+ topicStrLength[i] = UTF8Length(this.topics[i]);
692
+ remLength += topicStrLength[i] + 2;
693
+ }
694
+ break;
695
+
696
+ case MESSAGE_TYPE.PUBREL:
697
+ first |= 0x02; // Qos = 1;
698
+ break;
699
+
700
+ case MESSAGE_TYPE.PUBLISH:
701
+ if (this.payloadMessage.duplicate) first |= 0x08;
702
+ first = first |= (this.payloadMessage.qos << 1);
703
+ if (this.payloadMessage.retained) first |= 0x01;
704
+ destinationNameLength = UTF8Length(this.payloadMessage.destinationName);
705
+ remLength += destinationNameLength + 2;
706
+ var payloadBytes = this.payloadMessage.payloadBytes;
707
+ remLength += payloadBytes.byteLength;
708
+ if (payloadBytes instanceof ArrayBuffer)
709
+ payloadBytes = new Uint8Array(payloadBytes);
710
+ else if (!(payloadBytes instanceof Uint8Array))
711
+ payloadBytes = new Uint8Array(payloadBytes.buffer);
712
+ break;
713
+
714
+ case MESSAGE_TYPE.DISCONNECT:
715
+ break;
716
+
717
+ default:
718
+ ;
719
+ }
720
+
721
+ // Now we can allocate a buffer for the message
722
+
723
+ var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format
724
+ var pos = mbi.length + 1; // Offset of start of variable header
725
+ var buffer = new ArrayBuffer(remLength + pos);
726
+ var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes
727
+
728
+ //Write the fixed header into the buffer
729
+ byteStream[0] = first;
730
+ byteStream.set(mbi,1);
731
+
732
+ // If this is a PUBLISH then the variable header starts with a topic
733
+ if (this.type == MESSAGE_TYPE.PUBLISH)
734
+ pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos);
735
+ // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time
736
+
737
+ else if (this.type == MESSAGE_TYPE.CONNECT) {
738
+ switch (this.mqttVersion) {
739
+ case 3:
740
+ byteStream.set(MqttProtoIdentifierv3, pos);
741
+ pos += MqttProtoIdentifierv3.length;
742
+ break;
743
+ case 4:
744
+ byteStream.set(MqttProtoIdentifierv4, pos);
745
+ pos += MqttProtoIdentifierv4.length;
746
+ break;
747
+ }
748
+ var connectFlags = 0;
749
+ if (this.cleanSession)
750
+ connectFlags = 0x02;
751
+ if (this.willMessage != undefined ) {
752
+ connectFlags |= 0x04;
753
+ connectFlags |= (this.willMessage.qos<<3);
754
+ if (this.willMessage.retained) {
755
+ connectFlags |= 0x20;
756
+ }
757
+ }
758
+ if (this.userName != undefined)
759
+ connectFlags |= 0x80;
760
+ if (this.password != undefined)
761
+ connectFlags |= 0x40;
762
+ byteStream[pos++] = connectFlags;
763
+ pos = writeUint16 (this.keepAliveInterval, byteStream, pos);
764
+ }
765
+
766
+ // Output the messageIdentifier - if there is one
767
+ if (this.messageIdentifier != undefined)
768
+ pos = writeUint16 (this.messageIdentifier, byteStream, pos);
769
+
770
+ switch(this.type) {
771
+ case MESSAGE_TYPE.CONNECT:
772
+ pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos);
773
+ if (this.willMessage != undefined) {
774
+ pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos);
775
+ pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos);
776
+ byteStream.set(willMessagePayloadBytes, pos);
777
+ pos += willMessagePayloadBytes.byteLength;
778
+
779
+ }
780
+ if (this.userName != undefined)
781
+ pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos);
782
+ if (this.password != undefined)
783
+ pos = writeString(this.password, UTF8Length(this.password), byteStream, pos);
784
+ break;
785
+
786
+ case MESSAGE_TYPE.PUBLISH:
787
+ // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters.
788
+ byteStream.set(payloadBytes, pos);
789
+
790
+ break;
791
+
792
+ // case MESSAGE_TYPE.PUBREC:
793
+ // case MESSAGE_TYPE.PUBREL:
794
+ // case MESSAGE_TYPE.PUBCOMP:
795
+ // break;
796
+
797
+ case MESSAGE_TYPE.SUBSCRIBE:
798
+ // SUBSCRIBE has a list of topic strings and request QoS
799
+ for (var i=0; i<this.topics.length; i++) {
800
+ pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
801
+ byteStream[pos++] = this.requestedQos[i];
802
+ }
803
+ break;
804
+
805
+ case MESSAGE_TYPE.UNSUBSCRIBE:
806
+ // UNSUBSCRIBE has a list of topic strings
807
+ for (var i=0; i<this.topics.length; i++)
808
+ pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
809
+ break;
810
+
811
+ default:
812
+ // Do nothing.
813
+ }
814
+
815
+ return buffer;
816
+ }
817
+
818
+ function decodeMessage(input,pos) {
819
+ var startingPos = pos;
820
+ var first = input[pos];
821
+ var type = first >> 4;
822
+ var messageInfo = first &= 0x0f;
823
+ pos += 1;
824
+
825
+
826
+ // Decode the remaining length (MBI format)
827
+
828
+ var digit;
829
+ var remLength = 0;
830
+ var multiplier = 1;
831
+ do {
832
+ if (pos == input.length) {
833
+ return [null,startingPos];
834
+ }
835
+ digit = input[pos++];
836
+ remLength += ((digit & 0x7F) * multiplier);
837
+ multiplier *= 128;
838
+ } while ((digit & 0x80) != 0);
839
+
840
+ var endPos = pos+remLength;
841
+ if (endPos > input.length) {
842
+ return [null,startingPos];
843
+ }
844
+
845
+ var wireMessage = new WireMessage(type);
846
+ switch(type) {
847
+ case MESSAGE_TYPE.CONNACK:
848
+ var connectAcknowledgeFlags = input[pos++];
849
+ if (connectAcknowledgeFlags & 0x01)
850
+ wireMessage.sessionPresent = true;
851
+ wireMessage.returnCode = input[pos++];
852
+ break;
853
+
854
+ case MESSAGE_TYPE.PUBLISH:
855
+ var qos = (messageInfo >> 1) & 0x03;
856
+
857
+ var len = readUint16(input, pos);
858
+ pos += 2;
859
+ var topicName = parseUTF8(input, pos, len);
860
+ pos += len;
861
+ // If QoS 1 or 2 there will be a messageIdentifier
862
+ if (qos > 0) {
863
+ wireMessage.messageIdentifier = readUint16(input, pos);
864
+ pos += 2;
865
+ }
866
+
867
+ var message = new Paho.MQTT.Message(input.subarray(pos, endPos));
868
+ if ((messageInfo & 0x01) == 0x01)
869
+ message.retained = true;
870
+ if ((messageInfo & 0x08) == 0x08)
871
+ message.duplicate = true;
872
+ message.qos = qos;
873
+ message.destinationName = topicName;
874
+ wireMessage.payloadMessage = message;
875
+ break;
876
+
877
+ case MESSAGE_TYPE.PUBACK:
878
+ case MESSAGE_TYPE.PUBREC:
879
+ case MESSAGE_TYPE.PUBREL:
880
+ case MESSAGE_TYPE.PUBCOMP:
881
+ case MESSAGE_TYPE.UNSUBACK:
882
+ wireMessage.messageIdentifier = readUint16(input, pos);
883
+ break;
884
+
885
+ case MESSAGE_TYPE.SUBACK:
886
+ wireMessage.messageIdentifier = readUint16(input, pos);
887
+ pos += 2;
888
+ wireMessage.returnCode = input.subarray(pos, endPos);
889
+ break;
890
+
891
+ default:
892
+ ;
893
+ }
894
+
895
+ return [wireMessage,endPos];
896
+ }
897
+
898
+ function writeUint16(input, buffer, offset) {
899
+ buffer[offset++] = input >> 8; //MSB
900
+ buffer[offset++] = input % 256; //LSB
901
+ return offset;
902
+ }
903
+
904
+ function writeString(input, utf8Length, buffer, offset) {
905
+ offset = writeUint16(utf8Length, buffer, offset);
906
+ stringToUTF8(input, buffer, offset);
907
+ return offset + utf8Length;
908
+ }
909
+
910
+ function readUint16(buffer, offset) {
911
+ return 256*buffer[offset] + buffer[offset+1];
912
+ }
913
+
914
+ /**
915
+ * Encodes an MQTT Multi-Byte Integer
916
+ * @private
917
+ */
918
+ function encodeMBI(number) {
919
+ var output = new Array(1);
920
+ var numBytes = 0;
921
+
922
+ do {
923
+ var digit = number % 128;
924
+ number = number >> 7;
925
+ if (number > 0) {
926
+ digit |= 0x80;
927
+ }
928
+ output[numBytes++] = digit;
929
+ } while ( (number > 0) && (numBytes<4) );
930
+
931
+ return output;
932
+ }
933
+
934
+ /**
935
+ * Takes a String and calculates its length in bytes when encoded in UTF8.
936
+ * @private
937
+ */
938
+ function UTF8Length(input) {
939
+ var output = 0;
940
+ for (var i = 0; i<input.length; i++)
941
+ {
942
+ var charCode = input.charCodeAt(i);
943
+ if (charCode > 0x7FF)
944
+ {
945
+ // Surrogate pair means its a 4 byte character
946
+ if (0xD800 <= charCode && charCode <= 0xDBFF)
947
+ {
948
+ i++;
949
+ output++;
950
+ }
951
+ output +=3;
952
+ }
953
+ else if (charCode > 0x7F)
954
+ output +=2;
955
+ else
956
+ output++;
957
+ }
958
+ return output;
959
+ }
960
+
961
+ /**
962
+ * Takes a String and writes it into an array as UTF8 encoded bytes.
963
+ * @private
964
+ */
965
+ function stringToUTF8(input, output, start) {
966
+ var pos = start;
967
+ for (var i = 0; i<input.length; i++) {
968
+ var charCode = input.charCodeAt(i);
969
+
970
+ // Check for a surrogate pair.
971
+ if (0xD800 <= charCode && charCode <= 0xDBFF) {
972
+ var lowCharCode = input.charCodeAt(++i);
973
+ if (isNaN(lowCharCode)) {
974
+ throw new Error(format(ERROR.MALFORMED_UNICODE, [charCode, lowCharCode]));
975
+ }
976
+ charCode = ((charCode - 0xD800)<<10) + (lowCharCode - 0xDC00) + 0x10000;
977
+
978
+ }
979
+
980
+ if (charCode <= 0x7F) {
981
+ output[pos++] = charCode;
982
+ } else if (charCode <= 0x7FF) {
983
+ output[pos++] = charCode>>6 & 0x1F | 0xC0;
984
+ output[pos++] = charCode & 0x3F | 0x80;
985
+ } else if (charCode <= 0xFFFF) {
986
+ output[pos++] = charCode>>12 & 0x0F | 0xE0;
987
+ output[pos++] = charCode>>6 & 0x3F | 0x80;
988
+ output[pos++] = charCode & 0x3F | 0x80;
989
+ } else {
990
+ output[pos++] = charCode>>18 & 0x07 | 0xF0;
991
+ output[pos++] = charCode>>12 & 0x3F | 0x80;
992
+ output[pos++] = charCode>>6 & 0x3F | 0x80;
993
+ output[pos++] = charCode & 0x3F | 0x80;
994
+ };
995
+ }
996
+ return output;
997
+ }
998
+
999
+ function parseUTF8(input, offset, length) {
1000
+ var output = "";
1001
+ var utf16;
1002
+ var pos = offset;
1003
+
1004
+ while (pos < offset+length)
1005
+ {
1006
+ var byte1 = input[pos++];
1007
+ if (byte1 < 128)
1008
+ utf16 = byte1;
1009
+ else
1010
+ {
1011
+ var byte2 = input[pos++]-128;
1012
+ if (byte2 < 0)
1013
+ throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""]));
1014
+ if (byte1 < 0xE0) // 2 byte character
1015
+ utf16 = 64*(byte1-0xC0) + byte2;
1016
+ else
1017
+ {
1018
+ var byte3 = input[pos++]-128;
1019
+ if (byte3 < 0)
1020
+ throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)]));
1021
+ if (byte1 < 0xF0) // 3 byte character
1022
+ utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3;
1023
+ else
1024
+ {
1025
+ var byte4 = input[pos++]-128;
1026
+ if (byte4 < 0)
1027
+ throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
1028
+ if (byte1 < 0xF8) // 4 byte character
1029
+ utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4;
1030
+ else // longer encodings are not supported
1031
+ throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
1032
+ }
1033
+ }
1034
+ }
1035
+
1036
+ if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair
1037
+ {
1038
+ utf16 -= 0x10000;
1039
+ output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character
1040
+ utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character
1041
+ }
1042
+ output += String.fromCharCode(utf16);
1043
+ }
1044
+ return output;
1045
+ }
1046
+
1047
+ /**
1048
+ * Repeat keepalive requests, monitor responses.
1049
+ * @ignore
1050
+ */
1051
+ var Pinger = function(client, window, keepAliveInterval) {
1052
+ this._client = client;
1053
+ this._window = window;
1054
+ this._keepAliveInterval = keepAliveInterval*1000;
1055
+ this.isReset = false;
1056
+
1057
+ var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode();
1058
+
1059
+ var doTimeout = function (pinger) {
1060
+ return function () {
1061
+ return doPing.apply(pinger);
1062
+ };
1063
+ };
1064
+
1065
+ /** @ignore */
1066
+ var doPing = function() {
1067
+ if (!this.isReset) {
1068
+ this._client._trace("Pinger.doPing", "Timed out");
1069
+ this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT));
1070
+ } else {
1071
+ this.isReset = false;
1072
+ this._client._trace("Pinger.doPing", "send PINGREQ");
1073
+ this._client.socket.send(pingReq);
1074
+ this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval);
1075
+ }
1076
+ }
1077
+
1078
+ this.reset = function() {
1079
+ this.isReset = true;
1080
+ this._window.clearTimeout(this.timeout);
1081
+ if (this._keepAliveInterval > 0)
1082
+ this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval);
1083
+ }
1084
+
1085
+ this.cancel = function() {
1086
+ this._window.clearTimeout(this.timeout);
1087
+ }
1088
+ };
1089
+
1090
+ /**
1091
+ * Monitor request completion.
1092
+ * @ignore
1093
+ */
1094
+ var Timeout = function(client, window, timeoutSeconds, action, args) {
1095
+ this._window = window;
1096
+ if (!timeoutSeconds)
1097
+ timeoutSeconds = 30;
1098
+
1099
+ var doTimeout = function (action, client, args) {
1100
+ return function () {
1101
+ return action.apply(client, args);
1102
+ };
1103
+ };
1104
+ this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000);
1105
+
1106
+ this.cancel = function() {
1107
+ this._window.clearTimeout(this.timeout);
1108
+ }
1109
+ };
1110
+
1111
+ /*
1112
+ * Internal implementation of the Websockets MQTT V3.1 client.
1113
+ *
1114
+ * @name Paho.MQTT.ClientImpl @constructor
1115
+ * @param {String} host the DNS nameof the webSocket host.
1116
+ * @param {Number} port the port number for that host.
1117
+ * @param {String} clientId the MQ client identifier.
1118
+ */
1119
+ var ClientImpl = function (uri, host, port, path, clientId) {
1120
+ // Check dependencies are satisfied in this browser.
1121
+ if (!("WebSocket" in global && global["WebSocket"] !== null)) {
1122
+ throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"]));
1123
+ }
1124
+ if (!("localStorage" in global && global["localStorage"] !== null)) {
1125
+ throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"]));
1126
+ }
1127
+ if (!("ArrayBuffer" in global && global["ArrayBuffer"] !== null)) {
1128
+ throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"]));
1129
+ }
1130
+ this._trace("Paho.MQTT.Client", uri, host, port, path, clientId);
1131
+
1132
+ this.host = host;
1133
+ this.port = port;
1134
+ this.path = path;
1135
+ this.uri = uri;
1136
+ this.clientId = clientId;
1137
+
1138
+ // Local storagekeys are qualified with the following string.
1139
+ // The conditional inclusion of path in the key is for backward
1140
+ // compatibility to when the path was not configurable and assumed to
1141
+ // be /mqtt
1142
+ this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":";
1143
+
1144
+ // Create private instance-only message queue
1145
+ // Internal queue of messages to be sent, in sending order.
1146
+ this._msg_queue = [];
1147
+
1148
+ // Messages we have sent and are expecting a response for, indexed by their respective message ids.
1149
+ this._sentMessages = {};
1150
+
1151
+ // Messages we have received and acknowleged and are expecting a confirm message for
1152
+ // indexed by their respective message ids.
1153
+ this._receivedMessages = {};
1154
+
1155
+ // Internal list of callbacks to be executed when messages
1156
+ // have been successfully sent over web socket, e.g. disconnect
1157
+ // when it doesn't have to wait for ACK, just message is dispatched.
1158
+ this._notify_msg_sent = {};
1159
+
1160
+ // Unique identifier for SEND messages, incrementing
1161
+ // counter as messages are sent.
1162
+ this._message_identifier = 1;
1163
+
1164
+ // Used to determine the transmission sequence of stored sent messages.
1165
+ this._sequence = 0;
1166
+
1167
+
1168
+ // Load the local state, if any, from the saved version, only restore state relevant to this client.
1169
+ for (var key in localStorage)
1170
+ if ( key.indexOf("Sent:"+this._localKey) == 0
1171
+ || key.indexOf("Received:"+this._localKey) == 0)
1172
+ this.restore(key);
1173
+ };
1174
+
1175
+ // Messaging Client public instance members.
1176
+ ClientImpl.prototype.host;
1177
+ ClientImpl.prototype.port;
1178
+ ClientImpl.prototype.path;
1179
+ ClientImpl.prototype.uri;
1180
+ ClientImpl.prototype.clientId;
1181
+
1182
+ // Messaging Client private instance members.
1183
+ ClientImpl.prototype.socket;
1184
+ /* true once we have received an acknowledgement to a CONNECT packet. */
1185
+ ClientImpl.prototype.connected = false;
1186
+ /* The largest message identifier allowed, may not be larger than 2**16 but
1187
+ * if set smaller reduces the maximum number of outbound messages allowed.
1188
+ */
1189
+ ClientImpl.prototype.maxMessageIdentifier = 65536;
1190
+ ClientImpl.prototype.connectOptions;
1191
+ ClientImpl.prototype.hostIndex;
1192
+ ClientImpl.prototype.onConnectionLost;
1193
+ ClientImpl.prototype.onMessageDelivered;
1194
+ ClientImpl.prototype.onMessageArrived;
1195
+ ClientImpl.prototype.traceFunction;
1196
+ ClientImpl.prototype._msg_queue = null;
1197
+ ClientImpl.prototype._connectTimeout;
1198
+ /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */
1199
+ ClientImpl.prototype.sendPinger = null;
1200
+ /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */
1201
+ ClientImpl.prototype.receivePinger = null;
1202
+
1203
+ ClientImpl.prototype.receiveBuffer = null;
1204
+
1205
+ ClientImpl.prototype._traceBuffer = null;
1206
+ ClientImpl.prototype._MAX_TRACE_ENTRIES = 100;
1207
+
1208
+ ClientImpl.prototype.connect = function (connectOptions) {
1209
+ var connectOptionsMasked = this._traceMask(connectOptions, "password");
1210
+ this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected);
1211
+
1212
+ if (this.connected)
1213
+ throw new Error(format(ERROR.INVALID_STATE, ["already connected"]));
1214
+ if (this.socket)
1215
+ throw new Error(format(ERROR.INVALID_STATE, ["already connected"]));
1216
+
1217
+ this.connectOptions = connectOptions;
1218
+
1219
+ if (connectOptions.uris) {
1220
+ this.hostIndex = 0;
1221
+ this._doConnect(connectOptions.uris[0]);
1222
+ } else {
1223
+ this._doConnect(this.uri);
1224
+ }
1225
+
1226
+ };
1227
+
1228
+ ClientImpl.prototype.subscribe = function (filter, subscribeOptions) {
1229
+ this._trace("Client.subscribe", filter, subscribeOptions);
1230
+
1231
+ if (!this.connected)
1232
+ throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
1233
+
1234
+ var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE);
1235
+ wireMessage.topics=[filter];
1236
+ if (subscribeOptions.qos != undefined)
1237
+ wireMessage.requestedQos = [subscribeOptions.qos];
1238
+ else
1239
+ wireMessage.requestedQos = [0];
1240
+
1241
+ if (subscribeOptions.onSuccess) {
1242
+ wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});};
1243
+ }
1244
+
1245
+ if (subscribeOptions.onFailure) {
1246
+ wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});};
1247
+ }
1248
+
1249
+ if (subscribeOptions.timeout) {
1250
+ wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure
1251
+ , [{invocationContext:subscribeOptions.invocationContext,
1252
+ errorCode:ERROR.SUBSCRIBE_TIMEOUT.code,
1253
+ errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]);
1254
+ }
1255
+
1256
+ // All subscriptions return a SUBACK.
1257
+ this._requires_ack(wireMessage);
1258
+ this._schedule_message(wireMessage);
1259
+ };
1260
+
1261
+ /** @ignore */
1262
+ ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) {
1263
+ this._trace("Client.unsubscribe", filter, unsubscribeOptions);
1264
+
1265
+ if (!this.connected)
1266
+ throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
1267
+
1268
+ var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE);
1269
+ wireMessage.topics = [filter];
1270
+
1271
+ if (unsubscribeOptions.onSuccess) {
1272
+ wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});};
1273
+ }
1274
+ if (unsubscribeOptions.timeout) {
1275
+ wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure
1276
+ , [{invocationContext:unsubscribeOptions.invocationContext,
1277
+ errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code,
1278
+ errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]);
1279
+ }
1280
+
1281
+ // All unsubscribes return a SUBACK.
1282
+ this._requires_ack(wireMessage);
1283
+ this._schedule_message(wireMessage);
1284
+ };
1285
+
1286
+ ClientImpl.prototype.send = function (message) {
1287
+ this._trace("Client.send", message);
1288
+
1289
+ if (!this.connected)
1290
+ throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
1291
+
1292
+ wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH);
1293
+ wireMessage.payloadMessage = message;
1294
+
1295
+ if (message.qos > 0)
1296
+ this._requires_ack(wireMessage);
1297
+ else if (this.onMessageDelivered)
1298
+ this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage);
1299
+ this._schedule_message(wireMessage);
1300
+ };
1301
+
1302
+ ClientImpl.prototype.disconnect = function () {
1303
+ this._trace("Client.disconnect");
1304
+
1305
+ if (!this.socket)
1306
+ throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"]));
1307
+
1308
+ wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT);
1309
+
1310
+ // Run the disconnected call back as soon as the message has been sent,
1311
+ // in case of a failure later on in the disconnect processing.
1312
+ // as a consequence, the _disconected call back may be run several times.
1313
+ this._notify_msg_sent[wireMessage] = scope(this._disconnected, this);
1314
+
1315
+ this._schedule_message(wireMessage);
1316
+ };
1317
+
1318
+ ClientImpl.prototype.getTraceLog = function () {
1319
+ if ( this._traceBuffer !== null ) {
1320
+ this._trace("Client.getTraceLog", new Date());
1321
+ this._trace("Client.getTraceLog in flight messages", this._sentMessages.length);
1322
+ for (var key in this._sentMessages)
1323
+ this._trace("_sentMessages ",key, this._sentMessages[key]);
1324
+ for (var key in this._receivedMessages)
1325
+ this._trace("_receivedMessages ",key, this._receivedMessages[key]);
1326
+
1327
+ return this._traceBuffer;
1328
+ }
1329
+ };
1330
+
1331
+ ClientImpl.prototype.startTrace = function () {
1332
+ if ( this._traceBuffer === null ) {
1333
+ this._traceBuffer = [];
1334
+ }
1335
+ this._trace("Client.startTrace", new Date(), version);
1336
+ };
1337
+
1338
+ ClientImpl.prototype.stopTrace = function () {
1339
+ delete this._traceBuffer;
1340
+ };
1341
+
1342
+ ClientImpl.prototype._doConnect = function (wsurl) {
1343
+ // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters.
1344
+ if (this.connectOptions.useSSL) {
1345
+ var uriParts = wsurl.split(":");
1346
+ uriParts[0] = "wss";
1347
+ wsurl = uriParts.join(":");
1348
+ }
1349
+ this.connected = false;
1350
+ if (this.connectOptions.mqttVersion < 4) {
1351
+ this.socket = new WebSocket(wsurl, ["mqttv3.1"]);
1352
+ } else {
1353
+ this.socket = new WebSocket(wsurl, ["mqtt"]);
1354
+ }
1355
+ this.socket.binaryType = 'arraybuffer';
1356
+
1357
+ this.socket.onopen = scope(this._on_socket_open, this);
1358
+ this.socket.onmessage = scope(this._on_socket_message, this);
1359
+ this.socket.onerror = scope(this._on_socket_error, this);
1360
+ this.socket.onclose = scope(this._on_socket_close, this);
1361
+
1362
+ this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
1363
+ this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
1364
+
1365
+ this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]);
1366
+ };
1367
+
1368
+
1369
+ // Schedule a new message to be sent over the WebSockets
1370
+ // connection. CONNECT messages cause WebSocket connection
1371
+ // to be started. All other messages are queued internally
1372
+ // until this has happened. When WS connection starts, process
1373
+ // all outstanding messages.
1374
+ ClientImpl.prototype._schedule_message = function (message) {
1375
+ this._msg_queue.push(message);
1376
+ // Process outstanding messages in the queue if we have an open socket, and have received CONNACK.
1377
+ if (this.connected) {
1378
+ this._process_queue();
1379
+ }
1380
+ };
1381
+
1382
+ ClientImpl.prototype.store = function(prefix, wireMessage) {
1383
+ var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1};
1384
+
1385
+ switch(wireMessage.type) {
1386
+ case MESSAGE_TYPE.PUBLISH:
1387
+ if(wireMessage.pubRecReceived)
1388
+ storedMessage.pubRecReceived = true;
1389
+
1390
+ // Convert the payload to a hex string.
1391
+ storedMessage.payloadMessage = {};
1392
+ var hex = "";
1393
+ var messageBytes = wireMessage.payloadMessage.payloadBytes;
1394
+ for (var i=0; i<messageBytes.length; i++) {
1395
+ if (messageBytes[i] <= 0xF)
1396
+ hex = hex+"0"+messageBytes[i].toString(16);
1397
+ else
1398
+ hex = hex+messageBytes[i].toString(16);
1399
+ }
1400
+ storedMessage.payloadMessage.payloadHex = hex;
1401
+
1402
+ storedMessage.payloadMessage.qos = wireMessage.payloadMessage.qos;
1403
+ storedMessage.payloadMessage.destinationName = wireMessage.payloadMessage.destinationName;
1404
+ if (wireMessage.payloadMessage.duplicate)
1405
+ storedMessage.payloadMessage.duplicate = true;
1406
+ if (wireMessage.payloadMessage.retained)
1407
+ storedMessage.payloadMessage.retained = true;
1408
+
1409
+ // Add a sequence number to sent messages.
1410
+ if ( prefix.indexOf("Sent:") == 0 ) {
1411
+ if ( wireMessage.sequence === undefined )
1412
+ wireMessage.sequence = ++this._sequence;
1413
+ storedMessage.sequence = wireMessage.sequence;
1414
+ }
1415
+ break;
1416
+
1417
+ default:
1418
+ throw Error(format(ERROR.INVALID_STORED_DATA, [key, storedMessage]));
1419
+ }
1420
+ localStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage));
1421
+ };
1422
+
1423
+ ClientImpl.prototype.restore = function(key) {
1424
+ var value = localStorage.getItem(key);
1425
+ var storedMessage = JSON.parse(value);
1426
+
1427
+ var wireMessage = new WireMessage(storedMessage.type, storedMessage);
1428
+
1429
+ switch(storedMessage.type) {
1430
+ case MESSAGE_TYPE.PUBLISH:
1431
+ // Replace the payload message with a Message object.
1432
+ var hex = storedMessage.payloadMessage.payloadHex;
1433
+ var buffer = new ArrayBuffer((hex.length)/2);
1434
+ var byteStream = new Uint8Array(buffer);
1435
+ var i = 0;
1436
+ while (hex.length >= 2) {
1437
+ var x = parseInt(hex.substring(0, 2), 16);
1438
+ hex = hex.substring(2, hex.length);
1439
+ byteStream[i++] = x;
1440
+ }
1441
+ var payloadMessage = new Paho.MQTT.Message(byteStream);
1442
+
1443
+ payloadMessage.qos = storedMessage.payloadMessage.qos;
1444
+ payloadMessage.destinationName = storedMessage.payloadMessage.destinationName;
1445
+ if (storedMessage.payloadMessage.duplicate)
1446
+ payloadMessage.duplicate = true;
1447
+ if (storedMessage.payloadMessage.retained)
1448
+ payloadMessage.retained = true;
1449
+ wireMessage.payloadMessage = payloadMessage;
1450
+
1451
+ break;
1452
+
1453
+ default:
1454
+ throw Error(format(ERROR.INVALID_STORED_DATA, [key, value]));
1455
+ }
1456
+
1457
+ if (key.indexOf("Sent:"+this._localKey) == 0) {
1458
+ wireMessage.payloadMessage.duplicate = true;
1459
+ this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
1460
+ } else if (key.indexOf("Received:"+this._localKey) == 0) {
1461
+ this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
1462
+ }
1463
+ };
1464
+
1465
+ ClientImpl.prototype._process_queue = function () {
1466
+ var message = null;
1467
+ // Process messages in order they were added
1468
+ var fifo = this._msg_queue.reverse();
1469
+
1470
+ // Send all queued messages down socket connection
1471
+ while ((message = fifo.pop())) {
1472
+ this._socket_send(message);
1473
+ // Notify listeners that message was successfully sent
1474
+ if (this._notify_msg_sent[message]) {
1475
+ this._notify_msg_sent[message]();
1476
+ delete this._notify_msg_sent[message];
1477
+ }
1478
+ }
1479
+ };
1480
+
1481
+ /**
1482
+ * Expect an ACK response for this message. Add message to the set of in progress
1483
+ * messages and set an unused identifier in this message.
1484
+ * @ignore
1485
+ */
1486
+ ClientImpl.prototype._requires_ack = function (wireMessage) {
1487
+ var messageCount = Object.keys(this._sentMessages).length;
1488
+ if (messageCount > this.maxMessageIdentifier)
1489
+ throw Error ("Too many messages:"+messageCount);
1490
+
1491
+ while(this._sentMessages[this._message_identifier] !== undefined) {
1492
+ this._message_identifier++;
1493
+ }
1494
+ wireMessage.messageIdentifier = this._message_identifier;
1495
+ this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
1496
+ if (wireMessage.type === MESSAGE_TYPE.PUBLISH) {
1497
+ this.store("Sent:", wireMessage);
1498
+ }
1499
+ if (this._message_identifier === this.maxMessageIdentifier) {
1500
+ this._message_identifier = 1;
1501
+ }
1502
+ };
1503
+
1504
+ /**
1505
+ * Called when the underlying websocket has been opened.
1506
+ * @ignore
1507
+ */
1508
+ ClientImpl.prototype._on_socket_open = function () {
1509
+ // Create the CONNECT message object.
1510
+ var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions);
1511
+ wireMessage.clientId = this.clientId;
1512
+ this._socket_send(wireMessage);
1513
+ };
1514
+
1515
+ /**
1516
+ * Called when the underlying websocket has received a complete packet.
1517
+ * @ignore
1518
+ */
1519
+ ClientImpl.prototype._on_socket_message = function (event) {
1520
+ this._trace("Client._on_socket_message", event.data);
1521
+ // Reset the receive ping timer, we now have evidence the server is alive.
1522
+ this.receivePinger.reset();
1523
+ var messages = this._deframeMessages(event.data);
1524
+ for (var i = 0; i < messages.length; i+=1) {
1525
+ this._handleMessage(messages[i]);
1526
+ }
1527
+ }
1528
+
1529
+ ClientImpl.prototype._deframeMessages = function(data) {
1530
+ var byteArray = new Uint8Array(data);
1531
+ if (this.receiveBuffer) {
1532
+ var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length);
1533
+ newData.set(this.receiveBuffer);
1534
+ newData.set(byteArray,this.receiveBuffer.length);
1535
+ byteArray = newData;
1536
+ delete this.receiveBuffer;
1537
+ }
1538
+ try {
1539
+ var offset = 0;
1540
+ var messages = [];
1541
+ while(offset < byteArray.length) {
1542
+ var result = decodeMessage(byteArray,offset);
1543
+ var wireMessage = result[0];
1544
+ offset = result[1];
1545
+ if (wireMessage !== null) {
1546
+ messages.push(wireMessage);
1547
+ } else {
1548
+ break;
1549
+ }
1550
+ }
1551
+ if (offset < byteArray.length) {
1552
+ this.receiveBuffer = byteArray.subarray(offset);
1553
+ }
1554
+ } catch (error) {
1555
+ this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
1556
+ return;
1557
+ }
1558
+ return messages;
1559
+ }
1560
+
1561
+ ClientImpl.prototype._handleMessage = function(wireMessage) {
1562
+
1563
+ this._trace("Client._handleMessage", wireMessage);
1564
+
1565
+ try {
1566
+ switch(wireMessage.type) {
1567
+ case MESSAGE_TYPE.CONNACK:
1568
+ this._connectTimeout.cancel();
1569
+
1570
+ // If we have started using clean session then clear up the local state.
1571
+ if (this.connectOptions.cleanSession) {
1572
+ for (var key in this._sentMessages) {
1573
+ var sentMessage = this._sentMessages[key];
1574
+ localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier);
1575
+ }
1576
+ this._sentMessages = {};
1577
+
1578
+ for (var key in this._receivedMessages) {
1579
+ var receivedMessage = this._receivedMessages[key];
1580
+ localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier);
1581
+ }
1582
+ this._receivedMessages = {};
1583
+ }
1584
+ // Client connected and ready for business.
1585
+ if (wireMessage.returnCode === 0) {
1586
+ this.connected = true;
1587
+ // Jump to the end of the list of uris and stop looking for a good host.
1588
+ if (this.connectOptions.uris)
1589
+ this.hostIndex = this.connectOptions.uris.length;
1590
+ } else {
1591
+ this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]]));
1592
+ break;
1593
+ }
1594
+
1595
+ // Resend messages.
1596
+ var sequencedMessages = new Array();
1597
+ for (var msgId in this._sentMessages) {
1598
+ if (this._sentMessages.hasOwnProperty(msgId))
1599
+ sequencedMessages.push(this._sentMessages[msgId]);
1600
+ }
1601
+
1602
+ // Sort sentMessages into the original sent order.
1603
+ var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} );
1604
+ for (var i=0, len=sequencedMessages.length; i<len; i++) {
1605
+ var sentMessage = sequencedMessages[i];
1606
+ if (sentMessage.type == MESSAGE_TYPE.PUBLISH && sentMessage.pubRecReceived) {
1607
+ var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:sentMessage.messageIdentifier});
1608
+ this._schedule_message(pubRelMessage);
1609
+ } else {
1610
+ this._schedule_message(sentMessage);
1611
+ };
1612
+ }
1613
+
1614
+ // Execute the connectOptions.onSuccess callback if there is one.
1615
+ if (this.connectOptions.onSuccess) {
1616
+ this.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});
1617
+ }
1618
+
1619
+ // Process all queued messages now that the connection is established.
1620
+ this._process_queue();
1621
+ break;
1622
+
1623
+ case MESSAGE_TYPE.PUBLISH:
1624
+ this._receivePublish(wireMessage);
1625
+ break;
1626
+
1627
+ case MESSAGE_TYPE.PUBACK:
1628
+ var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
1629
+ // If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist.
1630
+ if (sentMessage) {
1631
+ delete this._sentMessages[wireMessage.messageIdentifier];
1632
+ localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
1633
+ if (this.onMessageDelivered)
1634
+ this.onMessageDelivered(sentMessage.payloadMessage);
1635
+ }
1636
+ break;
1637
+
1638
+ case MESSAGE_TYPE.PUBREC:
1639
+ var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
1640
+ // If this is a re flow of a PUBREC after we have restarted receivedMessage will not exist.
1641
+ if (sentMessage) {
1642
+ sentMessage.pubRecReceived = true;
1643
+ var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:wireMessage.messageIdentifier});
1644
+ this.store("Sent:", sentMessage);
1645
+ this._schedule_message(pubRelMessage);
1646
+ }
1647
+ break;
1648
+
1649
+ case MESSAGE_TYPE.PUBREL:
1650
+ var receivedMessage = this._receivedMessages[wireMessage.messageIdentifier];
1651
+ localStorage.removeItem("Received:"+this._localKey+wireMessage.messageIdentifier);
1652
+ // If this is a re flow of a PUBREL after we have restarted receivedMessage will not exist.
1653
+ if (receivedMessage) {
1654
+ this._receiveMessage(receivedMessage);
1655
+ delete this._receivedMessages[wireMessage.messageIdentifier];
1656
+ }
1657
+ // Always flow PubComp, we may have previously flowed PubComp but the server lost it and restarted.
1658
+ var pubCompMessage = new WireMessage(MESSAGE_TYPE.PUBCOMP, {messageIdentifier:wireMessage.messageIdentifier});
1659
+ this._schedule_message(pubCompMessage);
1660
+ break;
1661
+
1662
+ case MESSAGE_TYPE.PUBCOMP:
1663
+ var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
1664
+ delete this._sentMessages[wireMessage.messageIdentifier];
1665
+ localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
1666
+ if (this.onMessageDelivered)
1667
+ this.onMessageDelivered(sentMessage.payloadMessage);
1668
+ break;
1669
+
1670
+ case MESSAGE_TYPE.SUBACK:
1671
+ var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
1672
+ if (sentMessage) {
1673
+ if(sentMessage.timeOut)
1674
+ sentMessage.timeOut.cancel();
1675
+ wireMessage.returnCode.indexOf = Array.prototype.indexOf;
1676
+ if (wireMessage.returnCode.indexOf(0x80) !== -1) {
1677
+ if (sentMessage.onFailure) {
1678
+ sentMessage.onFailure(wireMessage.returnCode);
1679
+ }
1680
+ } else if (sentMessage.onSuccess) {
1681
+ sentMessage.onSuccess(wireMessage.returnCode);
1682
+ }
1683
+ delete this._sentMessages[wireMessage.messageIdentifier];
1684
+ }
1685
+ break;
1686
+
1687
+ case MESSAGE_TYPE.UNSUBACK:
1688
+ var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
1689
+ if (sentMessage) {
1690
+ if (sentMessage.timeOut)
1691
+ sentMessage.timeOut.cancel();
1692
+ if (sentMessage.callback) {
1693
+ sentMessage.callback();
1694
+ }
1695
+ delete this._sentMessages[wireMessage.messageIdentifier];
1696
+ }
1697
+
1698
+ break;
1699
+
1700
+ case MESSAGE_TYPE.PINGRESP:
1701
+ /* The sendPinger or receivePinger may have sent a ping, the receivePinger has already been reset. */
1702
+ this.sendPinger.reset();
1703
+ break;
1704
+
1705
+ case MESSAGE_TYPE.DISCONNECT:
1706
+ // Clients do not expect to receive disconnect packets.
1707
+ this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
1708
+ break;
1709
+
1710
+ default:
1711
+ this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
1712
+ };
1713
+ } catch (error) {
1714
+ this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
1715
+ return;
1716
+ }
1717
+ };
1718
+
1719
+ /** @ignore */
1720
+ ClientImpl.prototype._on_socket_error = function (error) {
1721
+ this._disconnected(ERROR.SOCKET_ERROR.code , format(ERROR.SOCKET_ERROR, [error.data]));
1722
+ };
1723
+
1724
+ /** @ignore */
1725
+ ClientImpl.prototype._on_socket_close = function () {
1726
+ this._disconnected(ERROR.SOCKET_CLOSE.code , format(ERROR.SOCKET_CLOSE));
1727
+ };
1728
+
1729
+ /** @ignore */
1730
+ ClientImpl.prototype._socket_send = function (wireMessage) {
1731
+
1732
+ if (wireMessage.type == 1) {
1733
+ var wireMessageMasked = this._traceMask(wireMessage, "password");
1734
+ this._trace("Client._socket_send", wireMessageMasked);
1735
+ }
1736
+ else this._trace("Client._socket_send", wireMessage);
1737
+
1738
+ this.socket.send(wireMessage.encode());
1739
+ /* We have proved to the server we are alive. */
1740
+ this.sendPinger.reset();
1741
+ };
1742
+
1743
+ /** @ignore */
1744
+ ClientImpl.prototype._receivePublish = function (wireMessage) {
1745
+ switch(wireMessage.payloadMessage.qos) {
1746
+ case "undefined":
1747
+ case 0:
1748
+ this._receiveMessage(wireMessage);
1749
+ break;
1750
+
1751
+ case 1:
1752
+ var pubAckMessage = new WireMessage(MESSAGE_TYPE.PUBACK, {messageIdentifier:wireMessage.messageIdentifier});
1753
+ this._schedule_message(pubAckMessage);
1754
+ this._receiveMessage(wireMessage);
1755
+ break;
1756
+
1757
+ case 2:
1758
+ this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
1759
+ this.store("Received:", wireMessage);
1760
+ var pubRecMessage = new WireMessage(MESSAGE_TYPE.PUBREC, {messageIdentifier:wireMessage.messageIdentifier});
1761
+ this._schedule_message(pubRecMessage);
1762
+
1763
+ break;
1764
+
1765
+ default:
1766
+ throw Error("Invaild qos="+wireMmessage.payloadMessage.qos);
1767
+ };
1768
+ };
1769
+
1770
+ /** @ignore */
1771
+ ClientImpl.prototype._receiveMessage = function (wireMessage) {
1772
+ if (this.onMessageArrived) {
1773
+ this.onMessageArrived(wireMessage.payloadMessage);
1774
+ }
1775
+ };
1776
+
1777
+ /**
1778
+ * Client has disconnected either at its own request or because the server
1779
+ * or network disconnected it. Remove all non-durable state.
1780
+ * @param {errorCode} [number] the error number.
1781
+ * @param {errorText} [string] the error text.
1782
+ * @ignore
1783
+ */
1784
+ ClientImpl.prototype._disconnected = function (errorCode, errorText) {
1785
+ this._trace("Client._disconnected", errorCode, errorText);
1786
+
1787
+ this.sendPinger.cancel();
1788
+ this.receivePinger.cancel();
1789
+ if (this._connectTimeout)
1790
+ this._connectTimeout.cancel();
1791
+ // Clear message buffers.
1792
+ this._msg_queue = [];
1793
+ this._notify_msg_sent = {};
1794
+
1795
+ if (this.socket) {
1796
+ // Cancel all socket callbacks so that they cannot be driven again by this socket.
1797
+ this.socket.onopen = null;
1798
+ this.socket.onmessage = null;
1799
+ this.socket.onerror = null;
1800
+ this.socket.onclose = null;
1801
+ if (this.socket.readyState === 1)
1802
+ this.socket.close();
1803
+ delete this.socket;
1804
+ }
1805
+
1806
+ if (this.connectOptions.uris && this.hostIndex < this.connectOptions.uris.length-1) {
1807
+ // Try the next host.
1808
+ this.hostIndex++;
1809
+ this._doConnect(this.connectOptions.uris[this.hostIndex]);
1810
+
1811
+ } else {
1812
+
1813
+ if (errorCode === undefined) {
1814
+ errorCode = ERROR.OK.code;
1815
+ errorText = format(ERROR.OK);
1816
+ }
1817
+
1818
+ // Run any application callbacks last as they may attempt to reconnect and hence create a new socket.
1819
+ if (this.connected) {
1820
+ this.connected = false;
1821
+ // Execute the connectionLostCallback if there is one, and we were connected.
1822
+ if (this.onConnectionLost)
1823
+ this.onConnectionLost({errorCode:errorCode, errorMessage:errorText});
1824
+ } else {
1825
+ // Otherwise we never had a connection, so indicate that the connect has failed.
1826
+ if (this.connectOptions.mqttVersion === 4 && this.connectOptions.mqttVersionExplicit === false) {
1827
+ this._trace("Failed to connect V4, dropping back to V3")
1828
+ this.connectOptions.mqttVersion = 3;
1829
+ if (this.connectOptions.uris) {
1830
+ this.hostIndex = 0;
1831
+ this._doConnect(this.connectOptions.uris[0]);
1832
+ } else {
1833
+ this._doConnect(this.uri);
1834
+ }
1835
+ } else if(this.connectOptions.onFailure) {
1836
+ this.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext, errorCode:errorCode, errorMessage:errorText});
1837
+ }
1838
+ }
1839
+ }
1840
+ };
1841
+
1842
+ /** @ignore */
1843
+ ClientImpl.prototype._trace = function () {
1844
+ // Pass trace message back to client's callback function
1845
+ if (this.traceFunction) {
1846
+ for (var i in arguments)
1847
+ {
1848
+ if (typeof arguments[i] !== "undefined")
1849
+ arguments[i] = JSON.stringify(arguments[i]);
1850
+ }
1851
+ var record = Array.prototype.slice.call(arguments).join("");
1852
+ this.traceFunction ({severity: "Debug", message: record });
1853
+ }
1854
+
1855
+ //buffer style trace
1856
+ if ( this._traceBuffer !== null ) {
1857
+ for (var i = 0, max = arguments.length; i < max; i++) {
1858
+ if ( this._traceBuffer.length == this._MAX_TRACE_ENTRIES ) {
1859
+ this._traceBuffer.shift();
1860
+ }
1861
+ if (i === 0) this._traceBuffer.push(arguments[i]);
1862
+ else if (typeof arguments[i] === "undefined" ) this._traceBuffer.push(arguments[i]);
1863
+ else this._traceBuffer.push(" "+JSON.stringify(arguments[i]));
1864
+ };
1865
+ };
1866
+ };
1867
+
1868
+ /** @ignore */
1869
+ ClientImpl.prototype._traceMask = function (traceObject, masked) {
1870
+ var traceObjectMasked = {};
1871
+ for (var attr in traceObject) {
1872
+ if (traceObject.hasOwnProperty(attr)) {
1873
+ if (attr == masked)
1874
+ traceObjectMasked[attr] = "******";
1875
+ else
1876
+ traceObjectMasked[attr] = traceObject[attr];
1877
+ }
1878
+ }
1879
+ return traceObjectMasked;
1880
+ };
1881
+
1882
+ // ------------------------------------------------------------------------
1883
+ // Public Programming interface.
1884
+ // ------------------------------------------------------------------------
1885
+
1886
+ /**
1887
+ * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object.
1888
+ * <p>
1889
+ * Most applications will create just one Client object and then call its connect() method,
1890
+ * however applications can create more than one Client object if they wish.
1891
+ * In this case the combination of host, port and clientId attributes must be different for each Client object.
1892
+ * <p>
1893
+ * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods
1894
+ * (even though the underlying protocol exchange might be synchronous in nature).
1895
+ * This means they signal their completion by calling back to the application,
1896
+ * via Success or Failure callback functions provided by the application on the method in question.
1897
+ * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime
1898
+ * of the script that made the invocation.
1899
+ * <p>
1900
+ * In contrast there are some callback functions, most notably <i>onMessageArrived</i>,
1901
+ * that are defined on the {@link Paho.MQTT.Client} object.
1902
+ * These may get called multiple times, and aren't directly related to specific method invocations made by the client.
1903
+ *
1904
+ * @name Paho.MQTT.Client
1905
+ *
1906
+ * @constructor
1907
+ *
1908
+ * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address.
1909
+ * @param {number} port - the port number to connect to - only required if host is not a URI
1910
+ * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'.
1911
+ * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length.
1912
+ *
1913
+ * @property {string} host - <i>read only</i> the server's DNS hostname or dotted decimal IP address.
1914
+ * @property {number} port - <i>read only</i> the server's port.
1915
+ * @property {string} path - <i>read only</i> the server's path.
1916
+ * @property {string} clientId - <i>read only</i> used when connecting to the server.
1917
+ * @property {function} onConnectionLost - called when a connection has been lost.
1918
+ * after a connect() method has succeeded.
1919
+ * Establish the call back used when a connection has been lost. The connection may be
1920
+ * lost because the client initiates a disconnect or because the server or network
1921
+ * cause the client to be disconnected. The disconnect call back may be called without
1922
+ * the connectionComplete call back being invoked if, for example the client fails to
1923
+ * connect.
1924
+ * A single response object parameter is passed to the onConnectionLost callback containing the following fields:
1925
+ * <ol>
1926
+ * <li>errorCode
1927
+ * <li>errorMessage
1928
+ * </ol>
1929
+ * @property {function} onMessageDelivered called when a message has been delivered.
1930
+ * All processing that this Client will ever do has been completed. So, for example,
1931
+ * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server
1932
+ * and the message has been removed from persistent storage before this callback is invoked.
1933
+ * Parameters passed to the onMessageDelivered callback are:
1934
+ * <ol>
1935
+ * <li>{@link Paho.MQTT.Message} that was delivered.
1936
+ * </ol>
1937
+ * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client.
1938
+ * Parameters passed to the onMessageArrived callback are:
1939
+ * <ol>
1940
+ * <li>{@link Paho.MQTT.Message} that has arrived.
1941
+ * </ol>
1942
+ */
1943
+ var Client = function (host, port, path, clientId) {
1944
+
1945
+ var uri;
1946
+
1947
+ if (typeof host !== "string")
1948
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"]));
1949
+
1950
+ if (arguments.length == 2) {
1951
+ // host: must be full ws:// uri
1952
+ // port: clientId
1953
+ clientId = port;
1954
+ uri = host;
1955
+ var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/);
1956
+ if (match) {
1957
+ host = match[4]||match[2];
1958
+ port = parseInt(match[7]);
1959
+ path = match[8];
1960
+ } else {
1961
+ throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"]));
1962
+ }
1963
+ } else {
1964
+ if (arguments.length == 3) {
1965
+ clientId = path;
1966
+ path = "/mqtt";
1967
+ }
1968
+ if (typeof port !== "number" || port < 0)
1969
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"]));
1970
+ if (typeof path !== "string")
1971
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"]));
1972
+
1973
+ var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]");
1974
+ uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path;
1975
+ }
1976
+
1977
+ var clientIdLength = 0;
1978
+ for (var i = 0; i<clientId.length; i++) {
1979
+ var charCode = clientId.charCodeAt(i);
1980
+ if (0xD800 <= charCode && charCode <= 0xDBFF) {
1981
+ i++; // Surrogate pair.
1982
+ }
1983
+ clientIdLength++;
1984
+ }
1985
+ if (typeof clientId !== "string" || clientIdLength > 65535)
1986
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"]));
1987
+
1988
+ var client = new ClientImpl(uri, host, port, path, clientId);
1989
+ this._getHost = function() { return host; };
1990
+ this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
1991
+
1992
+ this._getPort = function() { return port; };
1993
+ this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
1994
+
1995
+ this._getPath = function() { return path; };
1996
+ this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
1997
+
1998
+ this._getURI = function() { return uri; };
1999
+ this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
2000
+
2001
+ this._getClientId = function() { return client.clientId; };
2002
+ this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
2003
+
2004
+ this._getOnConnectionLost = function() { return client.onConnectionLost; };
2005
+ this._setOnConnectionLost = function(newOnConnectionLost) {
2006
+ if (typeof newOnConnectionLost === "function")
2007
+ client.onConnectionLost = newOnConnectionLost;
2008
+ else
2009
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"]));
2010
+ };
2011
+
2012
+ this._getOnMessageDelivered = function() { return client.onMessageDelivered; };
2013
+ this._setOnMessageDelivered = function(newOnMessageDelivered) {
2014
+ if (typeof newOnMessageDelivered === "function")
2015
+ client.onMessageDelivered = newOnMessageDelivered;
2016
+ else
2017
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"]));
2018
+ };
2019
+
2020
+ this._getOnMessageArrived = function() { return client.onMessageArrived; };
2021
+ this._setOnMessageArrived = function(newOnMessageArrived) {
2022
+ if (typeof newOnMessageArrived === "function")
2023
+ client.onMessageArrived = newOnMessageArrived;
2024
+ else
2025
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"]));
2026
+ };
2027
+
2028
+ this._getTrace = function() { return client.traceFunction; };
2029
+ this._setTrace = function(trace) {
2030
+ if(typeof trace === "function"){
2031
+ client.traceFunction = trace;
2032
+ }else{
2033
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"]));
2034
+ }
2035
+ };
2036
+
2037
+ /**
2038
+ * Connect this Messaging client to its server.
2039
+ *
2040
+ * @name Paho.MQTT.Client#connect
2041
+ * @function
2042
+ * @param {Object} connectOptions - attributes used with the connection.
2043
+ * @param {number} connectOptions.timeout - If the connect has not succeeded within this
2044
+ * number of seconds, it is deemed to have failed.
2045
+ * The default is 30 seconds.
2046
+ * @param {string} connectOptions.userName - Authentication username for this connection.
2047
+ * @param {string} connectOptions.password - Authentication password for this connection.
2048
+ * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client
2049
+ * disconnects abnormally.
2050
+ * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if
2051
+ * there is no activity for this number of seconds.
2052
+ * The default value of 60 seconds is assumed if not set.
2053
+ * @param {boolean} connectOptions.cleanSession - if true(default) the client and server
2054
+ * persistent state is deleted on successful connect.
2055
+ * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection.
2056
+ * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback.
2057
+ * @param {function} connectOptions.onSuccess - called when the connect acknowledgement
2058
+ * has been received from the server.
2059
+ * A single response object parameter is passed to the onSuccess callback containing the following fields:
2060
+ * <ol>
2061
+ * <li>invocationContext as passed in to the onSuccess method in the connectOptions.
2062
+ * </ol>
2063
+ * @config {function} [onFailure] called when the connect request has failed or timed out.
2064
+ * A single response object parameter is passed to the onFailure callback containing the following fields:
2065
+ * <ol>
2066
+ * <li>invocationContext as passed in to the onFailure method in the connectOptions.
2067
+ * <li>errorCode a number indicating the nature of the error.
2068
+ * <li>errorMessage text describing the error.
2069
+ * </ol>
2070
+ * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified
2071
+ * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place
2072
+ * of the host and port paramater on the construtor. The hosts are tried one at at time in order until
2073
+ * one of then succeeds.
2074
+ * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property
2075
+ * is not used.
2076
+ * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost
2077
+ * or disconnected before calling connect for a second or subsequent time.
2078
+ */
2079
+ this.connect = function (connectOptions) {
2080
+ connectOptions = connectOptions || {} ;
2081
+ validate(connectOptions, {timeout:"number",
2082
+ userName:"string",
2083
+ password:"string",
2084
+ willMessage:"object",
2085
+ keepAliveInterval:"number",
2086
+ cleanSession:"boolean",
2087
+ useSSL:"boolean",
2088
+ invocationContext:"object",
2089
+ onSuccess:"function",
2090
+ onFailure:"function",
2091
+ hosts:"object",
2092
+ ports:"object",
2093
+ mqttVersion:"number"});
2094
+
2095
+ // If no keep alive interval is set, assume 60 seconds.
2096
+ if (connectOptions.keepAliveInterval === undefined)
2097
+ connectOptions.keepAliveInterval = 60;
2098
+
2099
+ if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) {
2100
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"]));
2101
+ }
2102
+
2103
+ if (connectOptions.mqttVersion === undefined) {
2104
+ connectOptions.mqttVersionExplicit = false;
2105
+ connectOptions.mqttVersion = 4;
2106
+ } else {
2107
+ connectOptions.mqttVersionExplicit = true;
2108
+ }
2109
+
2110
+ //Check that if password is set, so is username
2111
+ if (connectOptions.password === undefined && connectOptions.userName !== undefined)
2112
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"]))
2113
+
2114
+ if (connectOptions.willMessage) {
2115
+ if (!(connectOptions.willMessage instanceof Message))
2116
+ throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"]));
2117
+ // The will message must have a payload that can be represented as a string.
2118
+ // Cause the willMessage to throw an exception if this is not the case.
2119
+ connectOptions.willMessage.stringPayload;
2120
+
2121
+ if (typeof connectOptions.willMessage.destinationName === "undefined")
2122
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"]));
2123
+ }
2124
+ if (typeof connectOptions.cleanSession === "undefined")
2125
+ connectOptions.cleanSession = true;
2126
+ if (connectOptions.hosts) {
2127
+
2128
+ if (!(connectOptions.hosts instanceof Array) )
2129
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
2130
+ if (connectOptions.hosts.length <1 )
2131
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
2132
+
2133
+ var usingURIs = false;
2134
+ for (var i = 0; i<connectOptions.hosts.length; i++) {
2135
+ if (typeof connectOptions.hosts[i] !== "string")
2136
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
2137
+ if (/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/.test(connectOptions.hosts[i])) {
2138
+ if (i == 0) {
2139
+ usingURIs = true;
2140
+ } else if (!usingURIs) {
2141
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
2142
+ }
2143
+ } else if (usingURIs) {
2144
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
2145
+ }
2146
+ }
2147
+
2148
+ if (!usingURIs) {
2149
+ if (!connectOptions.ports)
2150
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
2151
+ if (!(connectOptions.ports instanceof Array) )
2152
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
2153
+ if (connectOptions.hosts.length != connectOptions.ports.length)
2154
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
2155
+
2156
+ connectOptions.uris = [];
2157
+
2158
+ for (var i = 0; i<connectOptions.hosts.length; i++) {
2159
+ if (typeof connectOptions.ports[i] !== "number" || connectOptions.ports[i] < 0)
2160
+ throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.ports[i], "connectOptions.ports["+i+"]"]));
2161
+ var host = connectOptions.hosts[i];
2162
+ var port = connectOptions.ports[i];
2163
+
2164
+ var ipv6 = (host.indexOf(":") != -1);
2165
+ uri = "ws://"+(ipv6?"["+host+"]":host)+":"+port+path;
2166
+ connectOptions.uris.push(uri);
2167
+ }
2168
+ } else {
2169
+ connectOptions.uris = connectOptions.hosts;
2170
+ }
2171
+ }
2172
+
2173
+ client.connect(connectOptions);
2174
+ };
2175
+
2176
+ /**
2177
+ * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the filter.
2178
+ *
2179
+ * @name Paho.MQTT.Client#subscribe
2180
+ * @function
2181
+ * @param {string} filter describing the destinations to receive messages from.
2182
+ * <br>
2183
+ * @param {object} subscribeOptions - used to control the subscription
2184
+ *
2185
+ * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent
2186
+ * as a result of making this subscription.
2187
+ * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback
2188
+ * or onFailure callback.
2189
+ * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement
2190
+ * has been received from the server.
2191
+ * A single response object parameter is passed to the onSuccess callback containing the following fields:
2192
+ * <ol>
2193
+ * <li>invocationContext if set in the subscribeOptions.
2194
+ * </ol>
2195
+ * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out.
2196
+ * A single response object parameter is passed to the onFailure callback containing the following fields:
2197
+ * <ol>
2198
+ * <li>invocationContext - if set in the subscribeOptions.
2199
+ * <li>errorCode - a number indicating the nature of the error.
2200
+ * <li>errorMessage - text describing the error.
2201
+ * </ol>
2202
+ * @param {number} subscribeOptions.timeout - which, if present, determines the number of
2203
+ * seconds after which the onFailure calback is called.
2204
+ * The presence of a timeout does not prevent the onSuccess
2205
+ * callback from being called when the subscribe completes.
2206
+ * @throws {InvalidState} if the client is not in connected state.
2207
+ */
2208
+ this.subscribe = function (filter, subscribeOptions) {
2209
+ if (typeof filter !== "string")
2210
+ throw new Error("Invalid argument:"+filter);
2211
+ subscribeOptions = subscribeOptions || {} ;
2212
+ validate(subscribeOptions, {qos:"number",
2213
+ invocationContext:"object",
2214
+ onSuccess:"function",
2215
+ onFailure:"function",
2216
+ timeout:"number"
2217
+ });
2218
+ if (subscribeOptions.timeout && !subscribeOptions.onFailure)
2219
+ throw new Error("subscribeOptions.timeout specified with no onFailure callback.");
2220
+ if (typeof subscribeOptions.qos !== "undefined"
2221
+ && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 ))
2222
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"]));
2223
+ client.subscribe(filter, subscribeOptions);
2224
+ };
2225
+
2226
+ /**
2227
+ * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter.
2228
+ *
2229
+ * @name Paho.MQTT.Client#unsubscribe
2230
+ * @function
2231
+ * @param {string} filter - describing the destinations to receive messages from.
2232
+ * @param {object} unsubscribeOptions - used to control the subscription
2233
+ * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback
2234
+ or onFailure callback.
2235
+ * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server.
2236
+ * A single response object parameter is passed to the
2237
+ * onSuccess callback containing the following fields:
2238
+ * <ol>
2239
+ * <li>invocationContext - if set in the unsubscribeOptions.
2240
+ * </ol>
2241
+ * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out.
2242
+ * A single response object parameter is passed to the onFailure callback containing the following fields:
2243
+ * <ol>
2244
+ * <li>invocationContext - if set in the unsubscribeOptions.
2245
+ * <li>errorCode - a number indicating the nature of the error.
2246
+ * <li>errorMessage - text describing the error.
2247
+ * </ol>
2248
+ * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds
2249
+ * after which the onFailure callback is called. The presence of
2250
+ * a timeout does not prevent the onSuccess callback from being
2251
+ * called when the unsubscribe completes
2252
+ * @throws {InvalidState} if the client is not in connected state.
2253
+ */
2254
+ this.unsubscribe = function (filter, unsubscribeOptions) {
2255
+ if (typeof filter !== "string")
2256
+ throw new Error("Invalid argument:"+filter);
2257
+ unsubscribeOptions = unsubscribeOptions || {} ;
2258
+ validate(unsubscribeOptions, {invocationContext:"object",
2259
+ onSuccess:"function",
2260
+ onFailure:"function",
2261
+ timeout:"number"
2262
+ });
2263
+ if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure)
2264
+ throw new Error("unsubscribeOptions.timeout specified with no onFailure callback.");
2265
+ client.unsubscribe(filter, unsubscribeOptions);
2266
+ };
2267
+
2268
+ /**
2269
+ * Send a message to the consumers of the destination in the Message.
2270
+ *
2271
+ * @name Paho.MQTT.Client#send
2272
+ * @function
2273
+ * @param {string|Paho.MQTT.Message} topic - <b>mandatory</b> The name of the destination to which the message is to be sent.
2274
+ * - If it is the only parameter, used as Paho.MQTT.Message object.
2275
+ * @param {String|ArrayBuffer} payload - The message data to be sent.
2276
+ * @param {number} qos The Quality of Service used to deliver the message.
2277
+ * <dl>
2278
+ * <dt>0 Best effort (default).
2279
+ * <dt>1 At least once.
2280
+ * <dt>2 Exactly once.
2281
+ * </dl>
2282
+ * @param {Boolean} retained If true, the message is to be retained by the server and delivered
2283
+ * to both current and future subscriptions.
2284
+ * If false the server only delivers the message to current subscribers, this is the default for new Messages.
2285
+ * A received message has the retained boolean set to true if the message was published
2286
+ * with the retained boolean set to true
2287
+ * and the subscrption was made after the message has been published.
2288
+ * @throws {InvalidState} if the client is not connected.
2289
+ */
2290
+ this.send = function (topic,payload,qos,retained) {
2291
+ var message ;
2292
+
2293
+ if(arguments.length == 0){
2294
+ throw new Error("Invalid argument."+"length");
2295
+
2296
+ }else if(arguments.length == 1) {
2297
+
2298
+ if (!(topic instanceof Message) && (typeof topic !== "string"))
2299
+ throw new Error("Invalid argument:"+ typeof topic);
2300
+
2301
+ message = topic;
2302
+ if (typeof message.destinationName === "undefined")
2303
+ throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"]));
2304
+ client.send(message);
2305
+
2306
+ }else {
2307
+ //parameter checking in Message object
2308
+ message = new Message(payload);
2309
+ message.destinationName = topic;
2310
+ if(arguments.length >= 3)
2311
+ message.qos = qos;
2312
+ if(arguments.length >= 4)
2313
+ message.retained = retained;
2314
+ client.send(message);
2315
+ }
2316
+ };
2317
+
2318
+ /**
2319
+ * Normal disconnect of this Messaging client from its server.
2320
+ *
2321
+ * @name Paho.MQTT.Client#disconnect
2322
+ * @function
2323
+ * @throws {InvalidState} if the client is already disconnected.
2324
+ */
2325
+ this.disconnect = function () {
2326
+ client.disconnect();
2327
+ };
2328
+
2329
+ /**
2330
+ * Get the contents of the trace log.
2331
+ *
2332
+ * @name Paho.MQTT.Client#getTraceLog
2333
+ * @function
2334
+ * @return {Object[]} tracebuffer containing the time ordered trace records.
2335
+ */
2336
+ this.getTraceLog = function () {
2337
+ return client.getTraceLog();
2338
+ }
2339
+
2340
+ /**
2341
+ * Start tracing.
2342
+ *
2343
+ * @name Paho.MQTT.Client#startTrace
2344
+ * @function
2345
+ */
2346
+ this.startTrace = function () {
2347
+ client.startTrace();
2348
+ };
2349
+
2350
+ /**
2351
+ * Stop tracing.
2352
+ *
2353
+ * @name Paho.MQTT.Client#stopTrace
2354
+ * @function
2355
+ */
2356
+ this.stopTrace = function () {
2357
+ client.stopTrace();
2358
+ };
2359
+
2360
+ this.isConnected = function() {
2361
+ return client.connected;
2362
+ };
2363
+ };
2364
+
2365
+ Client.prototype = {
2366
+ get host() { return this._getHost(); },
2367
+ set host(newHost) { this._setHost(newHost); },
2368
+
2369
+ get port() { return this._getPort(); },
2370
+ set port(newPort) { this._setPort(newPort); },
2371
+
2372
+ get path() { return this._getPath(); },
2373
+ set path(newPath) { this._setPath(newPath); },
2374
+
2375
+ get clientId() { return this._getClientId(); },
2376
+ set clientId(newClientId) { this._setClientId(newClientId); },
2377
+
2378
+ get onConnectionLost() { return this._getOnConnectionLost(); },
2379
+ set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); },
2380
+
2381
+ get onMessageDelivered() { return this._getOnMessageDelivered(); },
2382
+ set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); },
2383
+
2384
+ get onMessageArrived() { return this._getOnMessageArrived(); },
2385
+ set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); },
2386
+
2387
+ get trace() { return this._getTrace(); },
2388
+ set trace(newTraceFunction) { this._setTrace(newTraceFunction); }
2389
+
2390
+ };
2391
+
2392
+ /**
2393
+ * An application message, sent or received.
2394
+ * <p>
2395
+ * All attributes may be null, which implies the default values.
2396
+ *
2397
+ * @name Paho.MQTT.Message
2398
+ * @constructor
2399
+ * @param {String|ArrayBuffer} payload The message data to be sent.
2400
+ * <p>
2401
+ * @property {string} payloadString <i>read only</i> The payload as a string if the payload consists of valid UTF-8 characters.
2402
+ * @property {ArrayBuffer} payloadBytes <i>read only</i> The payload as an ArrayBuffer.
2403
+ * <p>
2404
+ * @property {string} destinationName <b>mandatory</b> The name of the destination to which the message is to be sent
2405
+ * (for messages about to be sent) or the name of the destination from which the message has been received.
2406
+ * (for messages received by the onMessage function).
2407
+ * <p>
2408
+ * @property {number} qos The Quality of Service used to deliver the message.
2409
+ * <dl>
2410
+ * <dt>0 Best effort (default).
2411
+ * <dt>1 At least once.
2412
+ * <dt>2 Exactly once.
2413
+ * </dl>
2414
+ * <p>
2415
+ * @property {Boolean} retained If true, the message is to be retained by the server and delivered
2416
+ * to both current and future subscriptions.
2417
+ * If false the server only delivers the message to current subscribers, this is the default for new Messages.
2418
+ * A received message has the retained boolean set to true if the message was published
2419
+ * with the retained boolean set to true
2420
+ * and the subscrption was made after the message has been published.
2421
+ * <p>
2422
+ * @property {Boolean} duplicate <i>read only</i> If true, this message might be a duplicate of one which has already been received.
2423
+ * This is only set on messages received from the server.
2424
+ *
2425
+ */
2426
+ var Message = function (newPayload) {
2427
+ var payload;
2428
+ if ( typeof newPayload === "string"
2429
+ || newPayload instanceof ArrayBuffer
2430
+ || newPayload instanceof Int8Array
2431
+ || newPayload instanceof Uint8Array
2432
+ || newPayload instanceof Int16Array
2433
+ || newPayload instanceof Uint16Array
2434
+ || newPayload instanceof Int32Array
2435
+ || newPayload instanceof Uint32Array
2436
+ || newPayload instanceof Float32Array
2437
+ || newPayload instanceof Float64Array
2438
+ ) {
2439
+ payload = newPayload;
2440
+ } else {
2441
+ throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"]));
2442
+ }
2443
+
2444
+ this._getPayloadString = function () {
2445
+ if (typeof payload === "string")
2446
+ return payload;
2447
+ else
2448
+ return parseUTF8(payload, 0, payload.length);
2449
+ };
2450
+
2451
+ this._getPayloadBytes = function() {
2452
+ if (typeof payload === "string") {
2453
+ var buffer = new ArrayBuffer(UTF8Length(payload));
2454
+ var byteStream = new Uint8Array(buffer);
2455
+ stringToUTF8(payload, byteStream, 0);
2456
+
2457
+ return byteStream;
2458
+ } else {
2459
+ return payload;
2460
+ };
2461
+ };
2462
+
2463
+ var destinationName = undefined;
2464
+ this._getDestinationName = function() { return destinationName; };
2465
+ this._setDestinationName = function(newDestinationName) {
2466
+ if (typeof newDestinationName === "string")
2467
+ destinationName = newDestinationName;
2468
+ else
2469
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"]));
2470
+ };
2471
+
2472
+ var qos = 0;
2473
+ this._getQos = function() { return qos; };
2474
+ this._setQos = function(newQos) {
2475
+ if (newQos === 0 || newQos === 1 || newQos === 2 )
2476
+ qos = newQos;
2477
+ else
2478
+ throw new Error("Invalid argument:"+newQos);
2479
+ };
2480
+
2481
+ var retained = false;
2482
+ this._getRetained = function() { return retained; };
2483
+ this._setRetained = function(newRetained) {
2484
+ if (typeof newRetained === "boolean")
2485
+ retained = newRetained;
2486
+ else
2487
+ throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"]));
2488
+ };
2489
+
2490
+ var duplicate = false;
2491
+ this._getDuplicate = function() { return duplicate; };
2492
+ this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; };
2493
+ };
2494
+
2495
+ Message.prototype = {
2496
+ get payloadString() { return this._getPayloadString(); },
2497
+ get payloadBytes() { return this._getPayloadBytes(); },
2498
+
2499
+ get destinationName() { return this._getDestinationName(); },
2500
+ set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); },
2501
+
2502
+ get qos() { return this._getQos(); },
2503
+ set qos(newQos) { this._setQos(newQos); },
2504
+
2505
+ get retained() { return this._getRetained(); },
2506
+ set retained(newRetained) { this._setRetained(newRetained); },
2507
+
2508
+ get duplicate() { return this._getDuplicate(); },
2509
+ set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); }
2510
+ };
2511
+
2512
+ // Module contents.
2513
+ return {
2514
+ Client: Client,
2515
+ Message: Message
2516
+ };
2517
+ })(window);
2518
+ module.exports=Paho.MQTT;
2519
+ },{}],4:[function(require,module,exports){
2520
+ /**
2521
+ * Application-level APIs for nutella, browser version
2522
+ */
2523
+
2524
+ // Require various sub-modules
2525
+ var AppNetSubModule = require('./app_net');
2526
+ var AppLogSubModule = require('./app_log');
2527
+
2528
+
2529
+ var AppSubModule = function(main_nutella) {
2530
+ // Initialized the various sub-modules
2531
+ this.net = new AppNetSubModule(main_nutella);
2532
+ this.log = new AppLogSubModule(main_nutella);
2533
+ };
2534
+
2535
+
2536
+ module.exports = AppSubModule;
2537
+ },{"./app_log":5,"./app_net":6}],5:[function(require,module,exports){
2538
+ /**
2539
+ * App-level log APIs for nutella
2540
+ */
2541
+
2542
+ var AppNetSubModule = require('./app_net');
2543
+
2544
+ var AppLogSubModule = function(main_nutella) {
2545
+ this.net = new AppNetSubModule(main_nutella);
2546
+ };
2547
+
2548
+
2549
+
2550
+ AppLogSubModule.prototype.debug = function(message, code) {
2551
+ console.debug(message);
2552
+ this.net.publish('logging', logToJson(message, code, 'debug'));
2553
+ return code;
2554
+ };
2555
+
2556
+ AppLogSubModule.prototype.info = function(message, code) {
2557
+ console.info(message);
2558
+ this.net.publish('logging', logToJson(message, code, 'info'));
2559
+ return code;
2560
+ };
2561
+
2562
+ AppLogSubModule.prototype.success = function(message, code) {
2563
+ console.log('%c '+ message , 'color: #009933');
2564
+ this.net.publish('logging', logToJson(message, code, 'success'));
2565
+ return code;
2566
+ };
2567
+
2568
+ AppLogSubModule.prototype.warn = function(message, code) {
2569
+ console.warn(message);
2570
+ this.net.publish('logging', logToJson(message, code, 'warn'));
2571
+ return code;
2572
+ };
2573
+
2574
+ AppLogSubModule.prototype.error = function(message, code) {
2575
+ console.error(message);
2576
+ this.net.publish('logging', logToJson(message, code, 'error'));
2577
+ return code;
2578
+ };
2579
+
2580
+
2581
+ function logToJson( message, code, level) {
2582
+ return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code};
2583
+ }
2584
+
2585
+
2586
+
2587
+ module.exports = AppLogSubModule;
2588
+
2589
+ },{"./app_net":6}],6:[function(require,module,exports){
2590
+ /**
2591
+ * App-level Networking APIs for nutella
2592
+ */
2593
+
2594
+
2595
+ var AbstractNet = require('./util/net');
2596
+
2597
+
2598
+ /**
2599
+ * App-level network APIs for nutella
2600
+ * @param main_nutella
2601
+ * @constructor
2602
+ */
2603
+ var AppNetSubModule = function(main_nutella) {
2604
+ this.net = new AbstractNet(main_nutella);
2605
+ };
2606
+
2607
+
2608
+
2609
+ /**
2610
+ * Subscribes to a channel or filter.
2611
+ *
2612
+ * @param channel
2613
+ * @param callback
2614
+ * @param done_callback
2615
+ */
2616
+ AppNetSubModule.prototype.subscribe = function(channel, callback, done_callback) {
2617
+ this.net.subscribe_to(channel, callback, this.net.nutella.appId, undefined, done_callback);
2618
+ };
2619
+
2620
+
2621
+
2622
+ /**
2623
+ * Unsubscribes from a channel
2624
+ *
2625
+ * @param channel
2626
+ * @param done_callback
2627
+ */
2628
+ AppNetSubModule.prototype.unsubscribe = function(channel, done_callback) {
2629
+ this.net.unsubscribe_from(channel, this.net.nutella.appId, undefined, done_callback);
2630
+ };
2631
+
2632
+
2633
+
2634
+ /**
2635
+ * Publishes a message to a channel
2636
+ *
2637
+ * @param channel
2638
+ * @param message
2639
+ */
2640
+ AppNetSubModule.prototype.publish = function(channel, message) {
2641
+ this.net.publish_to(channel, message, this.net.nutella.appId, undefined);
2642
+ };
2643
+
2644
+
2645
+
2646
+ /**
2647
+ * Sends a request.
2648
+ *
2649
+ * @param channel
2650
+ * @param message
2651
+ * @param callback
2652
+ */
2653
+ AppNetSubModule.prototype.request = function(channel, message, callback) {
2654
+ this.net.request_to(channel, message, callback, this.net.nutella.appId, undefined);
2655
+ };
2656
+
2657
+
2658
+
2659
+ /**
2660
+ * Handles requests.
2661
+ *
2662
+ * @param channel
2663
+ * @param callback
2664
+ * @param done_callback
2665
+ */
2666
+ AppNetSubModule.prototype.handle_requests = function (channel, callback, done_callback) {
2667
+ this.net.handle_requests_on(channel, callback, this.net.nutella.appId, undefined, done_callback);
2668
+ };
2669
+
2670
+
2671
+
2672
+ //----------------------------------------------------------------------------------------------------------------
2673
+ // Application-level APIs to communicate at the run-level
2674
+ //----------------------------------------------------------------------------------------------------------------
2675
+
2676
+ /**
2677
+ * Allows application-level APIs to subscribe to a run-level channel within a specific run
2678
+ *
2679
+ * @param run_id
2680
+ * @param channel
2681
+ * @param callback
2682
+ * @param done_callback
2683
+ */
2684
+ AppNetSubModule.prototype.subscribe_to_run = function(run_id, channel, callback, done_callback) {
2685
+ this.net.subscribe_to(channel,callback,this.net.nutella.appId,run_id,done_callback);
2686
+ };
2687
+
2688
+
2689
+ /**
2690
+ * Allows application-level APIs to unsubscribe from a run-level channel within a specific run
2691
+ *
2692
+ * @param run_id
2693
+ * @param channel
2694
+ * @param done_callback
2695
+ */
2696
+ AppNetSubModule.prototype.unsubscribe_from_run = function(run_id, channel, done_callback) {
2697
+ this.net.unsubscribe_from(channel,this.net.nutella.appId,run_id,done_callback);
2698
+ };
2699
+
2700
+
2701
+ /**
2702
+ * Allows application-level APIs to publish to a run-level channel within a specific run
2703
+ *
2704
+ * @param run_id
2705
+ * @param channel
2706
+ * @param message
2707
+ */
2708
+ AppNetSubModule.prototype.publish_to_run = function( run_id, channel, message ) {
2709
+ this.net.publish_to(channel,message,this.net.nutella.appId, run_id);
2710
+ };
2711
+
2712
+
2713
+ /**
2714
+ * Allows application-level APIs to make a request to a run-level channel within a specific run
2715
+ *
2716
+ * @param run_id
2717
+ * @param channel
2718
+ * @param request
2719
+ * @param callback
2720
+ */
2721
+ AppNetSubModule.prototype.request_to_run = function( run_id, channel, request, callback) {
2722
+ this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id);
2723
+ };
2724
+
2725
+
2726
+ /**
2727
+ * Allows application-level APIs to handle requests on a run-level channel within a specific run
2728
+ *
2729
+ * @param run_id
2730
+ * @param channel
2731
+ * @param callback
2732
+ * @param done_callback
2733
+ */
2734
+ AppNetSubModule.prototype.handle_requests_on_run = function( run_id, channel, callback, done_callback ) {
2735
+ this.net.handle_requests_on(channel,callback,this.net.nutella.appId,run_id,done_callback);
2736
+ };
2737
+
2738
+
2739
+ //----------------------------------------------------------------------------------------------------------------
2740
+ // Application-level APIs to communicate at the run-level (broadcast)
2741
+ //----------------------------------------------------------------------------------------------------------------
2742
+
2743
+ /**
2744
+ * Fired whenever a message is received on the specified channel for any of the runs in the application
2745
+ *
2746
+ * @callback all_runs_cb
2747
+ * @param {string} message - the received message. Messages that are not JSON are discarded.
2748
+ * @param {string} run_id - the run_id of the channel the message was sent on
2749
+ * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
2750
+ */
2751
+
2752
+ /**
2753
+ * Allows application-level APIs to subscribe to a run-level channel *for ALL runs*
2754
+ *
2755
+ * @param {string} channel - the run-level channel we are subscribing to. Can be wildcard
2756
+ * @param {all_runs_cb} callback - the callback that is fired whenever a message is received on the channel
2757
+ */
2758
+ AppNetSubModule.prototype.subscribe_to_all_runs = function(channel, callback, done_callback) {
2759
+ var app_id = this.net.nutella.appId;
2760
+ //Pad channel
2761
+ var padded_channel = this.net.pad_channel(channel, app_id, '+');
2762
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
2763
+ try {
2764
+ var f = JSON.parse(mqtt_message);
2765
+ var run_id = extractRunId(app_id, mqtt_channel);
2766
+ if(f.type==='publish')
2767
+ callback(f.payload, run_id, f.from);
2768
+ } catch(e) {
2769
+ if (e instanceof SyntaxError) {
2770
+ // Message is not JSON, drop it
2771
+ } else {
2772
+ // Bubble up whatever exception is thrown
2773
+ throw e;
2774
+ }
2775
+ }
2776
+ };
2777
+ // Add to subscriptions, save mqtt callback and subscribe
2778
+ this.net.subscriptions.push(padded_channel);
2779
+ this.net.callbacks.push(mqtt_cb);
2780
+ this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback);
2781
+ // Notify subscription
2782
+ this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, this.net.nutella.appId, undefined);
2783
+ };
2784
+
2785
+
2786
+ /**
2787
+ * Allows application-level APIs to publish a message to a run-level channel *for ALL runs*
2788
+ *
2789
+ * @param channel
2790
+ * @param message
2791
+ */
2792
+ AppNetSubModule.prototype.publish_to_all_runs = function(channel, message) {
2793
+ this.net.nutella.runs_list.forEach(function(run_id){
2794
+ this.net.publish_to(channel,message,this.net.nutella.appId,run_id);
2795
+ }.bind(this));
2796
+ };
2797
+
2798
+
2799
+ /**
2800
+ * Allows application-level APIs to send a request to a run-level channel *for ALL runs*
2801
+ *
2802
+ * @param channel
2803
+ * @param request
2804
+ * @param callback
2805
+ */
2806
+ AppNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) {
2807
+ this.net.nutella.runs_list.forEach(function(run_id){
2808
+ this.net.request_to(channel,request,callback,this.net.nutella.appId,run_id);
2809
+ }.bind(this));
2810
+ };
2811
+
2812
+
2813
+ /**
2814
+ * This callback is used to handle all runs
2815
+ * @callback handle_all_run
2816
+ * @param {string} message - the received message. Messages that are not JSON are discarded.
2817
+ * @param {string} run_id - the run_id of the channel the message was sent on
2818
+ * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
2819
+ * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
2820
+ */
2821
+
2822
+ /**
2823
+ * Allows application-level APIs to handle requests to a run-level channel *for ALL runs*
2824
+ *
2825
+ * @param channel
2826
+ * @param callback
2827
+ * @param done_callback
2828
+ */
2829
+ AppNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) {
2830
+ var app_id = this.net.nutella.appId;
2831
+ // Pad channel
2832
+ var padded_channel = this.net.pad_channel(channel, app_id, '+');
2833
+ var ln = this.net;
2834
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
2835
+ try {
2836
+ var f = JSON.parse(mqtt_message);
2837
+ var run_id = extractRunId(app_id, mqtt_channel);
2838
+ // Only handle requests that have proper id set
2839
+ if(f.type!=='request' || f.id===undefined) return;
2840
+ // Execute callback and send response
2841
+ var m = ln.prepare_message_for_response(callback(f.payload, run_id, f.from), f.id);
2842
+ ln.nutella.mqtt_client.publish( padded_channel, m );
2843
+ } catch(e) {
2844
+ if (e instanceof SyntaxError) {
2845
+ // Message is not JSON, drop it
2846
+ } else {
2847
+ // Bubble up whatever exception is thrown
2848
+ throw e;
2849
+ }
2850
+ }
2851
+ };
2852
+ this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback);
2853
+ // Notify subscription
2854
+ this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, this.net.nutella.appId, undefined);
2855
+ };
2856
+
2857
+
2858
+
2859
+ // Utility function
2860
+
2861
+ function extractRunId(app_id, mqtt_channel) {
2862
+ var pc = '/nutella/apps/' + app_id + '/runs/';
2863
+ var sp = mqtt_channel.replace(pc, '').split('/');
2864
+ return sp[0];
2865
+ }
2866
+
2867
+
2868
+ module.exports = AppNetSubModule;
2869
+
2870
+ },{"./util/net":13}],7:[function(require,module,exports){
2871
+ /**
2872
+ * Framework-level APIs for nutella, browser version
2873
+ */
2874
+
2875
+ // Require various sub-modules
2876
+ var FrNetSubModule = require('./fr_net');
2877
+ var FrLogSubModule = require('./fr_log');
2878
+
2879
+
2880
+ var FrSubModule = function(main_nutella) {
2881
+ // Initialized the various sub-modules
2882
+ this.net = new FrNetSubModule(main_nutella);
2883
+ this.log = new FrLogSubModule(main_nutella);
2884
+ };
2885
+
2886
+
2887
+ module.exports = FrSubModule;
2888
+ },{"./fr_log":8,"./fr_net":9}],8:[function(require,module,exports){
2889
+ /**
2890
+ * Framework-level log APIs for nutella
2891
+ */
2892
+
2893
+ var FrNetSubModule = require('./app_net');
2894
+
2895
+ var FrLogSubModule = function(main_nutella) {
2896
+ this.net = new FrNetSubModule(main_nutella);
2897
+ };
2898
+
2899
+
2900
+
2901
+ FrLogSubModule.prototype.debug = function(message, code) {
2902
+ console.debug(message);
2903
+ this.net.publish('logging', logToJson(message, code, 'debug'));
2904
+ return code;
2905
+ };
2906
+
2907
+ FrLogSubModule.prototype.info = function(message, code) {
2908
+ console.info(message);
2909
+ this.net.publish('logging', logToJson(message, code, 'info'));
2910
+ return code;
2911
+ };
2912
+
2913
+ FrLogSubModule.prototype.success = function(message, code) {
2914
+ console.log('%c '+ message , 'color: #009933');
2915
+ this.net.publish('logging', logToJson(message, code, 'success'));
2916
+ return code;
2917
+ };
2918
+
2919
+ FrLogSubModule.prototype.warn = function(message, code) {
2920
+ console.warn(message);
2921
+ this.net.publish('logging', logToJson(message, code, 'warn'));
2922
+ return code;
2923
+ };
2924
+
2925
+ FrLogSubModule.prototype.error = function(message, code) {
2926
+ console.error(message);
2927
+ this.net.publish('logging', logToJson(message, code, 'error'));
2928
+ return code;
2929
+ };
2930
+
2931
+
2932
+ function logToJson( message, code, level) {
2933
+ return (code === undefined) ? {level: level, message: message} : {level: level, message: message, code: code};
2934
+ }
2935
+
2936
+
2937
+
2938
+ module.exports = FrLogSubModule;
2939
+
2940
+ },{"./app_net":6}],9:[function(require,module,exports){
2941
+ /**
2942
+ * Framework-level Networking APIs for nutella
2943
+ */
2944
+
2945
+
2946
+ var AbstractNet = require('./util/net');
2947
+
2948
+
2949
+ /**
2950
+ * Framework-level network APIs for nutella
2951
+ * @param main_nutella
2952
+ * @constructor
2953
+ */
2954
+ var FRNetSubModule = function(main_nutella) {
2955
+ this.net = new AbstractNet(main_nutella);
2956
+ };
2957
+
2958
+
2959
+
2960
+ /**
2961
+ * Subscribes to a channel or filter.
2962
+ *
2963
+ * @param channel
2964
+ * @param callback
2965
+ * @param done_callback
2966
+ */
2967
+ FRNetSubModule.prototype.subscribe = function(channel, callback, done_callback) {
2968
+ this.net.subscribe_to(channel, callback, undefined, undefined, done_callback);
2969
+ };
2970
+
2971
+
2972
+ /**
2973
+ * Unsubscribes from a channel
2974
+ *
2975
+ * @param channel
2976
+ * @param done_callback
2977
+ */
2978
+ FRNetSubModule.prototype.unsubscribe = function(channel, done_callback) {
2979
+ this.net.unsubscribe_from(channel, undefined, undefined, done_callback);
2980
+ };
2981
+
2982
+
2983
+ /**
2984
+ * Publishes a message to a channel
2985
+ *
2986
+ * @param channel
2987
+ * @param message
2988
+ */
2989
+ FRNetSubModule.prototype.publish = function(channel, message) {
2990
+ this.net.publish_to(channel, message, undefined, undefined);
2991
+ };
2992
+
2993
+
2994
+ /**
2995
+ * Sends a request.
2996
+ *
2997
+ * @param channel
2998
+ * @param message
2999
+ * @param callback
3000
+ */
3001
+ FRNetSubModule.prototype.request = function(channel, message, callback) {
3002
+ this.net.request_to(channel, message, callback, undefined, undefined);
3003
+ };
3004
+
3005
+
3006
+ /**
3007
+ * Handles requests.
3008
+ *
3009
+ * @param channel
3010
+ * @param callback
3011
+ * @param done_callback
3012
+ */
3013
+ FRNetSubModule.prototype.handle_requests = function(channel, callback, done_callback) {
3014
+ this.net.handle_requests_on(channel, callback, undefined, undefined, done_callback);
3015
+ };
3016
+
3017
+
3018
+
3019
+ //----------------------------------------------------------------------------------------------------------------
3020
+ // Framework-level APIs to communicate at the run-level to a specific run
3021
+ //----------------------------------------------------------------------------------------------------------------
3022
+
3023
+ /**
3024
+ * Allows framework-level APIs to subscribe to a run-level channel within a specific run
3025
+ *
3026
+ * @param app_id
3027
+ * @param run_id
3028
+ * @param channel
3029
+ * @param callback
3030
+ * @param done_callback
3031
+ */
3032
+ FRNetSubModule.prototype.subscribe_to_run = function(app_id, run_id, channel, callback,done_callback) {
3033
+ this.net.subscribe_to(channel,callback,app_id,run_id,done_callback)
3034
+ };
3035
+
3036
+
3037
+ /**
3038
+ * Allows framework-level APIs to unsubscribe from a run-level channel within a specific run
3039
+ *
3040
+ * @param app_id
3041
+ * @param run_id
3042
+ * @param channel
3043
+ * @param done_callback
3044
+ */
3045
+ FRNetSubModule.prototype.unsubscribe_to_run = function( app_id, run_id, channel, done_callback ) {
3046
+ this.net.unsubscribe_from(channel, app_id, run_id, done_callback);
3047
+ };
3048
+
3049
+
3050
+ /**
3051
+ * Allows framework-level APIs to publish to a run-level channel within a specific run
3052
+ *
3053
+ * @param app_id
3054
+ * @param run_id
3055
+ * @param channel
3056
+ * @param message
3057
+ */
3058
+ FRNetSubModule.prototype.publish_to_run = function( app_id, run_id, channel, message ) {
3059
+ this.net.publish_to(channel, message, app_id, run_id);
3060
+ };
3061
+
3062
+
3063
+ /**
3064
+ * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run
3065
+ *
3066
+ * @param app_id
3067
+ * @param run_id
3068
+ * @param channel
3069
+ * @param request
3070
+ * @param callback
3071
+ */
3072
+ FRNetSubModule.prototype.request_to_run = function( app_id, run_id, channel, request, callback) {
3073
+ this.net.request_to(channel, request, callback, app_id, run_id);
3074
+ };
3075
+
3076
+
3077
+ /**
3078
+ * Allows framework-level APIs to handle requests on a run-level channel within a specific run
3079
+ *
3080
+ * @param app_id
3081
+ * @param run_id
3082
+ * @param channel
3083
+ * @param callback
3084
+ */
3085
+ FRNetSubModule.prototype.handle_requests_on_run = function( app_id, run_id, channel, callback, done_callback) {
3086
+ this.net.handle_requests_on(channel, callback, app_id, run_id, done_callback)
3087
+ };
3088
+
3089
+
3090
+
3091
+ //----------------------------------------------------------------------------------------------------------------
3092
+ // Framework-level APIs to communicate at the run-level (broadcast)
3093
+ //----------------------------------------------------------------------------------------------------------------
3094
+
3095
+
3096
+ /**
3097
+ * Callback for subscribing to all runs
3098
+ * @callback allRunsCb
3099
+ # @param {string} message - the received message. Messages that are not JSON are discarded
3100
+ # @param {String} app_id - the app_id of the channel the message was sent on
3101
+ # @param {String} run_id - the run_id of the channel the message was sent on
3102
+ # @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
3103
+ */
3104
+
3105
+ /**
3106
+ * Allows framework-level APIs to subscribe to a run-level channel *for ALL runs*
3107
+ *
3108
+ * @param channel
3109
+ * @param {allRunsCb} callback
3110
+ * @param done_callback
3111
+ */
3112
+ FRNetSubModule.prototype.subscribe_to_all_runs = function( channel, callback, done_callback ) {
3113
+ //Pad channel
3114
+ var padded_channel = this.net.pad_channel(channel, '+', '+');
3115
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
3116
+ try {
3117
+ var f = JSON.parse(mqtt_message);
3118
+ var f1 = extractRunIdAndAppId(mqtt_channel);
3119
+ if(f.type==='publish')
3120
+ callback(f.payload, f1.appId, f1.runId, f.from);
3121
+ } catch(e) {
3122
+ if (e instanceof SyntaxError) {
3123
+ // Message is not JSON, drop it
3124
+ } else {
3125
+ // Bubble up whatever exception is thrown
3126
+ throw e;
3127
+ }
3128
+ }
3129
+ };
3130
+ // Add to subscriptions, save mqtt callback and subscribe
3131
+ this.net.subscriptions.push(padded_channel);
3132
+ this.net.callbacks.push(mqtt_cb);
3133
+ this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback);
3134
+ // Notify subscription
3135
+ this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined);
3136
+ };
3137
+
3138
+
3139
+ /**
3140
+ * Allows framework-level APIs to unsubscribe from a run-level channel *for ALL runs*
3141
+ *
3142
+ * @param channel
3143
+ * @param done_callback
3144
+ */
3145
+ FRNetSubModule.prototype.unsubscribe_from_all_runs = function(channel, done_callback) {
3146
+ this.net.unsubscribe_from(channel, '+', '+', done_callback);
3147
+ };
3148
+
3149
+
3150
+ /**
3151
+ * Allows framework-level APIs to publish a message to a run-level channel *for ALL runs*
3152
+ *
3153
+ * @param channel
3154
+ * @param message
3155
+ */
3156
+ FRNetSubModule.prototype.publish_to_all_runs = function( channel, message ) {
3157
+ Object.keys(this.net.nutella.runs_list).forEach(function(app_id) {
3158
+ this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){
3159
+ this.net.publish_to(channel, message, app_id, run_id);
3160
+ }.bind(this));
3161
+ }.bind(this));
3162
+ };
3163
+
3164
+
3165
+ /**
3166
+ * Allows framework-level APIs to send a request to a run-level channel *for ALL runs*
3167
+ *
3168
+ * @param channel
3169
+ * @param request
3170
+ * @param callback
3171
+ */
3172
+ FRNetSubModule.prototype.request_to_all_runs = function(channel, request, callback) {
3173
+ Object.keys(this.net.nutella.runs_list).forEach(function(app_id) {
3174
+ this.net.nutella.runs_list[app_id].runs.forEach(function(run_id){
3175
+ this.net.publish_to(channel, message, app_id, run_id);
3176
+ this.net.request_to(channel, request, callback, app_id, run_id);
3177
+ }.bind(this));
3178
+ }.bind(this));
3179
+ };
3180
+
3181
+ /**
3182
+ * Callback that is used to handle messages from all runs
3183
+ * @callback handle_all_runs_cb
3184
+ * @param {string} payload - the received message (request). Messages that are not JSON are discarded
3185
+ * @param {string} app_id - the app_id of the channel the request was sent on
3186
+ * @param {string} run_id - the run_id of the channel the request was sent on
3187
+ * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id)
3188
+ * @return {Object} the response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
3189
+ */
3190
+
3191
+ /**
3192
+ * Allows framework-level APIs to handle requests to a run-level channel *for ALL runs*
3193
+ *
3194
+ * @param channel
3195
+ * @param {handle_all_runs_cb} callback
3196
+ * @param done_callback
3197
+ */
3198
+ FRNetSubModule.prototype.handle_requests_on_all_runs = function(channel, callback, done_callback) {
3199
+ // Pad channel
3200
+ var padded_channel = this.net.pad_channel(channel, '+', '+');
3201
+ var ln = this.net;
3202
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
3203
+ try {
3204
+ var f = JSON.parse(mqtt_message);
3205
+ var f1 = extractRunIdAndAppId(mqtt_channel);
3206
+ // Only handle requests that have proper id set
3207
+ if(f.type!=='request' || f.id===undefined) return;
3208
+ // Execute callback and send response
3209
+ var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id);
3210
+ ln.nutella.mqtt_client.publish( padded_channel, m );
3211
+ } catch(e) {
3212
+ if (e instanceof SyntaxError) {
3213
+ // Message is not JSON, drop it
3214
+ } else {
3215
+ // Bubble up whatever exception is thrown
3216
+ throw e;
3217
+ }
3218
+ }
3219
+ };
3220
+ this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback);
3221
+ // Notify subscription
3222
+ this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined);
3223
+ };
3224
+
3225
+
3226
+
3227
+ //----------------------------------------------------------------------------------------------------------------
3228
+ // Framework-level APIs to communicate at the application-level
3229
+ //----------------------------------------------------------------------------------------------------------------
3230
+
3231
+
3232
+ /**
3233
+ * Allows framework-level APIs to subscribe to an app-level channel
3234
+ *
3235
+ * @param app_id
3236
+ * @param channel
3237
+ * @param callback
3238
+ * @param done_callback
3239
+ */
3240
+ FRNetSubModule.prototype.subscribe_to_app = function(app_id, channel, callback, done_callback) {
3241
+ this.net.subscribe_to(channel,callback,app_id, undefined, done_callback)
3242
+ };
3243
+
3244
+
3245
+ /**
3246
+ * Allows framework-level APIs to unsubscribe from an app-level channel within a specific run
3247
+ *
3248
+ * @param app_id
3249
+ * @param channel
3250
+ * @param done_callback
3251
+ */
3252
+ FRNetSubModule.prototype.unsubscribe_to_app = function( app_id, channel, done_callback) {
3253
+ this.net.unsubscribe_from(channel,app_id,undefined, done_callback);
3254
+ };
3255
+
3256
+
3257
+ /**
3258
+ * Allows framework-level APIs to publish to an app-level channel
3259
+ *
3260
+ * @param app_id
3261
+ * @param channel
3262
+ * @param message
3263
+ */
3264
+ FRNetSubModule.prototype.publish_to_app = function(app_id, channel, message) {
3265
+ this.net.publish_to(channel,message,app_id,undefined);
3266
+ };
3267
+
3268
+
3269
+ /**
3270
+ * Allows framework-level APIs to make an asynchronous request to a run-level channel within a specific run
3271
+ *
3272
+ * @param app_id
3273
+ * @param channel
3274
+ * @param request
3275
+ * @param callback
3276
+ */
3277
+ FRNetSubModule.prototype.request_to_app = function( app_id, channel, request, callback) {
3278
+ this.net.request_to(channel, request, callback, app_id, undefined);
3279
+ };
3280
+
3281
+
3282
+ /**
3283
+ * Allows framework-level APIs to handle requests on a run-level channel within a specific run
3284
+ *
3285
+ * @param app_id
3286
+ * @param channel
3287
+ * @param callback
3288
+ * @param done_callback
3289
+ */
3290
+ FRNetSubModule.prototype.handle_requests_on_app = function(app_id, channel, callback, done_callback) {
3291
+ this.net.handle_requests_on(channel, callback, app_id, undefined, done_callback);
3292
+ };
3293
+
3294
+
3295
+ //----------------------------------------------------------------------------------------------------------------
3296
+ // Framework-level APIs to communicate at the application-level (broadcast)
3297
+ //----------------------------------------------------------------------------------------------------------------
3298
+
3299
+ /**
3300
+ * Callback used to handle all messages received when subscribing to all applications
3301
+ * @callback subscribeToAllAppsCb
3302
+ * @param {string} message - the received message. Messages that are not JSON are discarded
3303
+ * @param {string} app_id - the app_id of the channel the message was sent on
3304
+ * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
3305
+ */
3306
+
3307
+ /**
3308
+ * Allows framework-level APIs to subscribe to an app-level channel *for ALL apps*
3309
+ *
3310
+ * @param channel
3311
+ * @param {subscribeToAllAppsCb} callback
3312
+ * @param done_callback
3313
+ */
3314
+ FRNetSubModule.prototype.subscribe_to_all_apps = function(channel, callback, done_callback) {
3315
+ //Pad channel
3316
+ var padded_channel = this.net.pad_channel(channel, '+', undefined);
3317
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
3318
+ try {
3319
+ var f = JSON.parse(mqtt_message);
3320
+ var app_id = extractAppId(mqtt_channel);
3321
+ if(f.type==='publish')
3322
+ callback(f.payload, app_id, f.from);
3323
+ } catch(e) {
3324
+ if (e instanceof SyntaxError) {
3325
+ // Message is not JSON, drop it
3326
+ } else {
3327
+ // Bubble up whatever exception is thrown
3328
+ throw e;
3329
+ }
3330
+ }
3331
+ };
3332
+ // Add to subscriptions, save mqtt callback and subscribe
3333
+ this.net.subscriptions.push(padded_channel);
3334
+ this.net.callbacks.push(mqtt_cb);
3335
+ this.net.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback);
3336
+ // Notify subscription
3337
+ this.net.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, undefined, undefined);
3338
+ };
3339
+
3340
+
3341
+ /**
3342
+ * Allows framework-level APIs to unsubscribe from an app-level channel *for ALL apps*
3343
+ *
3344
+ * @param channel
3345
+ * @param done_callback
3346
+ */
3347
+ FRNetSubModule.prototype.unsubscribe_from_all_apps = function(channel, done_callback) {
3348
+ this.net.unsubscribe_from(channel, '+', undefined, done_callback);
3349
+ };
3350
+
3351
+
3352
+ /**
3353
+ * Allows framework-level APIs to publish a message to an app-level channel *for ALL apps*
3354
+ *
3355
+ * @param channel
3356
+ * @param message
3357
+ */
3358
+ FRNetSubModule.prototype.publish_to_all_apps = function(channel, message) {
3359
+ Object.keys(this.net.nutella.runs_list).forEach(function(app_id) {
3360
+ this.net.publish_to(channel, message, app_id, undefined);
3361
+ }.bind(this));
3362
+ };
3363
+
3364
+
3365
+ /**
3366
+ * Allows framework-level APIs to send a request to an app-level channel *for ALL apps*
3367
+ *
3368
+ * @param channel
3369
+ * @param request
3370
+ * @param callback
3371
+ */
3372
+ FRNetSubModule.prototype.request_to_all_apps = function(channel, request, callback) {
3373
+ Object.keys(this.net.nutella.runs_list).forEach(function(app_id) {
3374
+ this.net.request_to(channel, request, callback, app_id, undefined);
3375
+ }.bind(this));
3376
+ };
3377
+
3378
+
3379
+ /**
3380
+ * This callback is used to handle messages coming from all applications
3381
+ * @callback handleAllAppsCb
3382
+ * @param {string} request - the received message (request). Messages that are not JSON are discarded.
3383
+ * @param {string} app_id - the app_id of the channel the request was sent on
3384
+ * @param {Object} from - the sender's identifiers (from containing, run_id, app_id, component_id and optionally resource_id)
3385
+ * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
3386
+ */
3387
+
3388
+ /**
3389
+ * Allows framework-level APIs to handle requests to app-level channel *for ALL runs*
3390
+ *
3391
+ * @param channel
3392
+ * @param {handleAllAppsCb} callback
3393
+ * @param done_callback
3394
+ */
3395
+ FRNetSubModule.prototype.handle_requests_on_all_apps = function(channel, callback, done_callback) {
3396
+ // Pad channel
3397
+ var padded_channel = this.net.pad_channel(channel, '+', undefined);
3398
+ var ln = this.net;
3399
+ var mqtt_cb = function(mqtt_message, mqtt_channel) {
3400
+ try {
3401
+ var f = JSON.parse(mqtt_message);
3402
+ var f1 = extractRunIdAndAppId(mqtt_channel);
3403
+ // Only handle requests that have proper id set
3404
+ if(f.type!=='request' || f.id===undefined) return;
3405
+ // Execute callback and send response
3406
+ var m = ln.prepare_message_for_response(callback(f.payload, f1.appId, f1.runId, f.from), f.id);
3407
+ ln.nutella.mqtt_client.publish( padded_channel, m );
3408
+ } catch(e) {
3409
+ if (e instanceof SyntaxError) {
3410
+ // Message is not JSON, drop it
3411
+ } else {
3412
+ // Bubble up whatever exception is thrown
3413
+ throw e;
3414
+ }
3415
+ }
3416
+ };
3417
+ this.net.nutella.mqtt_client.subscribe( padded_channel, mqtt_cb, done_callback);
3418
+ // Notify subscription
3419
+ this.net.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, undefined, undefined);
3420
+ };
3421
+
3422
+
3423
+ // Utility functions
3424
+
3425
+
3426
+ function extractRunIdAndAppId(mqtt_channel) {
3427
+ var sp = mqtt_channel.replace('/nutella/apps/', '').split('/');
3428
+ return {appId: sp[0], runId: sp[2]};
3429
+ }
3430
+
3431
+ function extractAppId(mqtt_channel) {
3432
+ var sp = mqtt_channel.replace('/nutella/apps/', '').split('/');
3433
+ return sp[0];
3434
+ }
3435
+
3436
+
3437
+
3438
+
3439
+ module.exports = FRNetSubModule;
3440
+
3441
+ },{"./util/net":13}],10:[function(require,module,exports){
3442
+ /**
3443
+ * Run-level and App-level Nutella instances for the browser
3444
+ */
3445
+
3446
+ var SimpleMQTTClient = require('simple-mqtt-client');
3447
+
3448
+ // Require various sub-modules
3449
+ var AppSubModule = require('./app_core_browser');
3450
+ var FrSubModule = require('./fr_core_browser');
3451
+ var NetSubModule = require('./run_net');
3452
+ var LogSubModule = require('./run_log');
3453
+
3454
+
3455
+ /**
3456
+ * Defines the RunNutellaInstance class.
3457
+ *
3458
+ * @param {String } app_id - the app_id this component belongs to
3459
+ * @param {string} run_id - the run_id this component is launched in
3460
+ * @param {string} broker_hostname - the hostname of the broker.
3461
+ * @param {string} component_id - the name of this component
3462
+ */
3463
+ var RunNutellaInstance = function (broker_hostname, app_id, run_id, component_id) {
3464
+ //Initialize parameters
3465
+ this.mqtt_client = new SimpleMQTTClient(broker_hostname);
3466
+ this.appId = app_id;
3467
+ this.runId = run_id;
3468
+ this.componentId = component_id;
3469
+ // Initialized the various sub-modules
3470
+ this.net = new NetSubModule(this);
3471
+ this.log = new LogSubModule(this);
3472
+ // Start pinging
3473
+ setInterval(function(){
3474
+ this.net.publish('pings', 'ping');
3475
+ }.bind(this),5000);
3476
+ };
3477
+
3478
+ /**
3479
+ * Sets the resource id for this instance of nutella
3480
+ *
3481
+ * @param {string} resource_id - the resource_id associated to this instance of nutella
3482
+ */
3483
+ RunNutellaInstance.prototype.setResourceId = function(resource_id){
3484
+ this.resourceId = resource_id;
3485
+ };
3486
+
3487
+
3488
+ /**
3489
+ * Defines the AppNutellaInstance class.
3490
+ *
3491
+ * @param {String } app_id - the app_id this component belongs to
3492
+ * @param {string} broker_hostname - the hostname of the broker.
3493
+ * @param {string} component_id - the name of this component
3494
+ */
3495
+ var AppNutellaInstance = function (broker_hostname, app_id, component_id) {
3496
+ //Initialize parameters
3497
+ this.mqtt_client = new SimpleMQTTClient(broker_hostname);
3498
+ this.appId = app_id;
3499
+ this.componentId = component_id;
3500
+ // Initialized the various sub-modules
3501
+ this.app = new AppSubModule(this);
3502
+ //Initialize the runs list
3503
+ this.runs_list = [];
3504
+ // Fetch the runs list
3505
+ this.app.net.request('app_runs_list', undefined, function(response) {
3506
+ this.runs_list = response;
3507
+ }.bind(this));
3508
+ // Subscribe to runs list updates
3509
+ this.app.net.subscribe('app_runs_list', function(message, from) {
3510
+ this.runs_list = message;
3511
+ }.bind(this));
3512
+ // Start pinging
3513
+ setInterval(function(){
3514
+ this.app.net.publish('pings', 'ping');
3515
+ }.bind(this),5000);
3516
+ };
3517
+
3518
+ /**
3519
+ * Sets the resource id for this instance of nutella
3520
+ *
3521
+ * @param {string} resource_id - the resource_id associated to this instance of nutella
3522
+ */
3523
+ AppNutellaInstance.prototype.setResourceId = function(resource_id){
3524
+ this.resourceId = resource_id;
3525
+ };
3526
+
3527
+
3528
+ /**
3529
+ * Defines the FRNutellaInstance class.
3530
+ *
3531
+ * @param {string} broker_hostname - the hostname of the broker.
3532
+ * @param {string} component_id - the name of this component
3533
+ */
3534
+ var FrNutellaInstance = function (broker_hostname, component_id) {
3535
+ //Initialize parameters
3536
+ this.mqtt_client = new SimpleMQTTClient(broker_hostname);
3537
+ this.componentId = component_id;
3538
+ // Initialize the various sub-modules
3539
+ this.f = new FrSubModule(this);
3540
+ //Initialize the runs list
3541
+ this.runs_list = {};
3542
+ // Fetch the runs list
3543
+ this.f.net.request('runs_list', undefined, function(response) {
3544
+ this.runs_list = response;
3545
+ }.bind(this));
3546
+ // Subscribe to runs list updates
3547
+ this.f.net.subscribe('runs_list', function(message, from) {
3548
+ this.runs_list = message;
3549
+ }.bind(this));
3550
+ // Start pinging
3551
+ setInterval(function(){
3552
+ this.f.net.publish('pings', 'ping');
3553
+ }.bind(this),5000);
3554
+ };
3555
+
3556
+ /**
3557
+ * Sets the resource id for this instance of nutella
3558
+ *
3559
+ * @param {string} resource_id - the resource_id associated to this instance of nutella
3560
+ */
3561
+ FrNutellaInstance.prototype.setResourceId = function(resource_id){
3562
+ this.resourceId = resource_id;
3563
+ };
3564
+
3565
+
3566
+
3567
+ module.exports = {
3568
+ RunNutellaInstance : RunNutellaInstance,
3569
+ AppNutellaInstance : AppNutellaInstance,
3570
+ FrNutellaInstance : FrNutellaInstance
3571
+ };
3572
+ },{"./app_core_browser":4,"./fr_core_browser":7,"./run_log":11,"./run_net":12,"simple-mqtt-client":2}],11:[function(require,module,exports){
3573
+ /**
3574
+ * Run-level Logging APIs for nutella
3575
+ */
3576
+
3577
+ var NetSubModule = require('./run_net');
3578
+
3579
+ var LogSubModule = function(main_nutella) {
3580
+ this.net = new NetSubModule(main_nutella);
3581
+ };
3582
+
3583
+
3584
+ LogSubModule.prototype.debug = function(message, code) {
3585
+ console.debug(message);
3586
+ this.net.publish('logging', logToJson(message, code, 'debug'));
3587
+ return code;
3588
+ };
3589
+
3590
+ LogSubModule.prototype.info = function(message, code) {
3591
+ console.info(message);
3592
+ this.net.publish('logging', logToJson(message, code, 'info'));
3593
+ return code;
3594
+ };
3595
+
3596
+ LogSubModule.prototype.success = function(message, code) {
3597
+ console.log('%c '+ message , 'color: #009933');
3598
+ this.net.publish('logging', logToJson(message, code, 'success'));
3599
+ return code;
3600
+ };
3601
+
3602
+ LogSubModule.prototype.warn = function(message, code) {
3603
+ console.warn(message);
3604
+ this.net.publish('logging', logToJson(message, code, 'warn'));
3605
+ return code;
3606
+ };
3607
+
3608
+ LogSubModule.prototype.error = function(message, code) {
3609
+ console.error(message);
3610
+ this.net.publish('logging', logToJson(message, code, 'error'));
3611
+ return code;
3612
+ };
3613
+
3614
+
3615
+ function logToJson( message, code, level) {
3616
+ return (code===undefined) ? {level: level, message: message} : {level: level, message: message, code: code};
3617
+ }
3618
+
3619
+
3620
+
3621
+
3622
+
3623
+ module.exports = LogSubModule;
3624
+ },{"./run_net":12}],12:[function(require,module,exports){
3625
+ /**
3626
+ * Run-level Network APIs for nutella
3627
+ */
3628
+
3629
+
3630
+ var AbstractNet = require('./util/net');
3631
+
3632
+
3633
+ /**
3634
+ * Run-level network APIs for nutella
3635
+ * @param main_nutella
3636
+ * @constructor
3637
+ */
3638
+ var NetSubModule = function(main_nutella) {
3639
+ // Store a reference to the main module
3640
+ this.nutella = main_nutella;
3641
+ this.net = new AbstractNet(main_nutella);
3642
+ };
3643
+
3644
+
3645
+
3646
+ /**
3647
+ * Subscribes to a channel or filter.
3648
+ *
3649
+ * @param channel
3650
+ * @param callback
3651
+ * @param done_callback
3652
+ */
3653
+ NetSubModule.prototype.subscribe = function(channel, callback, done_callback) {
3654
+ this.net.subscribe_to(channel, callback, this.nutella.appId, this.nutella.runId, done_callback);
3655
+ };
3656
+
3657
+
3658
+
3659
+ /**
3660
+ * Unsubscribes from a channel
3661
+ *
3662
+ * @param channel
3663
+ * @param done_callback
3664
+ */
3665
+ NetSubModule.prototype.unsubscribe = function(channel, done_callback) {
3666
+ this.net.unsubscribe_from(channel, this.nutella.appId, this.nutella.runId, done_callback);
3667
+ };
3668
+
3669
+
3670
+
3671
+ /**
3672
+ * Publishes a message to a channel
3673
+ *
3674
+ * @param channel
3675
+ * @param message
3676
+ */
3677
+ NetSubModule.prototype.publish = function(channel, message) {
3678
+ this.net.publish_to(channel, message, this.nutella.appId, this.nutella.runId);
3679
+ };
3680
+
3681
+
3682
+
3683
+ /**
3684
+ * Sends a request.
3685
+ *
3686
+ * @param channel
3687
+ * @param message
3688
+ * @param callback
3689
+ */
3690
+ NetSubModule.prototype.request = function(channel, message, callback) {
3691
+ this.net.request_to(channel, message, callback, this.nutella.appId, this.nutella.runId);
3692
+ };
3693
+
3694
+
3695
+
3696
+ /**
3697
+ * Handles requests.
3698
+ *
3699
+ * @param channel
3700
+ * @param callback
3701
+ * @param done_callback
3702
+ */
3703
+ NetSubModule.prototype.handle_requests = function(channel, callback, done_callback) {
3704
+ this.net.handle_requests_on(channel, callback, this.nutella.appId, this.nutella.runId, done_callback);
3705
+ };
3706
+
3707
+
3708
+ module.exports = NetSubModule;
3709
+
3710
+ },{"./util/net":13}],13:[function(require,module,exports){
3711
+ /**
3712
+ * Network APIs abstraction
3713
+ */
3714
+
3715
+
3716
+ var AbstractNet = function(main_nutella) {
3717
+ this.subscriptions = [];
3718
+ this.callbacks = [];
3719
+ this.nutella = main_nutella;
3720
+ };
3721
+
3722
+
3723
+ /**
3724
+ * This callback is fired whenever a message is received by the subscribe
3725
+ *
3726
+ * @callback subscribeCallback
3727
+ * @param {string} message - the received message. Messages that are not JSON are discarded
3728
+ * @param {string} [channel] - the channel the message was received on (optional, only for wildcard subscriptions)
3729
+ * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
3730
+ */
3731
+
3732
+ /**
3733
+ * Subscribes to a channel or to a set of channels
3734
+ *
3735
+ * @param {string} channel - the channel or filter we are subscribing to. Can contain wildcard(s)
3736
+ * @param {subscribeCallback} callback - fired whenever a message is received
3737
+ * @param {string|undefined} appId - used to pad channels
3738
+ * @param {string|undefined} runId - used to pad channels
3739
+ * @param {function} done_callback - fired whenever the subscribe is successful
3740
+ */
3741
+ AbstractNet.prototype.subscribe_to = function(channel, callback, appId, runId, done_callback) {
3742
+ // Pad channel
3743
+ var padded_channel = this.pad_channel(channel, appId, runId);
3744
+ // Maintain unique subscriptions
3745
+ if(this.subscriptions.indexOf(padded_channel)>-1)
3746
+ throw 'You can`t subscribe twice to the same channel`';
3747
+ // Depending on what type of channel we are subscribing to (wildcard or simple)
3748
+ // register a different kind of callback
3749
+ var mqtt_cb;
3750
+ if(this.nutella.mqtt_client.isChannelWildcard(padded_channel))
3751
+ mqtt_cb = function(mqtt_message, mqtt_channel) {
3752
+ try {
3753
+ var f = JSON.parse(mqtt_message);
3754
+ if(f.type==='publish')
3755
+ callback(f.payload, this.un_pad_channel(mqtt_channel, appId, runId), f.from);
3756
+ } catch(e) {
3757
+ if (e instanceof SyntaxError) {
3758
+ // Message is not JSON, drop it
3759
+ } else {
3760
+ // Bubble up whatever exception is thrown
3761
+ throw e;
3762
+ }
3763
+ }
3764
+ };
3765
+ else
3766
+ mqtt_cb = function(mqtt_message) {
3767
+ try {
3768
+ var f = JSON.parse(mqtt_message);
3769
+ if(f.type==='publish')
3770
+ callback(f.payload, f.from);
3771
+ } catch(e) {
3772
+ if (e instanceof SyntaxError) {
3773
+ // Message is not JSON, drop it
3774
+ } else {
3775
+ // Bubble up whatever exception is thrown
3776
+ throw e;
3777
+ }
3778
+ }
3779
+ };
3780
+ // Add to subscriptions, save mqtt callback and subscribe
3781
+ this.subscriptions.push(padded_channel);
3782
+ this.callbacks.push(mqtt_cb);
3783
+ this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback);
3784
+ // Notify subscription
3785
+ this.publish_to('subscriptions', {type: 'subscribe', channel: padded_channel}, appId, runId);
3786
+ };
3787
+
3788
+
3789
+ /**
3790
+ * Unsubscribes from a channel or a set of channels
3791
+ *
3792
+ * @param {string} channel - we want to unsubscribe from. Can contain wildcard(s)
3793
+ * @param {string|undefined} appId - used to pad channels
3794
+ * @param {string|undefined} runId - used to pad channels
3795
+ * @param {function} done_callback - fired whenever the subscribe is successful
3796
+ */
3797
+ AbstractNet.prototype.unsubscribe_from = function(channel, appId, runId, done_callback ) {
3798
+ // Pad channel
3799
+ var padded_channel = this.pad_channel(channel, appId, run_id);
3800
+ var idx = this.subscriptions.indexOf(padded_channel);
3801
+ // If we are not subscribed to this channel, return (no error is given)
3802
+ if(idx===-1) return;
3803
+ // Fetch the mqtt_callback associated with this channel/subscription
3804
+ var mqtt_cb = this.callbacks[idx];
3805
+ // Remove from subscriptions, callbacks and unsubscribe
3806
+ this.subscriptions.splice(idx, 1);
3807
+ this.callbacks.splice(idx, 1);
3808
+ this.nutella.mqtt_client.unsubscribe(padded_channel, mqtt_cb, done_callback);
3809
+ };
3810
+
3811
+
3812
+ /**
3813
+ * Publishes a message to a channel
3814
+ *
3815
+ * @param {String} channel - the channel we want to publish the message to. *CANNOT* contain wildcard(s)!
3816
+ * @param {Object} message - the message we are publishing. This can be any JS variable, even undefined.
3817
+ * @param {String|undefined} appId - used to pad the channels
3818
+ * @param {String|undefined} runId - used to pad the channels
3819
+ */
3820
+ AbstractNet.prototype.publish_to = function(channel, message, appId, runId) {
3821
+ // Pad channel
3822
+ var padded_channel = this.pad_channel(channel, appId, runId);
3823
+ // Throw exception if trying to publish something that is not JSON
3824
+ try {
3825
+ var m = this.prepare_message_for_publish(message);
3826
+ this.nutella.mqtt_client.publish(padded_channel, m);
3827
+ } catch(e) {
3828
+ console.error('Error: you are trying to publish something that is not JSON');
3829
+ console.error(e.message);
3830
+ }
3831
+ };
3832
+
3833
+
3834
+ /**
3835
+ * This callback is fired whenever a response to a request is received
3836
+ *
3837
+ * @callback requestCallback
3838
+ * @param {string} response - the body of the response
3839
+ */
3840
+
3841
+ /**
3842
+ * Performs an asynchronous request
3843
+ *
3844
+ * @param {string} channel - the channel we want to make the request to. *CANNOT* contain wildcard(s)!
3845
+ * @param {string} message - the body of the request. This can be any JS variable, even undefined.
3846
+ * @param {requestCallback} callback - the callback that is fired whenever a response is received
3847
+ * @param {string|undefined} appId - used to pad channels
3848
+ * @param {string|undefined} runId - used to pad channels
3849
+ */
3850
+ AbstractNet.prototype.request_to = function( channel, message, callback, appId, runId ) {
3851
+ // Save nutella reference
3852
+ var nut = this.nutella;
3853
+ // Pad channel
3854
+ var padded_channel = this.pad_channel(channel, appId, runId);
3855
+ // Prepare message
3856
+ var m = this.prepare_message_for_request(message);
3857
+ //Prepare callback
3858
+ var mqtt_cb = function(mqtt_message) {
3859
+ var f = JSON.parse(mqtt_message);
3860
+ if (f.id===m.id && f.type==='response') {
3861
+ callback(f.payload);
3862
+ nut.mqtt_client.unsubscribe(padded_channel, mqtt_cb);
3863
+ }
3864
+ };
3865
+ // Subscribe
3866
+ this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, function() {
3867
+ // Publish message
3868
+ nut.mqtt_client.publish( padded_channel, m.message );
3869
+ });
3870
+
3871
+ };
3872
+
3873
+
3874
+ /**
3875
+ * This callback is fired whenever a request is received that needs to be handled
3876
+ *
3877
+ * @callback handleCallback
3878
+ * @param {string} request - the body of the received request (payload). Messages that are not JSON are discarded.
3879
+ * @param {Object} from - the sender's identifiers (run_id, app_id, component_id and optionally resource_id)
3880
+ * @return {Object} The response sent back to the client that performed the request. Whatever is returned by the callback is marshaled into a JSON string and sent via MQTT.
3881
+ */
3882
+
3883
+ /**
3884
+ * Handles requests on a certain channel or a certain set of channels
3885
+ *
3886
+ * @param {string} channel - the channel we want to listen for requests on. Can contain wildcard(s).
3887
+ * @param {handleCallback} callback - fired whenever a message is received
3888
+ * @param {string|undefined} appId - used to pad channels
3889
+ * @param {string|undefined} runId - used to pad channels
3890
+ * @param {function} done_callback - fired whenever we are ready to handle requests
3891
+ */
3892
+ AbstractNet.prototype.handle_requests_on = function( channel, callback, appId, runId, done_callback) {
3893
+ // Save nutella reference
3894
+ var nut = this.nutella;
3895
+ // Pad channel
3896
+ var padded_channel = this.pad_channel(channel, appId, runId);
3897
+ var mqtt_cb = function(request) {
3898
+ try {
3899
+ // Extract nutella fields
3900
+ var f = JSON.parse(request);
3901
+ // Only handle requests that have proper id set
3902
+ if(f.type!=='request' || f.id===undefined) return;
3903
+ // Execute callback and send response
3904
+ var m = this.prepare_message_for_response(callback(f.payload, f.from), f.id);
3905
+ nut.mqtt_client.publish( padded_channel, m );
3906
+ } catch(e) {
3907
+ if (e instanceof SyntaxError) {
3908
+ // Message is not JSON, drop it
3909
+ } else {
3910
+ // Bubble up whatever exception is thrown
3911
+ throw e;
3912
+ }
3913
+ }
3914
+ };
3915
+ // Subscribe to the channel
3916
+ this.nutella.mqtt_client.subscribe(padded_channel, mqtt_cb, done_callback);
3917
+ // Notify subscription
3918
+ this.publish_to('subscriptions', {type: 'handle_requests', channel: padded_channel}, appId, runId);
3919
+ };
3920
+
3921
+
3922
+
3923
+ /**
3924
+ * Pads the channel with app_id and run_id
3925
+ *
3926
+ * @param channel
3927
+ * @param app_id
3928
+ * @param run_id
3929
+ * @return {string} the padded channel
3930
+ */
3931
+ AbstractNet.prototype.pad_channel = function(channel, app_id, run_id) {
3932
+ if (run_id!==undefined && app_id===undefined)
3933
+ throw 'If the run_id is specified, app_id needs to also be specified';
3934
+ if (app_id===undefined && run_id===undefined)
3935
+ return '/nutella/' + channel;
3936
+ if (app_id!==undefined && run_id===undefined)
3937
+ return '/nutella/apps/' + app_id + '/' + channel;
3938
+ return '/nutella/apps/' + app_id + '/runs/' + run_id + '/' + channel;
3939
+ };
3940
+
3941
+
3942
+ /**
3943
+ * Un-pads the channel with app_id and run_id
3944
+ *
3945
+ * @param channel
3946
+ * @param app_id
3947
+ * @param run_id
3948
+ * @return {string} the un-padded channel
3949
+ */
3950
+ AbstractNet.prototype.un_pad_channel = function(channel, app_id, run_id) {
3951
+ if (run_id!==undefined && app_id===undefined)
3952
+ throw 'If the run_id is specified, app_id needs to also be specified';
3953
+ if (app_id===undefined && run_id===undefined)
3954
+ return channel.replace('/nutella/', '');
3955
+ if (app_id!==undefined && run_id===undefined)
3956
+ return channel.replace("/nutella/apps/" + app_id + "/", '');
3957
+ return channel.replace("/nutella/apps/" + app_id + "/runs/" + run_id + "/", '');
3958
+ };
3959
+
3960
+
3961
+ /**
3962
+ * Assembles the unique ID of the component, starting from app_id, run_id, component_id and resource_id
3963
+ *
3964
+ * @return {Object} an object containing the unique ID of the component sending the message
3965
+ */
3966
+ AbstractNet.prototype.assemble_from = function() {
3967
+ var from = {};
3968
+ // Set type, run_id and app_id whenever appropriate
3969
+ if(this.nutella.runId===undefined) {
3970
+ if(this.nutella.appId===undefined) {
3971
+ from.type = 'framework';
3972
+ } else {
3973
+ from.type = 'app';
3974
+ from.app_id = this.nutella.appId;
3975
+ }
3976
+ } else {
3977
+ from.type = 'run';
3978
+ from.app_id = this.nutella.appId;
3979
+ from.run_id = this.nutella.runId;
3980
+ }
3981
+ // Set the component_id
3982
+ from.component_id = this.nutella.componentId;
3983
+ // Set resource_id, if defined
3984
+ if (this.nutella.resourceId!==undefined)
3985
+ from.resource_id = this.nutella.resourceId;
3986
+ return from;
3987
+ };
3988
+
3989
+
3990
+ /**
3991
+ * Prepares a message for a publish
3992
+ *
3993
+ * @param {Object} message - the message content
3994
+ * @return {string} the serialized message, ready to be shipped over the net
3995
+ */
3996
+ AbstractNet.prototype.prepare_message_for_publish = function (message) {
3997
+ if(message===undefined)
3998
+ return JSON.stringify({type: 'publish', from: this.assemble_from()});
3999
+ return JSON.stringify({type: 'publish', from: this.assemble_from(), payload: message});
4000
+ };
4001
+
4002
+
4003
+ /**
4004
+ * Prepares a message for a request
4005
+ *
4006
+ * @param {Object} message - the message content
4007
+ * @return {Object} the serialized response, ready to be shipped over the net and the id of the response
4008
+ */
4009
+ AbstractNet.prototype.prepare_message_for_request = function (message) {
4010
+ var id = Math.floor((Math.random() * 100000) + 1).toString();
4011
+ var m = {};
4012
+ m.id = id;
4013
+ if(message===undefined)
4014
+ m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from()});
4015
+ else
4016
+ m.message = JSON.stringify({id: id, type: 'request', from: this.assemble_from(), payload: message});
4017
+ return m;
4018
+ };
4019
+
4020
+
4021
+ /**
4022
+ * Prepares a message for a response
4023
+ *
4024
+ * @param {Object} response - the response content
4025
+ * @param {string} id - the original request id
4026
+ * @return {string} the serialized message, ready to be shipped over the net
4027
+ */
4028
+ AbstractNet.prototype.prepare_message_for_response = function (response, id) {
4029
+ if(response===undefined)
4030
+ return JSON.stringify({id: id, type: 'response', from: this.assemble_from()});
4031
+ return JSON.stringify({id: id, type: 'response', from: this.assemble_from(), payload: response});
4032
+ };
4033
+
4034
+
4035
+
4036
+ // Export module
4037
+ module.exports = AbstractNet;
4038
+ },{}]},{},[1])(1)
4039
+ });