Advertisement
Guest User

Untitled

a guest
Oct 28th, 2018
60
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 82.35 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
  5. <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script>
  6. <script type="text/javascript">
  7. //sample HTML/JS script that will publish/subscribe to topics in the Google Chrome Console
  8. //by Matthew Bordignon @bordignon on twitter.
  9.  
  10. /*******************************************************************************
  11. * Copyright (c) 2013 IBM Corp.
  12. *
  13. * All rights reserved. This program and the accompanying materials
  14. * are made available under the terms of the Eclipse Public License v1.0
  15. * and Eclipse Distribution License v1.0 which accompany this distribution.
  16. *
  17. * The Eclipse Public License is available at
  18. * http://www.eclipse.org/legal/epl-v10.html
  19. * and the Eclipse Distribution License is available at
  20. * http://www.eclipse.org/org/documents/edl-v10.php.
  21. *
  22. * Contributors:
  23. * Andrew Banks - initial API and implementation and initial documentation
  24. *******************************************************************************/
  25.  
  26.  
  27. // Only expose a single object name in the global namespace.
  28. // Everything must go through this module. Global Paho.MQTT module
  29. // only has a single public function, client, which returns
  30. // a Paho.MQTT client object given connection details.
  31.  
  32. /**
  33. * Send and receive messages using web browsers.
  34. * <p>
  35. * This programming interface lets a JavaScript client application use the MQTT V3.1 or
  36. * V3.1.1 protocol to connect to an MQTT-supporting messaging server.
  37. *
  38. * The function supported includes:
  39. * <ol>
  40. * <li>Connecting to and disconnecting from a server. The server is identified by its host name and port number.
  41. * <li>Specifying options that relate to the communications link with the server,
  42. * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required.
  43. * <li>Subscribing to and receiving messages from MQTT Topics.
  44. * <li>Publishing messages to MQTT Topics.
  45. * </ol>
  46. * <p>
  47. * The API consists of two main objects:
  48. * <dl>
  49. * <dt><b>{@link Paho.MQTT.Client}</b></dt>
  50. * <dd>This contains methods that provide the functionality of the API,
  51. * including provision of callbacks that notify the application when a message
  52. * arrives from or is delivered to the messaging server,
  53. * or when the status of its connection to the messaging server changes.</dd>
  54. * <dt><b>{@link Paho.MQTT.Message}</b></dt>
  55. * <dd>This encapsulates the payload of the message along with various attributes
  56. * associated with its delivery, in particular the destination to which it has
  57. * been (or is about to be) sent.</dd>
  58. * </dl>
  59. * <p>
  60. * The programming interface validates parameters passed to it, and will throw
  61. * an Error containing an error message intended for developer use, if it detects
  62. * an error with any parameter.
  63. * <p>
  64. * Example:
  65. *
  66. * <code><pre>
  67. client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
  68. client.onConnectionLost = onConnectionLost;
  69. client.onMessageArrived = onMessageArrived;
  70. client.connect({onSuccess:onConnect});
  71.  
  72. function onConnect() {
  73. // Once a connection has been made, make a subscription and send a message.
  74. console.log("onConnect");
  75. client.subscribe("/World");
  76. message = new Paho.MQTT.Message("Hello");
  77. message.destinationName = "/World";
  78. client.send(message);
  79. };
  80. function onConnectionLost(responseObject) {
  81. if (responseObject.errorCode !== 0)
  82. console.log("onConnectionLost:"+responseObject.errorMessage);
  83. };
  84. function onMessageArrived(message) {
  85. console.log("onMessageArrived:"+message.payloadString);
  86. client.disconnect();
  87. };
  88. * </pre></code>
  89. * @namespace Paho.MQTT
  90. */
  91.  
  92. if (typeof Paho === "undefined") {
  93. Paho = {};
  94. }
  95.  
  96. Paho.MQTT = (function (global) {
  97.  
  98. // Private variables below, these are only visible inside the function closure
  99. // which is used to define the module.
  100.  
  101. var version = "@VERSION@";
  102. var buildLevel = "@BUILDLEVEL@";
  103.  
  104. /**
  105. * Unique message type identifiers, with associated
  106. * associated integer values.
  107. * @private
  108. */
  109. var MESSAGE_TYPE = {
  110. CONNECT: 1,
  111. CONNACK: 2,
  112. PUBLISH: 3,
  113. PUBACK: 4,
  114. PUBREC: 5,
  115. PUBREL: 6,
  116. PUBCOMP: 7,
  117. SUBSCRIBE: 8,
  118. SUBACK: 9,
  119. UNSUBSCRIBE: 10,
  120. UNSUBACK: 11,
  121. PINGREQ: 12,
  122. PINGRESP: 13,
  123. DISCONNECT: 14
  124. };
  125.  
  126. // Collection of utility methods used to simplify module code
  127. // and promote the DRY pattern.
  128.  
  129. /**
  130. * Validate an object's parameter names to ensure they
  131. * match a list of expected variables name for this option
  132. * type. Used to ensure option object passed into the API don't
  133. * contain erroneous parameters.
  134. * @param {Object} obj - User options object
  135. * @param {Object} keys - valid keys and types that may exist in obj.
  136. * @throws {Error} Invalid option parameter found.
  137. * @private
  138. */
  139. var validate = function(obj, keys) {
  140. for (var key in obj) {
  141. if (obj.hasOwnProperty(key)) {
  142. if (keys.hasOwnProperty(key)) {
  143. if (typeof obj[key] !== keys[key])
  144. throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key]));
  145. } else {
  146. var errorStr = "Unknown property, " + key + ". Valid properties are:";
  147. for (var key in keys)
  148. if (keys.hasOwnProperty(key))
  149. errorStr = errorStr+" "+key;
  150. throw new Error(errorStr);
  151. }
  152. }
  153. }
  154. };
  155.  
  156. /**
  157. * Return a new function which runs the user function bound
  158. * to a fixed scope.
  159. * @param {function} User function
  160. * @param {object} Function scope
  161. * @return {function} User function bound to another scope
  162. * @private
  163. */
  164. var scope = function (f, scope) {
  165. return function () {
  166. return f.apply(scope, arguments);
  167. };
  168. };
  169.  
  170. /**
  171. * Unique message type identifiers, with associated
  172. * associated integer values.
  173. * @private
  174. */
  175. var ERROR = {
  176. OK: {code:0, text:"AMQJSC0000I OK."},
  177. CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."},
  178. SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."},
  179. UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."},
  180. PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."},
  181. INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"},
  182. CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."},
  183. SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."},
  184. SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."},
  185. MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."},
  186. UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."},
  187. INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."},
  188. INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."},
  189. INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."},
  190. UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."},
  191. INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."},
  192. INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."},
  193. MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."},
  194. };
  195.  
  196. /** CONNACK RC Meaning. */
  197. var CONNACK_RC = {
  198. 0:"Connection Accepted",
  199. 1:"Connection Refused: unacceptable protocol version",
  200. 2:"Connection Refused: identifier rejected",
  201. 3:"Connection Refused: server unavailable",
  202. 4:"Connection Refused: bad user name or password",
  203. 5:"Connection Refused: not authorized"
  204. };
  205.  
  206. /**
  207. * Format an error message text.
  208. * @private
  209. * @param {error} ERROR.KEY value above.
  210. * @param {substitutions} [array] substituted into the text.
  211. * @return the text with the substitutions made.
  212. */
  213. var format = function(error, substitutions) {
  214. var text = error.text;
  215. if (substitutions) {
  216. var field,start;
  217. for (var i=0; i<substitutions.length; i++) {
  218. field = "{"+i+"}";
  219. start = text.indexOf(field);
  220. if(start > 0) {
  221. var part1 = text.substring(0,start);
  222. var part2 = text.substring(start+field.length);
  223. text = part1+substitutions[i]+part2;
  224. }
  225. }
  226. }
  227. return text;
  228. };
  229.  
  230. //MQTT protocol and version 6 M Q I s d p 3
  231. var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03];
  232. //MQTT proto/version for 311 4 M Q T T 4
  233. var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04];
  234.  
  235. /**
  236. * Construct an MQTT wire protocol message.
  237. * @param type MQTT packet type.
  238. * @param options optional wire message attributes.
  239. *
  240. * Optional properties
  241. *
  242. * messageIdentifier: message ID in the range [0..65535]
  243. * payloadMessage: Application Message - PUBLISH only
  244. * connectStrings: array of 0 or more Strings to be put into the CONNECT payload
  245. * topics: array of strings (SUBSCRIBE, UNSUBSCRIBE)
  246. * requestQoS: array of QoS values [0..2]
  247. *
  248. * "Flag" properties
  249. * cleanSession: true if present / false if absent (CONNECT)
  250. * willMessage: true if present / false if absent (CONNECT)
  251. * isRetained: true if present / false if absent (CONNECT)
  252. * userName: true if present / false if absent (CONNECT)
  253. * password: true if present / false if absent (CONNECT)
  254. * keepAliveInterval: integer [0..65535] (CONNECT)
  255. *
  256. * @private
  257. * @ignore
  258. */
  259. var WireMessage = function (type, options) {
  260. this.type = type;
  261. for (var name in options) {
  262. if (options.hasOwnProperty(name)) {
  263. this[name] = options[name];
  264. }
  265. }
  266. };
  267.  
  268. WireMessage.prototype.encode = function() {
  269. // Compute the first byte of the fixed header
  270. var first = ((this.type & 0x0f) << 4);
  271.  
  272. /*
  273. * Now calculate the length of the variable header + payload by adding up the lengths
  274. * of all the component parts
  275. */
  276.  
  277. var remLength = 0;
  278. var topicStrLength = new Array();
  279. var destinationNameLength = 0;
  280.  
  281. // if the message contains a messageIdentifier then we need two bytes for that
  282. if (this.messageIdentifier != undefined)
  283. remLength += 2;
  284.  
  285. switch(this.type) {
  286. // If this a Connect then we need to include 12 bytes for its header
  287. case MESSAGE_TYPE.CONNECT:
  288. switch(this.mqttVersion) {
  289. case 3:
  290. remLength += MqttProtoIdentifierv3.length + 3;
  291. break;
  292. case 4:
  293. remLength += MqttProtoIdentifierv4.length + 3;
  294. break;
  295. }
  296.  
  297. remLength += UTF8Length(this.clientId) + 2;
  298. if (this.willMessage != undefined) {
  299. remLength += UTF8Length(this.willMessage.destinationName) + 2;
  300. // Will message is always a string, sent as UTF-8 characters with a preceding length.
  301. var willMessagePayloadBytes = this.willMessage.payloadBytes;
  302. if (!(willMessagePayloadBytes instanceof Uint8Array))
  303. willMessagePayloadBytes = new Uint8Array(payloadBytes);
  304. remLength += willMessagePayloadBytes.byteLength +2;
  305. }
  306. if (this.userName != undefined)
  307. remLength += UTF8Length(this.userName) + 2;
  308. if (this.password != undefined)
  309. remLength += UTF8Length(this.password) + 2;
  310. break;
  311.  
  312. // Subscribe, Unsubscribe can both contain topic strings
  313. case MESSAGE_TYPE.SUBSCRIBE:
  314. first |= 0x02; // Qos = 1;
  315. for ( var i = 0; i < this.topics.length; i++) {
  316. topicStrLength[i] = UTF8Length(this.topics[i]);
  317. remLength += topicStrLength[i] + 2;
  318. }
  319. remLength += this.requestedQos.length; // 1 byte for each topic's Qos
  320. // QoS on Subscribe only
  321. break;
  322.  
  323. case MESSAGE_TYPE.UNSUBSCRIBE:
  324. first |= 0x02; // Qos = 1;
  325. for ( var i = 0; i < this.topics.length; i++) {
  326. topicStrLength[i] = UTF8Length(this.topics[i]);
  327. remLength += topicStrLength[i] + 2;
  328. }
  329. break;
  330.  
  331. case MESSAGE_TYPE.PUBREL:
  332. first |= 0x02; // Qos = 1;
  333. break;
  334.  
  335. case MESSAGE_TYPE.PUBLISH:
  336. if (this.payloadMessage.duplicate) first |= 0x08;
  337. first = first |= (this.payloadMessage.qos << 1);
  338. if (this.payloadMessage.retained) first |= 0x01;
  339. destinationNameLength = UTF8Length(this.payloadMessage.destinationName);
  340. remLength += destinationNameLength + 2;
  341. var payloadBytes = this.payloadMessage.payloadBytes;
  342. remLength += payloadBytes.byteLength;
  343. if (payloadBytes instanceof ArrayBuffer)
  344. payloadBytes = new Uint8Array(payloadBytes);
  345. else if (!(payloadBytes instanceof Uint8Array))
  346. payloadBytes = new Uint8Array(payloadBytes.buffer);
  347. break;
  348.  
  349. case MESSAGE_TYPE.DISCONNECT:
  350. break;
  351.  
  352. default:
  353. ;
  354. }
  355.  
  356. // Now we can allocate a buffer for the message
  357.  
  358. var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format
  359. var pos = mbi.length + 1; // Offset of start of variable header
  360. var buffer = new ArrayBuffer(remLength + pos);
  361. var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes
  362.  
  363. //Write the fixed header into the buffer
  364. byteStream[0] = first;
  365. byteStream.set(mbi,1);
  366.  
  367. // If this is a PUBLISH then the variable header starts with a topic
  368. if (this.type == MESSAGE_TYPE.PUBLISH)
  369. pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos);
  370. // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time
  371.  
  372. else if (this.type == MESSAGE_TYPE.CONNECT) {
  373. switch (this.mqttVersion) {
  374. case 3:
  375. byteStream.set(MqttProtoIdentifierv3, pos);
  376. pos += MqttProtoIdentifierv3.length;
  377. break;
  378. case 4:
  379. byteStream.set(MqttProtoIdentifierv4, pos);
  380. pos += MqttProtoIdentifierv4.length;
  381. break;
  382. }
  383. var connectFlags = 0;
  384. if (this.cleanSession)
  385. connectFlags = 0x02;
  386. if (this.willMessage != undefined ) {
  387. connectFlags |= 0x04;
  388. connectFlags |= (this.willMessage.qos<<3);
  389. if (this.willMessage.retained) {
  390. connectFlags |= 0x20;
  391. }
  392. }
  393. if (this.userName != undefined)
  394. connectFlags |= 0x80;
  395. if (this.password != undefined)
  396. connectFlags |= 0x40;
  397. byteStream[pos++] = connectFlags;
  398. pos = writeUint16 (this.keepAliveInterval, byteStream, pos);
  399. }
  400.  
  401. // Output the messageIdentifier - if there is one
  402. if (this.messageIdentifier != undefined)
  403. pos = writeUint16 (this.messageIdentifier, byteStream, pos);
  404.  
  405. switch(this.type) {
  406. case MESSAGE_TYPE.CONNECT:
  407. pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos);
  408. if (this.willMessage != undefined) {
  409. pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos);
  410. pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos);
  411. byteStream.set(willMessagePayloadBytes, pos);
  412. pos += willMessagePayloadBytes.byteLength;
  413.  
  414. }
  415. if (this.userName != undefined)
  416. pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos);
  417. if (this.password != undefined)
  418. pos = writeString(this.password, UTF8Length(this.password), byteStream, pos);
  419. break;
  420.  
  421. case MESSAGE_TYPE.PUBLISH:
  422. // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters.
  423. byteStream.set(payloadBytes, pos);
  424.  
  425. break;
  426.  
  427. // case MESSAGE_TYPE.PUBREC:
  428. // case MESSAGE_TYPE.PUBREL:
  429. // case MESSAGE_TYPE.PUBCOMP:
  430. // break;
  431.  
  432. case MESSAGE_TYPE.SUBSCRIBE:
  433. // SUBSCRIBE has a list of topic strings and request QoS
  434. for (var i=0; i<this.topics.length; i++) {
  435. pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
  436. byteStream[pos++] = this.requestedQos[i];
  437. }
  438. break;
  439.  
  440. case MESSAGE_TYPE.UNSUBSCRIBE:
  441. // UNSUBSCRIBE has a list of topic strings
  442. for (var i=0; i<this.topics.length; i++)
  443. pos = writeString(this.topics[i], topicStrLength[i], byteStream, pos);
  444. break;
  445.  
  446. default:
  447. // Do nothing.
  448. }
  449.  
  450. return buffer;
  451. }
  452.  
  453. function decodeMessage(input,pos) {
  454. var startingPos = pos;
  455. var first = input[pos];
  456. var type = first >> 4;
  457. var messageInfo = first &= 0x0f;
  458. pos += 1;
  459.  
  460.  
  461. // Decode the remaining length (MBI format)
  462.  
  463. var digit;
  464. var remLength = 0;
  465. var multiplier = 1;
  466. do {
  467. if (pos == input.length) {
  468. return [null,startingPos];
  469. }
  470. digit = input[pos++];
  471. remLength += ((digit & 0x7F) * multiplier);
  472. multiplier *= 128;
  473. } while ((digit & 0x80) != 0);
  474.  
  475. var endPos = pos+remLength;
  476. if (endPos > input.length) {
  477. return [null,startingPos];
  478. }
  479.  
  480. var wireMessage = new WireMessage(type);
  481. switch(type) {
  482. case MESSAGE_TYPE.CONNACK:
  483. var connectAcknowledgeFlags = input[pos++];
  484. if (connectAcknowledgeFlags & 0x01)
  485. wireMessage.sessionPresent = true;
  486. wireMessage.returnCode = input[pos++];
  487. break;
  488.  
  489. case MESSAGE_TYPE.PUBLISH:
  490. var qos = (messageInfo >> 1) & 0x03;
  491.  
  492. var len = readUint16(input, pos);
  493. pos += 2;
  494. var topicName = parseUTF8(input, pos, len);
  495. pos += len;
  496. // If QoS 1 or 2 there will be a messageIdentifier
  497. if (qos > 0) {
  498. wireMessage.messageIdentifier = readUint16(input, pos);
  499. pos += 2;
  500. }
  501.  
  502. var message = new Paho.MQTT.Message(input.subarray(pos, endPos));
  503. if ((messageInfo & 0x01) == 0x01)
  504. message.retained = true;
  505. if ((messageInfo & 0x08) == 0x08)
  506. message.duplicate = true;
  507. message.qos = qos;
  508. message.destinationName = topicName;
  509. wireMessage.payloadMessage = message;
  510. break;
  511.  
  512. case MESSAGE_TYPE.PUBACK:
  513. case MESSAGE_TYPE.PUBREC:
  514. case MESSAGE_TYPE.PUBREL:
  515. case MESSAGE_TYPE.PUBCOMP:
  516. case MESSAGE_TYPE.UNSUBACK:
  517. wireMessage.messageIdentifier = readUint16(input, pos);
  518. break;
  519.  
  520. case MESSAGE_TYPE.SUBACK:
  521. wireMessage.messageIdentifier = readUint16(input, pos);
  522. pos += 2;
  523. wireMessage.returnCode = input.subarray(pos, endPos);
  524. break;
  525.  
  526. default:
  527. ;
  528. }
  529.  
  530. return [wireMessage,endPos];
  531. }
  532.  
  533. function writeUint16(input, buffer, offset) {
  534. buffer[offset++] = input >> 8; //MSB
  535. buffer[offset++] = input % 256; //LSB
  536. return offset;
  537. }
  538.  
  539. function writeString(input, utf8Length, buffer, offset) {
  540. offset = writeUint16(utf8Length, buffer, offset);
  541. stringToUTF8(input, buffer, offset);
  542. return offset + utf8Length;
  543. }
  544.  
  545. function readUint16(buffer, offset) {
  546. return 256*buffer[offset] + buffer[offset+1];
  547. }
  548.  
  549. /**
  550. * Encodes an MQTT Multi-Byte Integer
  551. * @private
  552. */
  553. function encodeMBI(number) {
  554. var output = new Array(1);
  555. var numBytes = 0;
  556.  
  557. do {
  558. var digit = number % 128;
  559. number = number >> 7;
  560. if (number > 0) {
  561. digit |= 0x80;
  562. }
  563. output[numBytes++] = digit;
  564. } while ( (number > 0) && (numBytes<4) );
  565.  
  566. return output;
  567. }
  568.  
  569. /**
  570. * Takes a String and calculates its length in bytes when encoded in UTF8.
  571. * @private
  572. */
  573. function UTF8Length(input) {
  574. var output = 0;
  575. for (var i = 0; i<input.length; i++)
  576. {
  577. var charCode = input.charCodeAt(i);
  578. if (charCode > 0x7FF)
  579. {
  580. // Surrogate pair means its a 4 byte character
  581. if (0xD800 <= charCode && charCode <= 0xDBFF)
  582. {
  583. i++;
  584. output++;
  585. }
  586. output +=3;
  587. }
  588. else if (charCode > 0x7F)
  589. output +=2;
  590. else
  591. output++;
  592. }
  593. return output;
  594. }
  595.  
  596. /**
  597. * Takes a String and writes it into an array as UTF8 encoded bytes.
  598. * @private
  599. */
  600. function stringToUTF8(input, output, start) {
  601. var pos = start;
  602. for (var i = 0; i<input.length; i++) {
  603. var charCode = input.charCodeAt(i);
  604.  
  605. // Check for a surrogate pair.
  606. if (0xD800 <= charCode && charCode <= 0xDBFF) {
  607. var lowCharCode = input.charCodeAt(++i);
  608. if (isNaN(lowCharCode)) {
  609. throw new Error(format(ERROR.MALFORMED_UNICODE, [charCode, lowCharCode]));
  610. }
  611. charCode = ((charCode - 0xD800)<<10) + (lowCharCode - 0xDC00) + 0x10000;
  612.  
  613. }
  614.  
  615. if (charCode <= 0x7F) {
  616. output[pos++] = charCode;
  617. } else if (charCode <= 0x7FF) {
  618. output[pos++] = charCode>>6 & 0x1F | 0xC0;
  619. output[pos++] = charCode & 0x3F | 0x80;
  620. } else if (charCode <= 0xFFFF) {
  621. output[pos++] = charCode>>12 & 0x0F | 0xE0;
  622. output[pos++] = charCode>>6 & 0x3F | 0x80;
  623. output[pos++] = charCode & 0x3F | 0x80;
  624. } else {
  625. output[pos++] = charCode>>18 & 0x07 | 0xF0;
  626. output[pos++] = charCode>>12 & 0x3F | 0x80;
  627. output[pos++] = charCode>>6 & 0x3F | 0x80;
  628. output[pos++] = charCode & 0x3F | 0x80;
  629. };
  630. }
  631. return output;
  632. }
  633.  
  634. function parseUTF8(input, offset, length) {
  635. var output = "";
  636. var utf16;
  637. var pos = offset;
  638.  
  639. while (pos < offset+length)
  640. {
  641. var byte1 = input[pos++];
  642. if (byte1 < 128)
  643. utf16 = byte1;
  644. else
  645. {
  646. var byte2 = input[pos++]-128;
  647. if (byte2 < 0)
  648. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""]));
  649. if (byte1 < 0xE0) // 2 byte character
  650. utf16 = 64*(byte1-0xC0) + byte2;
  651. else
  652. {
  653. var byte3 = input[pos++]-128;
  654. if (byte3 < 0)
  655. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)]));
  656. if (byte1 < 0xF0) // 3 byte character
  657. utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3;
  658. else
  659. {
  660. var byte4 = input[pos++]-128;
  661. if (byte4 < 0)
  662. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
  663. if (byte1 < 0xF8) // 4 byte character
  664. utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4;
  665. else // longer encodings are not supported
  666. throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)]));
  667. }
  668. }
  669. }
  670.  
  671. if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair
  672. {
  673. utf16 -= 0x10000;
  674. output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character
  675. utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character
  676. }
  677. output += String.fromCharCode(utf16);
  678. }
  679. return output;
  680. }
  681.  
  682. /**
  683. * Repeat keepalive requests, monitor responses.
  684. * @ignore
  685. */
  686. var Pinger = function(client, window, keepAliveInterval) {
  687. this._client = client;
  688. this._window = window;
  689. this._keepAliveInterval = keepAliveInterval*1000;
  690. this.isReset = false;
  691.  
  692. var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode();
  693.  
  694. var doTimeout = function (pinger) {
  695. return function () {
  696. return doPing.apply(pinger);
  697. };
  698. };
  699.  
  700. /** @ignore */
  701. var doPing = function() {
  702. if (!this.isReset) {
  703. this._client._trace("Pinger.doPing", "Timed out");
  704. this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT));
  705. } else {
  706. this.isReset = false;
  707. this._client._trace("Pinger.doPing", "send PINGREQ");
  708. this._client.socket.send(pingReq);
  709. this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval);
  710. }
  711. }
  712.  
  713. this.reset = function() {
  714. this.isReset = true;
  715. this._window.clearTimeout(this.timeout);
  716. if (this._keepAliveInterval > 0)
  717. this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval);
  718. }
  719.  
  720. this.cancel = function() {
  721. this._window.clearTimeout(this.timeout);
  722. }
  723. };
  724.  
  725. /**
  726. * Monitor request completion.
  727. * @ignore
  728. */
  729. var Timeout = function(client, window, timeoutSeconds, action, args) {
  730. this._window = window;
  731. if (!timeoutSeconds)
  732. timeoutSeconds = 30;
  733.  
  734. var doTimeout = function (action, client, args) {
  735. return function () {
  736. return action.apply(client, args);
  737. };
  738. };
  739. this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000);
  740.  
  741. this.cancel = function() {
  742. this._window.clearTimeout(this.timeout);
  743. }
  744. };
  745.  
  746. /*
  747. * Internal implementation of the Websockets MQTT V3.1 client.
  748. *
  749. * @name Paho.MQTT.ClientImpl @constructor
  750. * @param {String} host the DNS nameof the webSocket host.
  751. * @param {Number} port the port number for that host.
  752. * @param {String} clientId the MQ client identifier.
  753. */
  754. var ClientImpl = function (uri, host, port, path, clientId) {
  755. // Check dependencies are satisfied in this browser.
  756. if (!("WebSocket" in global && global["WebSocket"] !== null)) {
  757. throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"]));
  758. }
  759. if (!("localStorage" in global && global["localStorage"] !== null)) {
  760. throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"]));
  761. }
  762. if (!("ArrayBuffer" in global && global["ArrayBuffer"] !== null)) {
  763. throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"]));
  764. }
  765. this._trace("Paho.MQTT.Client", uri, host, port, path, clientId);
  766.  
  767. this.host = host;
  768. this.port = port;
  769. this.path = path;
  770. this.uri = uri;
  771. this.clientId = clientId;
  772.  
  773. // Local storagekeys are qualified with the following string.
  774. // The conditional inclusion of path in the key is for backward
  775. // compatibility to when the path was not configurable and assumed to
  776. // be /mqtt
  777. this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":";
  778.  
  779. // Create private instance-only message queue
  780. // Internal queue of messages to be sent, in sending order.
  781. this._msg_queue = [];
  782.  
  783. // Messages we have sent and are expecting a response for, indexed by their respective message ids.
  784. this._sentMessages = {};
  785.  
  786. // Messages we have received and acknowleged and are expecting a confirm message for
  787. // indexed by their respective message ids.
  788. this._receivedMessages = {};
  789.  
  790. // Internal list of callbacks to be executed when messages
  791. // have been successfully sent over web socket, e.g. disconnect
  792. // when it doesn't have to wait for ACK, just message is dispatched.
  793. this._notify_msg_sent = {};
  794.  
  795. // Unique identifier for SEND messages, incrementing
  796. // counter as messages are sent.
  797. this._message_identifier = 1;
  798.  
  799. // Used to determine the transmission sequence of stored sent messages.
  800. this._sequence = 0;
  801.  
  802.  
  803. // Load the local state, if any, from the saved version, only restore state relevant to this client.
  804. for (var key in localStorage)
  805. if ( key.indexOf("Sent:"+this._localKey) == 0
  806. || key.indexOf("Received:"+this._localKey) == 0)
  807. this.restore(key);
  808. };
  809.  
  810. // Messaging Client public instance members.
  811. ClientImpl.prototype.host;
  812. ClientImpl.prototype.port;
  813. ClientImpl.prototype.path;
  814. ClientImpl.prototype.uri;
  815. ClientImpl.prototype.clientId;
  816.  
  817. // Messaging Client private instance members.
  818. ClientImpl.prototype.socket;
  819. /* true once we have received an acknowledgement to a CONNECT packet. */
  820. ClientImpl.prototype.connected = false;
  821. /* The largest message identifier allowed, may not be larger than 2**16 but
  822. * if set smaller reduces the maximum number of outbound messages allowed.
  823. */
  824. ClientImpl.prototype.maxMessageIdentifier = 65536;
  825. ClientImpl.prototype.connectOptions;
  826. ClientImpl.prototype.hostIndex;
  827. ClientImpl.prototype.onConnectionLost;
  828. ClientImpl.prototype.onMessageDelivered;
  829. ClientImpl.prototype.onMessageArrived;
  830. ClientImpl.prototype.traceFunction;
  831. ClientImpl.prototype._msg_queue = null;
  832. ClientImpl.prototype._connectTimeout;
  833. /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */
  834. ClientImpl.prototype.sendPinger = null;
  835. /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */
  836. ClientImpl.prototype.receivePinger = null;
  837.  
  838. ClientImpl.prototype.receiveBuffer = null;
  839.  
  840. ClientImpl.prototype._traceBuffer = null;
  841. ClientImpl.prototype._MAX_TRACE_ENTRIES = 100;
  842.  
  843. ClientImpl.prototype.connect = function (connectOptions) {
  844. var connectOptionsMasked = this._traceMask(connectOptions, "password");
  845. this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected);
  846.  
  847. if (this.connected)
  848. throw new Error(format(ERROR.INVALID_STATE, ["already connected"]));
  849. if (this.socket)
  850. throw new Error(format(ERROR.INVALID_STATE, ["already connected"]));
  851.  
  852. this.connectOptions = connectOptions;
  853.  
  854. if (connectOptions.uris) {
  855. this.hostIndex = 0;
  856. this._doConnect(connectOptions.uris[0]);
  857. } else {
  858. this._doConnect(this.uri);
  859. }
  860.  
  861. };
  862.  
  863. ClientImpl.prototype.subscribe = function (filter, subscribeOptions) {
  864. this._trace("Client.subscribe", filter, subscribeOptions);
  865.  
  866. if (!this.connected)
  867. throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
  868.  
  869. var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE);
  870. wireMessage.topics=[filter];
  871. if (subscribeOptions.qos != undefined)
  872. wireMessage.requestedQos = [subscribeOptions.qos];
  873. else
  874. wireMessage.requestedQos = [0];
  875.  
  876. if (subscribeOptions.onSuccess) {
  877. wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});};
  878. }
  879.  
  880. if (subscribeOptions.onFailure) {
  881. wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});};
  882. }
  883.  
  884. if (subscribeOptions.timeout) {
  885. wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure
  886. , [{invocationContext:subscribeOptions.invocationContext,
  887. errorCode:ERROR.SUBSCRIBE_TIMEOUT.code,
  888. errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]);
  889. }
  890.  
  891. // All subscriptions return a SUBACK.
  892. this._requires_ack(wireMessage);
  893. this._schedule_message(wireMessage);
  894. };
  895.  
  896. /** @ignore */
  897. ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) {
  898. this._trace("Client.unsubscribe", filter, unsubscribeOptions);
  899.  
  900. if (!this.connected)
  901. throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
  902.  
  903. var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE);
  904. wireMessage.topics = [filter];
  905.  
  906. if (unsubscribeOptions.onSuccess) {
  907. wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});};
  908. }
  909. if (unsubscribeOptions.timeout) {
  910. wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure
  911. , [{invocationContext:unsubscribeOptions.invocationContext,
  912. errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code,
  913. errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]);
  914. }
  915.  
  916. // All unsubscribes return a SUBACK.
  917. this._requires_ack(wireMessage);
  918. this._schedule_message(wireMessage);
  919. };
  920.  
  921. ClientImpl.prototype.send = function (message) {
  922. this._trace("Client.send", message);
  923.  
  924. if (!this.connected)
  925. throw new Error(format(ERROR.INVALID_STATE, ["not connected"]));
  926.  
  927. wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH);
  928. wireMessage.payloadMessage = message;
  929.  
  930. if (message.qos > 0)
  931. this._requires_ack(wireMessage);
  932. else if (this.onMessageDelivered)
  933. this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage);
  934. this._schedule_message(wireMessage);
  935. };
  936.  
  937. ClientImpl.prototype.disconnect = function () {
  938. this._trace("Client.disconnect");
  939.  
  940. if (!this.socket)
  941. throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"]));
  942.  
  943. wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT);
  944.  
  945. // Run the disconnected call back as soon as the message has been sent,
  946. // in case of a failure later on in the disconnect processing.
  947. // as a consequence, the _disconected call back may be run several times.
  948. this._notify_msg_sent[wireMessage] = scope(this._disconnected, this);
  949.  
  950. this._schedule_message(wireMessage);
  951. };
  952.  
  953. ClientImpl.prototype.getTraceLog = function () {
  954. if ( this._traceBuffer !== null ) {
  955. this._trace("Client.getTraceLog", new Date());
  956. this._trace("Client.getTraceLog in flight messages", this._sentMessages.length);
  957. for (var key in this._sentMessages)
  958. this._trace("_sentMessages ",key, this._sentMessages[key]);
  959. for (var key in this._receivedMessages)
  960. this._trace("_receivedMessages ",key, this._receivedMessages[key]);
  961.  
  962. return this._traceBuffer;
  963. }
  964. };
  965.  
  966. ClientImpl.prototype.startTrace = function () {
  967. if ( this._traceBuffer === null ) {
  968. this._traceBuffer = [];
  969. }
  970. this._trace("Client.startTrace", new Date(), version);
  971. };
  972.  
  973. ClientImpl.prototype.stopTrace = function () {
  974. delete this._traceBuffer;
  975. };
  976.  
  977. ClientImpl.prototype._doConnect = function (wsurl) {
  978. // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters.
  979. if (this.connectOptions.useSSL) {
  980. var uriParts = wsurl.split(":");
  981. uriParts[0] = "wss";
  982. wsurl = uriParts.join(":");
  983. }
  984. this.connected = false;
  985. if (this.connectOptions.mqttVersion < 4) {
  986. this.socket = new WebSocket(wsurl, ["mqttv3.1"]);
  987. } else {
  988. this.socket = new WebSocket(wsurl, ["mqtt"]);
  989. }
  990. this.socket.binaryType = 'arraybuffer';
  991.  
  992. this.socket.onopen = scope(this._on_socket_open, this);
  993. this.socket.onmessage = scope(this._on_socket_message, this);
  994. this.socket.onerror = scope(this._on_socket_error, this);
  995. this.socket.onclose = scope(this._on_socket_close, this);
  996.  
  997. this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
  998. this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval);
  999.  
  1000. this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]);
  1001. };
  1002.  
  1003.  
  1004. // Schedule a new message to be sent over the WebSockets
  1005. // connection. CONNECT messages cause WebSocket connection
  1006. // to be started. All other messages are queued internally
  1007. // until this has happened. When WS connection starts, process
  1008. // all outstanding messages.
  1009. ClientImpl.prototype._schedule_message = function (message) {
  1010. this._msg_queue.push(message);
  1011. // Process outstanding messages in the queue if we have an open socket, and have received CONNACK.
  1012. if (this.connected) {
  1013. this._process_queue();
  1014. }
  1015. };
  1016.  
  1017. ClientImpl.prototype.store = function(prefix, wireMessage) {
  1018. var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1};
  1019.  
  1020. switch(wireMessage.type) {
  1021. case MESSAGE_TYPE.PUBLISH:
  1022. if(wireMessage.pubRecReceived)
  1023. storedMessage.pubRecReceived = true;
  1024.  
  1025. // Convert the payload to a hex string.
  1026. storedMessage.payloadMessage = {};
  1027. var hex = "";
  1028. var messageBytes = wireMessage.payloadMessage.payloadBytes;
  1029. for (var i=0; i<messageBytes.length; i++) {
  1030. if (messageBytes[i] <= 0xF)
  1031. hex = hex+"0"+messageBytes[i].toString(16);
  1032. else
  1033. hex = hex+messageBytes[i].toString(16);
  1034. }
  1035. storedMessage.payloadMessage.payloadHex = hex;
  1036.  
  1037. storedMessage.payloadMessage.qos = wireMessage.payloadMessage.qos;
  1038. storedMessage.payloadMessage.destinationName = wireMessage.payloadMessage.destinationName;
  1039. if (wireMessage.payloadMessage.duplicate)
  1040. storedMessage.payloadMessage.duplicate = true;
  1041. if (wireMessage.payloadMessage.retained)
  1042. storedMessage.payloadMessage.retained = true;
  1043.  
  1044. // Add a sequence number to sent messages.
  1045. if ( prefix.indexOf("Sent:") == 0 ) {
  1046. if ( wireMessage.sequence === undefined )
  1047. wireMessage.sequence = ++this._sequence;
  1048. storedMessage.sequence = wireMessage.sequence;
  1049. }
  1050. break;
  1051.  
  1052. default:
  1053. throw Error(format(ERROR.INVALID_STORED_DATA, [key, storedMessage]));
  1054. }
  1055. localStorage.setItem(prefix+this._localKey+wireMessage.messageIdentifier, JSON.stringify(storedMessage));
  1056. };
  1057.  
  1058. ClientImpl.prototype.restore = function(key) {
  1059. var value = localStorage.getItem(key);
  1060. var storedMessage = JSON.parse(value);
  1061.  
  1062. var wireMessage = new WireMessage(storedMessage.type, storedMessage);
  1063.  
  1064. switch(storedMessage.type) {
  1065. case MESSAGE_TYPE.PUBLISH:
  1066. // Replace the payload message with a Message object.
  1067. var hex = storedMessage.payloadMessage.payloadHex;
  1068. var buffer = new ArrayBuffer((hex.length)/2);
  1069. var byteStream = new Uint8Array(buffer);
  1070. var i = 0;
  1071. while (hex.length >= 2) {
  1072. var x = parseInt(hex.substring(0, 2), 16);
  1073. hex = hex.substring(2, hex.length);
  1074. byteStream[i++] = x;
  1075. }
  1076. var payloadMessage = new Paho.MQTT.Message(byteStream);
  1077.  
  1078. payloadMessage.qos = storedMessage.payloadMessage.qos;
  1079. payloadMessage.destinationName = storedMessage.payloadMessage.destinationName;
  1080. if (storedMessage.payloadMessage.duplicate)
  1081. payloadMessage.duplicate = true;
  1082. if (storedMessage.payloadMessage.retained)
  1083. payloadMessage.retained = true;
  1084. wireMessage.payloadMessage = payloadMessage;
  1085.  
  1086. break;
  1087.  
  1088. default:
  1089. throw Error(format(ERROR.INVALID_STORED_DATA, [key, value]));
  1090. }
  1091.  
  1092. if (key.indexOf("Sent:"+this._localKey) == 0) {
  1093. wireMessage.payloadMessage.duplicate = true;
  1094. this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
  1095. } else if (key.indexOf("Received:"+this._localKey) == 0) {
  1096. this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
  1097. }
  1098. };
  1099.  
  1100. ClientImpl.prototype._process_queue = function () {
  1101. var message = null;
  1102. // Process messages in order they were added
  1103. var fifo = this._msg_queue.reverse();
  1104.  
  1105. // Send all queued messages down socket connection
  1106. while ((message = fifo.pop())) {
  1107. this._socket_send(message);
  1108. // Notify listeners that message was successfully sent
  1109. if (this._notify_msg_sent[message]) {
  1110. this._notify_msg_sent[message]();
  1111. delete this._notify_msg_sent[message];
  1112. }
  1113. }
  1114. };
  1115.  
  1116. /**
  1117. * Expect an ACK response for this message. Add message to the set of in progress
  1118. * messages and set an unused identifier in this message.
  1119. * @ignore
  1120. */
  1121. ClientImpl.prototype._requires_ack = function (wireMessage) {
  1122. var messageCount = Object.keys(this._sentMessages).length;
  1123. if (messageCount > this.maxMessageIdentifier)
  1124. throw Error ("Too many messages:"+messageCount);
  1125.  
  1126. while(this._sentMessages[this._message_identifier] !== undefined) {
  1127. this._message_identifier++;
  1128. }
  1129. wireMessage.messageIdentifier = this._message_identifier;
  1130. this._sentMessages[wireMessage.messageIdentifier] = wireMessage;
  1131. if (wireMessage.type === MESSAGE_TYPE.PUBLISH) {
  1132. this.store("Sent:", wireMessage);
  1133. }
  1134. if (this._message_identifier === this.maxMessageIdentifier) {
  1135. this._message_identifier = 1;
  1136. }
  1137. };
  1138.  
  1139. /**
  1140. * Called when the underlying websocket has been opened.
  1141. * @ignore
  1142. */
  1143. ClientImpl.prototype._on_socket_open = function () {
  1144. // Create the CONNECT message object.
  1145. var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions);
  1146. wireMessage.clientId = this.clientId;
  1147. this._socket_send(wireMessage);
  1148. };
  1149.  
  1150. /**
  1151. * Called when the underlying websocket has received a complete packet.
  1152. * @ignore
  1153. */
  1154. ClientImpl.prototype._on_socket_message = function (event) {
  1155. this._trace("Client._on_socket_message", event.data);
  1156. // Reset the receive ping timer, we now have evidence the server is alive.
  1157. this.receivePinger.reset();
  1158. var messages = this._deframeMessages(event.data);
  1159. for (var i = 0; i < messages.length; i+=1) {
  1160. this._handleMessage(messages[i]);
  1161. }
  1162. }
  1163.  
  1164. ClientImpl.prototype._deframeMessages = function(data) {
  1165. var byteArray = new Uint8Array(data);
  1166. if (this.receiveBuffer) {
  1167. var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length);
  1168. newData.set(this.receiveBuffer);
  1169. newData.set(byteArray,this.receiveBuffer.length);
  1170. byteArray = newData;
  1171. delete this.receiveBuffer;
  1172. }
  1173. try {
  1174. var offset = 0;
  1175. var messages = [];
  1176. while(offset < byteArray.length) {
  1177. var result = decodeMessage(byteArray,offset);
  1178. var wireMessage = result[0];
  1179. offset = result[1];
  1180. if (wireMessage !== null) {
  1181. messages.push(wireMessage);
  1182. } else {
  1183. break;
  1184. }
  1185. }
  1186. if (offset < byteArray.length) {
  1187. this.receiveBuffer = byteArray.subarray(offset);
  1188. }
  1189. } catch (error) {
  1190. this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
  1191. return;
  1192. }
  1193. return messages;
  1194. }
  1195.  
  1196. ClientImpl.prototype._handleMessage = function(wireMessage) {
  1197.  
  1198. this._trace("Client._handleMessage", wireMessage);
  1199.  
  1200. try {
  1201. switch(wireMessage.type) {
  1202. case MESSAGE_TYPE.CONNACK:
  1203. this._connectTimeout.cancel();
  1204.  
  1205. // If we have started using clean session then clear up the local state.
  1206. if (this.connectOptions.cleanSession) {
  1207. for (var key in this._sentMessages) {
  1208. var sentMessage = this._sentMessages[key];
  1209. localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier);
  1210. }
  1211. this._sentMessages = {};
  1212.  
  1213. for (var key in this._receivedMessages) {
  1214. var receivedMessage = this._receivedMessages[key];
  1215. localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier);
  1216. }
  1217. this._receivedMessages = {};
  1218. }
  1219. // Client connected and ready for business.
  1220. if (wireMessage.returnCode === 0) {
  1221. this.connected = true;
  1222. // Jump to the end of the list of uris and stop looking for a good host.
  1223. if (this.connectOptions.uris)
  1224. this.hostIndex = this.connectOptions.uris.length;
  1225. } else {
  1226. this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]]));
  1227. break;
  1228. }
  1229.  
  1230. // Resend messages.
  1231. var sequencedMessages = new Array();
  1232. for (var msgId in this._sentMessages) {
  1233. if (this._sentMessages.hasOwnProperty(msgId))
  1234. sequencedMessages.push(this._sentMessages[msgId]);
  1235. }
  1236.  
  1237. // Sort sentMessages into the original sent order.
  1238. var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} );
  1239. for (var i=0, len=sequencedMessages.length; i<len; i++) {
  1240. var sentMessage = sequencedMessages[i];
  1241. if (sentMessage.type == MESSAGE_TYPE.PUBLISH && sentMessage.pubRecReceived) {
  1242. var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:sentMessage.messageIdentifier});
  1243. this._schedule_message(pubRelMessage);
  1244. } else {
  1245. this._schedule_message(sentMessage);
  1246. };
  1247. }
  1248.  
  1249. // Execute the connectOptions.onSuccess callback if there is one.
  1250. if (this.connectOptions.onSuccess) {
  1251. this.connectOptions.onSuccess({invocationContext:this.connectOptions.invocationContext});
  1252. }
  1253.  
  1254. // Process all queued messages now that the connection is established.
  1255. this._process_queue();
  1256. break;
  1257.  
  1258. case MESSAGE_TYPE.PUBLISH:
  1259. this._receivePublish(wireMessage);
  1260. break;
  1261.  
  1262. case MESSAGE_TYPE.PUBACK:
  1263. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1264. // If this is a re flow of a PUBACK after we have restarted receivedMessage will not exist.
  1265. if (sentMessage) {
  1266. delete this._sentMessages[wireMessage.messageIdentifier];
  1267. localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
  1268. if (this.onMessageDelivered)
  1269. this.onMessageDelivered(sentMessage.payloadMessage);
  1270. }
  1271. break;
  1272.  
  1273. case MESSAGE_TYPE.PUBREC:
  1274. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1275. // If this is a re flow of a PUBREC after we have restarted receivedMessage will not exist.
  1276. if (sentMessage) {
  1277. sentMessage.pubRecReceived = true;
  1278. var pubRelMessage = new WireMessage(MESSAGE_TYPE.PUBREL, {messageIdentifier:wireMessage.messageIdentifier});
  1279. this.store("Sent:", sentMessage);
  1280. this._schedule_message(pubRelMessage);
  1281. }
  1282. break;
  1283.  
  1284. case MESSAGE_TYPE.PUBREL:
  1285. var receivedMessage = this._receivedMessages[wireMessage.messageIdentifier];
  1286. localStorage.removeItem("Received:"+this._localKey+wireMessage.messageIdentifier);
  1287. // If this is a re flow of a PUBREL after we have restarted receivedMessage will not exist.
  1288. if (receivedMessage) {
  1289. this._receiveMessage(receivedMessage);
  1290. delete this._receivedMessages[wireMessage.messageIdentifier];
  1291. }
  1292. // Always flow PubComp, we may have previously flowed PubComp but the server lost it and restarted.
  1293. var pubCompMessage = new WireMessage(MESSAGE_TYPE.PUBCOMP, {messageIdentifier:wireMessage.messageIdentifier});
  1294. this._schedule_message(pubCompMessage);
  1295. break;
  1296.  
  1297. case MESSAGE_TYPE.PUBCOMP:
  1298. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1299. delete this._sentMessages[wireMessage.messageIdentifier];
  1300. localStorage.removeItem("Sent:"+this._localKey+wireMessage.messageIdentifier);
  1301. if (this.onMessageDelivered)
  1302. this.onMessageDelivered(sentMessage.payloadMessage);
  1303. break;
  1304.  
  1305. case MESSAGE_TYPE.SUBACK:
  1306. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1307. if (sentMessage) {
  1308. if(sentMessage.timeOut)
  1309. sentMessage.timeOut.cancel();
  1310. // This will need to be fixed when we add multiple topic support
  1311. if (wireMessage.returnCode[0] === 0x80) {
  1312. if (sentMessage.onFailure) {
  1313. sentMessage.onFailure(wireMessage.returnCode);
  1314. }
  1315. } else if (sentMessage.onSuccess) {
  1316. sentMessage.onSuccess(wireMessage.returnCode);
  1317. }
  1318. delete this._sentMessages[wireMessage.messageIdentifier];
  1319. }
  1320. break;
  1321.  
  1322. case MESSAGE_TYPE.UNSUBACK:
  1323. var sentMessage = this._sentMessages[wireMessage.messageIdentifier];
  1324. if (sentMessage) {
  1325. if (sentMessage.timeOut)
  1326. sentMessage.timeOut.cancel();
  1327. if (sentMessage.callback) {
  1328. sentMessage.callback();
  1329. }
  1330. delete this._sentMessages[wireMessage.messageIdentifier];
  1331. }
  1332.  
  1333. break;
  1334.  
  1335. case MESSAGE_TYPE.PINGRESP:
  1336. /* The sendPinger or receivePinger may have sent a ping, the receivePinger has already been reset. */
  1337. this.sendPinger.reset();
  1338. break;
  1339.  
  1340. case MESSAGE_TYPE.DISCONNECT:
  1341. // Clients do not expect to receive disconnect packets.
  1342. this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
  1343. break;
  1344.  
  1345. default:
  1346. this._disconnected(ERROR.INVALID_MQTT_MESSAGE_TYPE.code , format(ERROR.INVALID_MQTT_MESSAGE_TYPE, [wireMessage.type]));
  1347. };
  1348. } catch (error) {
  1349. this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()]));
  1350. return;
  1351. }
  1352. };
  1353.  
  1354. /** @ignore */
  1355. ClientImpl.prototype._on_socket_error = function (error) {
  1356. this._disconnected(ERROR.SOCKET_ERROR.code , format(ERROR.SOCKET_ERROR, [error.data]));
  1357. };
  1358.  
  1359. /** @ignore */
  1360. ClientImpl.prototype._on_socket_close = function () {
  1361. this._disconnected(ERROR.SOCKET_CLOSE.code , format(ERROR.SOCKET_CLOSE));
  1362. };
  1363.  
  1364. /** @ignore */
  1365. ClientImpl.prototype._socket_send = function (wireMessage) {
  1366.  
  1367. if (wireMessage.type == 1) {
  1368. var wireMessageMasked = this._traceMask(wireMessage, "password");
  1369. this._trace("Client._socket_send", wireMessageMasked);
  1370. }
  1371. else this._trace("Client._socket_send", wireMessage);
  1372.  
  1373. this.socket.send(wireMessage.encode());
  1374. /* We have proved to the server we are alive. */
  1375. this.sendPinger.reset();
  1376. };
  1377.  
  1378. /** @ignore */
  1379. ClientImpl.prototype._receivePublish = function (wireMessage) {
  1380. switch(wireMessage.payloadMessage.qos) {
  1381. case "undefined":
  1382. case 0:
  1383. this._receiveMessage(wireMessage);
  1384. break;
  1385.  
  1386. case 1:
  1387. var pubAckMessage = new WireMessage(MESSAGE_TYPE.PUBACK, {messageIdentifier:wireMessage.messageIdentifier});
  1388. this._schedule_message(pubAckMessage);
  1389. this._receiveMessage(wireMessage);
  1390. break;
  1391.  
  1392. case 2:
  1393. this._receivedMessages[wireMessage.messageIdentifier] = wireMessage;
  1394. this.store("Received:", wireMessage);
  1395. var pubRecMessage = new WireMessage(MESSAGE_TYPE.PUBREC, {messageIdentifier:wireMessage.messageIdentifier});
  1396. this._schedule_message(pubRecMessage);
  1397.  
  1398. break;
  1399.  
  1400. default:
  1401. throw Error("Invaild qos="+wireMmessage.payloadMessage.qos);
  1402. };
  1403. };
  1404.  
  1405. /** @ignore */
  1406. ClientImpl.prototype._receiveMessage = function (wireMessage) {
  1407. if (this.onMessageArrived) {
  1408. this.onMessageArrived(wireMessage.payloadMessage);
  1409. }
  1410. };
  1411.  
  1412. /**
  1413. * Client has disconnected either at its own request or because the server
  1414. * or network disconnected it. Remove all non-durable state.
  1415. * @param {errorCode} [number] the error number.
  1416. * @param {errorText} [string] the error text.
  1417. * @ignore
  1418. */
  1419. ClientImpl.prototype._disconnected = function (errorCode, errorText) {
  1420. this._trace("Client._disconnected", errorCode, errorText);
  1421.  
  1422. this.sendPinger.cancel();
  1423. this.receivePinger.cancel();
  1424. if (this._connectTimeout)
  1425. this._connectTimeout.cancel();
  1426. // Clear message buffers.
  1427. this._msg_queue = [];
  1428. this._notify_msg_sent = {};
  1429.  
  1430. if (this.socket) {
  1431. // Cancel all socket callbacks so that they cannot be driven again by this socket.
  1432. this.socket.onopen = null;
  1433. this.socket.onmessage = null;
  1434. this.socket.onerror = null;
  1435. this.socket.onclose = null;
  1436. if (this.socket.readyState === 1)
  1437. this.socket.close();
  1438. delete this.socket;
  1439. }
  1440.  
  1441. if (this.connectOptions.uris && this.hostIndex < this.connectOptions.uris.length-1) {
  1442. // Try the next host.
  1443. this.hostIndex++;
  1444. this._doConnect(this.connectOptions.uris[this.hostIndex]);
  1445.  
  1446. } else {
  1447.  
  1448. if (errorCode === undefined) {
  1449. errorCode = ERROR.OK.code;
  1450. errorText = format(ERROR.OK);
  1451. }
  1452.  
  1453. // Run any application callbacks last as they may attempt to reconnect and hence create a new socket.
  1454. if (this.connected) {
  1455. this.connected = false;
  1456. // Execute the connectionLostCallback if there is one, and we were connected.
  1457. if (this.onConnectionLost)
  1458. this.onConnectionLost({errorCode:errorCode, errorMessage:errorText});
  1459. } else {
  1460. // Otherwise we never had a connection, so indicate that the connect has failed.
  1461. if (this.connectOptions.mqttVersion === 4 && this.connectOptions.mqttVersionExplicit === false) {
  1462. this._trace("Failed to connect V4, dropping back to V3")
  1463. this.connectOptions.mqttVersion = 3;
  1464. if (this.connectOptions.uris) {
  1465. this.hostIndex = 0;
  1466. this._doConnect(this.connectOptions.uris[0]);
  1467. } else {
  1468. this._doConnect(this.uri);
  1469. }
  1470. } else if(this.connectOptions.onFailure) {
  1471. this.connectOptions.onFailure({invocationContext:this.connectOptions.invocationContext, errorCode:errorCode, errorMessage:errorText});
  1472. }
  1473. }
  1474. }
  1475. };
  1476.  
  1477. /** @ignore */
  1478. ClientImpl.prototype._trace = function () {
  1479. // Pass trace message back to client's callback function
  1480. if (this.traceFunction) {
  1481. for (var i in arguments)
  1482. {
  1483. if (typeof arguments[i] !== "undefined")
  1484. arguments[i] = JSON.stringify(arguments[i]);
  1485. }
  1486. var record = Array.prototype.slice.call(arguments).join("");
  1487. this.traceFunction ({severity: "Debug", message: record });
  1488. }
  1489.  
  1490. //buffer style trace
  1491. if ( this._traceBuffer !== null ) {
  1492. for (var i = 0, max = arguments.length; i < max; i++) {
  1493. if ( this._traceBuffer.length == this._MAX_TRACE_ENTRIES ) {
  1494. this._traceBuffer.shift();
  1495. }
  1496. if (i === 0) this._traceBuffer.push(arguments[i]);
  1497. else if (typeof arguments[i] === "undefined" ) this._traceBuffer.push(arguments[i]);
  1498. else this._traceBuffer.push(" "+JSON.stringify(arguments[i]));
  1499. };
  1500. };
  1501. };
  1502.  
  1503. /** @ignore */
  1504. ClientImpl.prototype._traceMask = function (traceObject, masked) {
  1505. var traceObjectMasked = {};
  1506. for (var attr in traceObject) {
  1507. if (traceObject.hasOwnProperty(attr)) {
  1508. if (attr == masked)
  1509. traceObjectMasked[attr] = "******";
  1510. else
  1511. traceObjectMasked[attr] = traceObject[attr];
  1512. }
  1513. }
  1514. return traceObjectMasked;
  1515. };
  1516.  
  1517. // ------------------------------------------------------------------------
  1518. // Public Programming interface.
  1519. // ------------------------------------------------------------------------
  1520.  
  1521. /**
  1522. * The JavaScript application communicates to the server using a {@link Paho.MQTT.Client} object.
  1523. * <p>
  1524. * Most applications will create just one Client object and then call its connect() method,
  1525. * however applications can create more than one Client object if they wish.
  1526. * In this case the combination of host, port and clientId attributes must be different for each Client object.
  1527. * <p>
  1528. * The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods
  1529. * (even though the underlying protocol exchange might be synchronous in nature).
  1530. * This means they signal their completion by calling back to the application,
  1531. * via Success or Failure callback functions provided by the application on the method in question.
  1532. * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime
  1533. * of the script that made the invocation.
  1534. * <p>
  1535. * In contrast there are some callback functions, most notably <i>onMessageArrived</i>,
  1536. * that are defined on the {@link Paho.MQTT.Client} object.
  1537. * These may get called multiple times, and aren't directly related to specific method invocations made by the client.
  1538. *
  1539. * @name Paho.MQTT.Client
  1540. *
  1541. * @constructor
  1542. *
  1543. * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address.
  1544. * @param {number} port - the port number to connect to - only required if host is not a URI
  1545. * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'.
  1546. * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length.
  1547. *
  1548. * @property {string} host - <i>read only</i> the server's DNS hostname or dotted decimal IP address.
  1549. * @property {number} port - <i>read only</i> the server's port.
  1550. * @property {string} path - <i>read only</i> the server's path.
  1551. * @property {string} clientId - <i>read only</i> used when connecting to the server.
  1552. * @property {function} onConnectionLost - called when a connection has been lost.
  1553. * after a connect() method has succeeded.
  1554. * Establish the call back used when a connection has been lost. The connection may be
  1555. * lost because the client initiates a disconnect or because the server or network
  1556. * cause the client to be disconnected. The disconnect call back may be called without
  1557. * the connectionComplete call back being invoked if, for example the client fails to
  1558. * connect.
  1559. * A single response object parameter is passed to the onConnectionLost callback containing the following fields:
  1560. * <ol>
  1561. * <li>errorCode
  1562. * <li>errorMessage
  1563. * </ol>
  1564. * @property {function} onMessageDelivered called when a message has been delivered.
  1565. * All processing that this Client will ever do has been completed. So, for example,
  1566. * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server
  1567. * and the message has been removed from persistent storage before this callback is invoked.
  1568. * Parameters passed to the onMessageDelivered callback are:
  1569. * <ol>
  1570. * <li>{@link Paho.MQTT.Message} that was delivered.
  1571. * </ol>
  1572. * @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client.
  1573. * Parameters passed to the onMessageArrived callback are:
  1574. * <ol>
  1575. * <li>{@link Paho.MQTT.Message} that has arrived.
  1576. * </ol>
  1577. */
  1578. var Client = function (host, port, path, clientId) {
  1579.  
  1580. var uri;
  1581.  
  1582. if (typeof host !== "string")
  1583. throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"]));
  1584.  
  1585. if (arguments.length == 2) {
  1586. // host: must be full ws:// uri
  1587. // port: clientId
  1588. clientId = port;
  1589. uri = host;
  1590. var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/);
  1591. if (match) {
  1592. host = match[4]||match[2];
  1593. port = parseInt(match[7]);
  1594. path = match[8];
  1595. } else {
  1596. throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"]));
  1597. }
  1598. } else {
  1599. if (arguments.length == 3) {
  1600. clientId = path;
  1601. path = "/mqtt";
  1602. }
  1603. if (typeof port !== "number" || port < 0)
  1604. throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"]));
  1605. if (typeof path !== "string")
  1606. throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"]));
  1607.  
  1608. var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]");
  1609. uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path;
  1610. }
  1611.  
  1612. var clientIdLength = 0;
  1613. for (var i = 0; i<clientId.length; i++) {
  1614. var charCode = clientId.charCodeAt(i);
  1615. if (0xD800 <= charCode && charCode <= 0xDBFF) {
  1616. i++; // Surrogate pair.
  1617. }
  1618. clientIdLength++;
  1619. }
  1620. if (typeof clientId !== "string" || clientIdLength > 65535)
  1621. throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"]));
  1622.  
  1623. var client = new ClientImpl(uri, host, port, path, clientId);
  1624. this._getHost = function() { return host; };
  1625. this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1626.  
  1627. this._getPort = function() { return port; };
  1628. this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1629.  
  1630. this._getPath = function() { return path; };
  1631. this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1632.  
  1633. this._getURI = function() { return uri; };
  1634. this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1635.  
  1636. this._getClientId = function() { return client.clientId; };
  1637. this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); };
  1638.  
  1639. this._getOnConnectionLost = function() { return client.onConnectionLost; };
  1640. this._setOnConnectionLost = function(newOnConnectionLost) {
  1641. if (typeof newOnConnectionLost === "function")
  1642. client.onConnectionLost = newOnConnectionLost;
  1643. else
  1644. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"]));
  1645. };
  1646.  
  1647. this._getOnMessageDelivered = function() { return client.onMessageDelivered; };
  1648. this._setOnMessageDelivered = function(newOnMessageDelivered) {
  1649. if (typeof newOnMessageDelivered === "function")
  1650. client.onMessageDelivered = newOnMessageDelivered;
  1651. else
  1652. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"]));
  1653. };
  1654.  
  1655. this._getOnMessageArrived = function() { return client.onMessageArrived; };
  1656. this._setOnMessageArrived = function(newOnMessageArrived) {
  1657. if (typeof newOnMessageArrived === "function")
  1658. client.onMessageArrived = newOnMessageArrived;
  1659. else
  1660. throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"]));
  1661. };
  1662.  
  1663. this._getTrace = function() { return client.traceFunction; };
  1664. this._setTrace = function(trace) {
  1665. if(typeof trace === "function"){
  1666. client.traceFunction = trace;
  1667. }else{
  1668. throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"]));
  1669. }
  1670. };
  1671.  
  1672. /**
  1673. * Connect this Messaging client to its server.
  1674. *
  1675. * @name Paho.MQTT.Client#connect
  1676. * @function
  1677. * @param {Object} connectOptions - attributes used with the connection.
  1678. * @param {number} connectOptions.timeout - If the connect has not succeeded within this
  1679. * number of seconds, it is deemed to have failed.
  1680. * The default is 30 seconds.
  1681. * @param {string} connectOptions.userName - Authentication username for this connection.
  1682. * @param {string} connectOptions.password - Authentication password for this connection.
  1683. * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client
  1684. * disconnects abnormally.
  1685. * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if
  1686. * there is no activity for this number of seconds.
  1687. * The default value of 60 seconds is assumed if not set.
  1688. * @param {boolean} connectOptions.cleanSession - if true(default) the client and server
  1689. * persistent state is deleted on successful connect.
  1690. * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection.
  1691. * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback.
  1692. * @param {function} connectOptions.onSuccess - called when the connect acknowledgement
  1693. * has been received from the server.
  1694. * A single response object parameter is passed to the onSuccess callback containing the following fields:
  1695. * <ol>
  1696. * <li>invocationContext as passed in to the onSuccess method in the connectOptions.
  1697. * </ol>
  1698. * @config {function} [onFailure] called when the connect request has failed or timed out.
  1699. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1700. * <ol>
  1701. * <li>invocationContext as passed in to the onFailure method in the connectOptions.
  1702. * <li>errorCode a number indicating the nature of the error.
  1703. * <li>errorMessage text describing the error.
  1704. * </ol>
  1705. * @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified
  1706. * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place
  1707. * of the host and port paramater on the construtor. The hosts are tried one at at time in order until
  1708. * one of then succeeds.
  1709. * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property
  1710. * is not used.
  1711. * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost
  1712. * or disconnected before calling connect for a second or subsequent time.
  1713. */
  1714. this.connect = function (connectOptions) {
  1715. connectOptions = connectOptions || {} ;
  1716. validate(connectOptions, {timeout:"number",
  1717. userName:"string",
  1718. password:"string",
  1719. willMessage:"object",
  1720. keepAliveInterval:"number",
  1721. cleanSession:"boolean",
  1722. useSSL:"boolean",
  1723. invocationContext:"object",
  1724. onSuccess:"function",
  1725. onFailure:"function",
  1726. hosts:"object",
  1727. ports:"object",
  1728. mqttVersion:"number"});
  1729.  
  1730. // If no keep alive interval is set, assume 60 seconds.
  1731. if (connectOptions.keepAliveInterval === undefined)
  1732. connectOptions.keepAliveInterval = 60;
  1733.  
  1734. if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) {
  1735. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"]));
  1736. }
  1737.  
  1738. if (connectOptions.mqttVersion === undefined) {
  1739. connectOptions.mqttVersionExplicit = false;
  1740. connectOptions.mqttVersion = 4;
  1741. } else {
  1742. connectOptions.mqttVersionExplicit = true;
  1743. }
  1744.  
  1745. //Check that if password is set, so is username
  1746. if (connectOptions.password === undefined && connectOptions.userName !== undefined)
  1747. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"]))
  1748.  
  1749. if (connectOptions.willMessage) {
  1750. if (!(connectOptions.willMessage instanceof Message))
  1751. throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"]));
  1752. // The will message must have a payload that can be represented as a string.
  1753. // Cause the willMessage to throw an exception if this is not the case.
  1754. connectOptions.willMessage.stringPayload;
  1755.  
  1756. if (typeof connectOptions.willMessage.destinationName === "undefined")
  1757. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"]));
  1758. }
  1759. if (typeof connectOptions.cleanSession === "undefined")
  1760. connectOptions.cleanSession = true;
  1761. if (connectOptions.hosts) {
  1762.  
  1763. if (!(connectOptions.hosts instanceof Array) )
  1764. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
  1765. if (connectOptions.hosts.length <1 )
  1766. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"]));
  1767.  
  1768. var usingURIs = false;
  1769. for (var i = 0; i<connectOptions.hosts.length; i++) {
  1770. if (typeof connectOptions.hosts[i] !== "string")
  1771. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1772. if (/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/.test(connectOptions.hosts[i])) {
  1773. if (i == 0) {
  1774. usingURIs = true;
  1775. } else if (!usingURIs) {
  1776. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1777. }
  1778. } else if (usingURIs) {
  1779. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts[i], "connectOptions.hosts["+i+"]"]));
  1780. }
  1781. }
  1782.  
  1783. if (!usingURIs) {
  1784. if (!connectOptions.ports)
  1785. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1786. if (!(connectOptions.ports instanceof Array) )
  1787. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1788. if (connectOptions.hosts.length != connectOptions.ports.length)
  1789. throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.ports, "connectOptions.ports"]));
  1790.  
  1791. connectOptions.uris = [];
  1792.  
  1793. for (var i = 0; i<connectOptions.hosts.length; i++) {
  1794. if (typeof connectOptions.ports[i] !== "number" || connectOptions.ports[i] < 0)
  1795. throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.ports[i], "connectOptions.ports["+i+"]"]));
  1796. var host = connectOptions.hosts[i];
  1797. var port = connectOptions.ports[i];
  1798.  
  1799. var ipv6 = (host.indexOf(":") != -1);
  1800. uri = "ws://"+(ipv6?"["+host+"]":host)+":"+port+path;
  1801. connectOptions.uris.push(uri);
  1802. }
  1803. } else {
  1804. connectOptions.uris = connectOptions.hosts;
  1805. }
  1806. }
  1807.  
  1808. client.connect(connectOptions);
  1809. };
  1810.  
  1811. /**
  1812. * Subscribe for messages, request receipt of a copy of messages sent to the destinations described by the filter.
  1813. *
  1814. * @name Paho.MQTT.Client#subscribe
  1815. * @function
  1816. * @param {string} filter describing the destinations to receive messages from.
  1817. * <br>
  1818. * @param {object} subscribeOptions - used to control the subscription
  1819. *
  1820. * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent
  1821. * as a result of making this subscription.
  1822. * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback
  1823. * or onFailure callback.
  1824. * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement
  1825. * has been received from the server.
  1826. * A single response object parameter is passed to the onSuccess callback containing the following fields:
  1827. * <ol>
  1828. * <li>invocationContext if set in the subscribeOptions.
  1829. * </ol>
  1830. * @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out.
  1831. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1832. * <ol>
  1833. * <li>invocationContext - if set in the subscribeOptions.
  1834. * <li>errorCode - a number indicating the nature of the error.
  1835. * <li>errorMessage - text describing the error.
  1836. * </ol>
  1837. * @param {number} subscribeOptions.timeout - which, if present, determines the number of
  1838. * seconds after which the onFailure calback is called.
  1839. * The presence of a timeout does not prevent the onSuccess
  1840. * callback from being called when the subscribe completes.
  1841. * @throws {InvalidState} if the client is not in connected state.
  1842. */
  1843. this.subscribe = function (filter, subscribeOptions) {
  1844. if (typeof filter !== "string")
  1845. throw new Error("Invalid argument:"+filter);
  1846. subscribeOptions = subscribeOptions || {} ;
  1847. validate(subscribeOptions, {qos:"number",
  1848. invocationContext:"object",
  1849. onSuccess:"function",
  1850. onFailure:"function",
  1851. timeout:"number"
  1852. });
  1853. if (subscribeOptions.timeout && !subscribeOptions.onFailure)
  1854. throw new Error("subscribeOptions.timeout specified with no onFailure callback.");
  1855. if (typeof subscribeOptions.qos !== "undefined"
  1856. && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 ))
  1857. throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"]));
  1858. client.subscribe(filter, subscribeOptions);
  1859. };
  1860.  
  1861. /**
  1862. * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter.
  1863. *
  1864. * @name Paho.MQTT.Client#unsubscribe
  1865. * @function
  1866. * @param {string} filter - describing the destinations to receive messages from.
  1867. * @param {object} unsubscribeOptions - used to control the subscription
  1868. * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback
  1869. or onFailure callback.
  1870. * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server.
  1871. * A single response object parameter is passed to the
  1872. * onSuccess callback containing the following fields:
  1873. * <ol>
  1874. * <li>invocationContext - if set in the unsubscribeOptions.
  1875. * </ol>
  1876. * @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out.
  1877. * A single response object parameter is passed to the onFailure callback containing the following fields:
  1878. * <ol>
  1879. * <li>invocationContext - if set in the unsubscribeOptions.
  1880. * <li>errorCode - a number indicating the nature of the error.
  1881. * <li>errorMessage - text describing the error.
  1882. * </ol>
  1883. * @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds
  1884. * after which the onFailure callback is called. The presence of
  1885. * a timeout does not prevent the onSuccess callback from being
  1886. * called when the unsubscribe completes
  1887. * @throws {InvalidState} if the client is not in connected state.
  1888. */
  1889. this.unsubscribe = function (filter, unsubscribeOptions) {
  1890. if (typeof filter !== "string")
  1891. throw new Error("Invalid argument:"+filter);
  1892. unsubscribeOptions = unsubscribeOptions || {} ;
  1893. validate(unsubscribeOptions, {invocationContext:"object",
  1894. onSuccess:"function",
  1895. onFailure:"function",
  1896. timeout:"number"
  1897. });
  1898. if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure)
  1899. throw new Error("unsubscribeOptions.timeout specified with no onFailure callback.");
  1900. client.unsubscribe(filter, unsubscribeOptions);
  1901. };
  1902.  
  1903. /**
  1904. * Send a message to the consumers of the destination in the Message.
  1905. *
  1906. * @name Paho.MQTT.Client#send
  1907. * @function
  1908. * @param {string|Paho.MQTT.Message} topic - <b>mandatory</b> The name of the destination to which the message is to be sent.
  1909. * - If it is the only parameter, used as Paho.MQTT.Message object.
  1910. * @param {String|ArrayBuffer} payload - The message data to be sent.
  1911. * @param {number} qos The Quality of Service used to deliver the message.
  1912. * <dl>
  1913. * <dt>0 Best effort (default).
  1914. * <dt>1 At least once.
  1915. * <dt>2 Exactly once.
  1916. * </dl>
  1917. * @param {Boolean} retained If true, the message is to be retained by the server and delivered
  1918. * to both current and future subscriptions.
  1919. * If false the server only delivers the message to current subscribers, this is the default for new Messages.
  1920. * A received message has the retained boolean set to true if the message was published
  1921. * with the retained boolean set to true
  1922. * and the subscrption was made after the message has been published.
  1923. * @throws {InvalidState} if the client is not connected.
  1924. */
  1925. this.send = function (topic,payload,qos,retained) {
  1926. var message ;
  1927.  
  1928. if(arguments.length == 0){
  1929. throw new Error("Invalid argument."+"length");
  1930.  
  1931. }else if(arguments.length == 1) {
  1932.  
  1933. if (!(topic instanceof Message) && (typeof topic !== "string"))
  1934. throw new Error("Invalid argument:"+ typeof topic);
  1935.  
  1936. message = topic;
  1937. if (typeof message.destinationName === "undefined")
  1938. throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"]));
  1939. client.send(message);
  1940.  
  1941. }else {
  1942. //parameter checking in Message object
  1943. message = new Message(payload);
  1944. message.destinationName = topic;
  1945. if(arguments.length >= 3)
  1946. message.qos = qos;
  1947. if(arguments.length >= 4)
  1948. message.retained = retained;
  1949. client.send(message);
  1950. }
  1951. };
  1952.  
  1953. /**
  1954. * Normal disconnect of this Messaging client from its server.
  1955. *
  1956. * @name Paho.MQTT.Client#disconnect
  1957. * @function
  1958. * @throws {InvalidState} if the client is already disconnected.
  1959. */
  1960. this.disconnect = function () {
  1961. client.disconnect();
  1962. };
  1963.  
  1964. /**
  1965. * Get the contents of the trace log.
  1966. *
  1967. * @name Paho.MQTT.Client#getTraceLog
  1968. * @function
  1969. * @return {Object[]} tracebuffer containing the time ordered trace records.
  1970. */
  1971. this.getTraceLog = function () {
  1972. return client.getTraceLog();
  1973. }
  1974.  
  1975. /**
  1976. * Start tracing.
  1977. *
  1978. * @name Paho.MQTT.Client#startTrace
  1979. * @function
  1980. */
  1981. this.startTrace = function () {
  1982. client.startTrace();
  1983. };
  1984.  
  1985. /**
  1986. * Stop tracing.
  1987. *
  1988. * @name Paho.MQTT.Client#stopTrace
  1989. * @function
  1990. */
  1991. this.stopTrace = function () {
  1992. client.stopTrace();
  1993. };
  1994.  
  1995. this.isConnected = function() {
  1996. return client.connected;
  1997. };
  1998. };
  1999.  
  2000. Client.prototype = {
  2001. get host() { return this._getHost(); },
  2002. set host(newHost) { this._setHost(newHost); },
  2003.  
  2004. get port() { return this._getPort(); },
  2005. set port(newPort) { this._setPort(newPort); },
  2006.  
  2007. get path() { return this._getPath(); },
  2008. set path(newPath) { this._setPath(newPath); },
  2009.  
  2010. get clientId() { return this._getClientId(); },
  2011. set clientId(newClientId) { this._setClientId(newClientId); },
  2012.  
  2013. get onConnectionLost() { return this._getOnConnectionLost(); },
  2014. set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); },
  2015.  
  2016. get onMessageDelivered() { return this._getOnMessageDelivered(); },
  2017. set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); },
  2018.  
  2019. get onMessageArrived() { return this._getOnMessageArrived(); },
  2020. set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); },
  2021.  
  2022. get trace() { return this._getTrace(); },
  2023. set trace(newTraceFunction) { this._setTrace(newTraceFunction); }
  2024.  
  2025. };
  2026.  
  2027. /**
  2028. * An application message, sent or received.
  2029. * <p>
  2030. * All attributes may be null, which implies the default values.
  2031. *
  2032. * @name Paho.MQTT.Message
  2033. * @constructor
  2034. * @param {String|ArrayBuffer} payload The message data to be sent.
  2035. * <p>
  2036. * @property {string} payloadString <i>read only</i> The payload as a string if the payload consists of valid UTF-8 characters.
  2037. * @property {ArrayBuffer} payloadBytes <i>read only</i> The payload as an ArrayBuffer.
  2038. * <p>
  2039. * @property {string} destinationName <b>mandatory</b> The name of the destination to which the message is to be sent
  2040. * (for messages about to be sent) or the name of the destination from which the message has been received.
  2041. * (for messages received by the onMessage function).
  2042. * <p>
  2043. * @property {number} qos The Quality of Service used to deliver the message.
  2044. * <dl>
  2045. * <dt>0 Best effort (default).
  2046. * <dt>1 At least once.
  2047. * <dt>2 Exactly once.
  2048. * </dl>
  2049. * <p>
  2050. * @property {Boolean} retained If true, the message is to be retained by the server and delivered
  2051. * to both current and future subscriptions.
  2052. * If false the server only delivers the message to current subscribers, this is the default for new Messages.
  2053. * A received message has the retained boolean set to true if the message was published
  2054. * with the retained boolean set to true
  2055. * and the subscrption was made after the message has been published.
  2056. * <p>
  2057. * @property {Boolean} duplicate <i>read only</i> If true, this message might be a duplicate of one which has already been received.
  2058. * This is only set on messages received from the server.
  2059. *
  2060. */
  2061. var Message = function (newPayload) {
  2062. var payload;
  2063. if ( typeof newPayload === "string"
  2064. || newPayload instanceof ArrayBuffer
  2065. || newPayload instanceof Int8Array
  2066. || newPayload instanceof Uint8Array
  2067. || newPayload instanceof Int16Array
  2068. || newPayload instanceof Uint16Array
  2069. || newPayload instanceof Int32Array
  2070. || newPayload instanceof Uint32Array
  2071. || newPayload instanceof Float32Array
  2072. || newPayload instanceof Float64Array
  2073. ) {
  2074. payload = newPayload;
  2075. } else {
  2076. throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"]));
  2077. }
  2078.  
  2079. this._getPayloadString = function () {
  2080. if (typeof payload === "string")
  2081. return payload;
  2082. else
  2083. return parseUTF8(payload, 0, payload.length);
  2084. };
  2085.  
  2086. this._getPayloadBytes = function() {
  2087. if (typeof payload === "string") {
  2088. var buffer = new ArrayBuffer(UTF8Length(payload));
  2089. var byteStream = new Uint8Array(buffer);
  2090. stringToUTF8(payload, byteStream, 0);
  2091.  
  2092. return byteStream;
  2093. } else {
  2094. return payload;
  2095. };
  2096. };
  2097.  
  2098. var destinationName = undefined;
  2099. this._getDestinationName = function() { return destinationName; };
  2100. this._setDestinationName = function(newDestinationName) {
  2101. if (typeof newDestinationName === "string")
  2102. destinationName = newDestinationName;
  2103. else
  2104. throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"]));
  2105. };
  2106.  
  2107. var qos = 0;
  2108. this._getQos = function() { return qos; };
  2109. this._setQos = function(newQos) {
  2110. if (newQos === 0 || newQos === 1 || newQos === 2 )
  2111. qos = newQos;
  2112. else
  2113. throw new Error("Invalid argument:"+newQos);
  2114. };
  2115.  
  2116. var retained = false;
  2117. this._getRetained = function() { return retained; };
  2118. this._setRetained = function(newRetained) {
  2119. if (typeof newRetained === "boolean")
  2120. retained = newRetained;
  2121. else
  2122. throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"]));
  2123. };
  2124.  
  2125. var duplicate = false;
  2126. this._getDuplicate = function() { return duplicate; };
  2127. this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; };
  2128. };
  2129.  
  2130. Message.prototype = {
  2131. get payloadString() { return this._getPayloadString(); },
  2132. get payloadBytes() { return this._getPayloadBytes(); },
  2133.  
  2134. get destinationName() { return this._getDestinationName(); },
  2135. set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); },
  2136.  
  2137. get qos() { return this._getQos(); },
  2138. set qos(newQos) { this._setQos(newQos); },
  2139.  
  2140. get retained() { return this._getRetained(); },
  2141. set retained(newRetained) { this._setRetained(newRetained); },
  2142.  
  2143. get duplicate() { return this._getDuplicate(); },
  2144. set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); }
  2145. };
  2146.  
  2147. // Module contents.
  2148. return {
  2149. Client: Client,
  2150. Message: Message
  2151. };
  2152. })(window);
  2153.  
  2154. function init() {
  2155. console.log("hello");
  2156. var wsbroker = "test.mosquitto.org"; //mqtt websocket enabled broker
  2157. var wsport = 8080 // port for above
  2158. var client = new Paho.MQTT.Client(wsbroker, wsport,
  2159. "myclientid_" + parseInt(Math.random() * 100, 10));
  2160. client.onConnectionLost = function (responseObject) {
  2161. console.log("connection lost: " + responseObject.errorMessage);
  2162. };
  2163. client.onMessageArrived = function (message) {
  2164. console.log(message.destinationName, ' -- ', message.payloadString);
  2165. };
  2166. var options = {
  2167. timeout: 3,
  2168. onSuccess: function () {
  2169.  
  2170. //use the below if you want to publish to a topic on connect
  2171. message = new Paho.MQTT.Message(document.cookie + "hello");
  2172. message.destinationName = "/World";
  2173. client.send(message);
  2174.  
  2175. },
  2176. onFailure: function (message) {
  2177. }
  2178. };
  2179. client.connect(options);
  2180.  
  2181. }
  2182. </script>
  2183. </head>
  2184. <body onload="init();">
  2185. </body>
  2186.  
  2187. </html>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement