nutella_framework 0.4.5 → 0.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +14 -15
  3. data/VERSION +1 -1
  4. data/framework_components/beacon-cloud-bot/README.md +27 -0
  5. data/framework_components/beacon-cloud-bot/beacon_cloud_bot.rb +154 -0
  6. data/framework_components/beacon-cloud-bot/nutella.json +6 -0
  7. data/framework_components/beacon-cloud-bot/startup +4 -0
  8. data/framework_components/beacon-cloud-interface/LICENSE +21 -0
  9. data/framework_components/beacon-cloud-interface/Readme.md +0 -0
  10. data/framework_components/beacon-cloud-interface/bower.json +29 -0
  11. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/.bower.json +23 -0
  12. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/bower.json +14 -0
  13. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/mqttws31.js +2081 -0
  14. data/framework_components/beacon-cloud-interface/bower_components/bower-mqttws/readme.md +4 -0
  15. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/.bower.json +37 -0
  16. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/LICENSE +21 -0
  17. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/README.md +15 -0
  18. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/bower.json +28 -0
  19. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/mqtt_client_hello_world.html +23 -0
  20. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/browser/nutella_hello_world.html +52 -0
  21. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/mqtt_client_hello_world.js +14 -0
  22. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/examples/node/nutella_hello_world.js +38 -0
  23. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/nutella_lib.js +789 -0
  24. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/package.json +30 -0
  25. data/framework_components/beacon-cloud-interface/bower_components/nutella_lib/simple-js-mqtt-client.js +428 -0
  26. data/framework_components/beacon-cloud-interface/css/animation.css +17 -0
  27. data/framework_components/beacon-cloud-interface/css/cursor.css +16 -0
  28. data/framework_components/beacon-cloud-interface/css/page_layout.css +73 -0
  29. data/framework_components/beacon-cloud-interface/index.html +157 -0
  30. data/framework_components/beacon-cloud-interface/js/lib/nutella_lib.js +4039 -0
  31. data/framework_components/beacon-cloud-interface/js/react/beacon-add.js +102 -0
  32. data/framework_components/beacon-cloud-interface/js/react/beacon-table.js +73 -0
  33. data/framework_components/beacon-cloud-interface/js/react/beacon.js +97 -0
  34. data/framework_components/beacon-cloud-interface/nutella.json +6 -0
  35. data/framework_components/example_framework_web_interface/index.html +11 -2
  36. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.npmignore +10 -0
  37. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/.travis.yml +5 -0
  38. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/LICENSE +21 -0
  39. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/README.md +27 -0
  40. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js +4039 -0
  41. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/dist/nutella_lib.js.map +1 -0
  42. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/browser_hello_world.html +67 -0
  43. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/examples/node_hello_world.js +51 -0
  44. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/gulpfile.js +31 -0
  45. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/package.json +41 -0
  46. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core.js +19 -0
  47. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_core_browser.js +17 -0
  48. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_log.js +50 -0
  49. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_net.js +279 -0
  50. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/app_persist.js +20 -0
  51. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_core_browser.js +17 -0
  52. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_log.js +50 -0
  53. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/fr_net.js +499 -0
  54. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i.js +74 -0
  55. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_i_browser.js +130 -0
  56. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib.js +91 -0
  57. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/nutella_lib_browser.js +90 -0
  58. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_log.js +51 -0
  59. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_net.js +84 -0
  60. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/run_persist.js +20 -0
  61. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/src/util/net.js +327 -0
  62. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/nutella.test.js +16 -0
  63. data/framework_components/example_framework_web_interface/node_modules/nutella_lib/test/runner.html +22 -0
  64. data/framework_components/example_framework_web_interface/package.json +15 -0
  65. data/framework_components/{order.json.example → order.json} +0 -0
  66. data/framework_components/runs_list_bot/{app_runs_list_bot.rb → runs_list_bot.rb} +9 -3
  67. data/framework_components/runs_list_bot/startup +1 -1
  68. data/lib/commands/meta/run_command.rb +21 -36
  69. data/lib/commands/start.rb +9 -199
  70. data/lib/commands/util/components_list.rb +68 -0
  71. data/lib/commands/util/components_starter.rb +169 -0
  72. data/nutella_framework.gemspec +109 -47
  73. data/nutella_lib/framework_net.rb +17 -13
  74. metadata +84 -106
@@ -0,0 +1,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
+ });