mqttws31.js 79 KB

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