預約系統 IOS / Android Windows

paho-mqtt.js 88KB

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