Advertisement
Guest User

Untitled

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