HubConnection.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. "use strict";
  2. // Copyright (c) .NET Foundation. All rights reserved.
  3. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  4. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  5. return new (P || (P = Promise))(function (resolve, reject) {
  6. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  7. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  8. function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
  9. step((generator = generator.apply(thisArg, _arguments || [])).next());
  10. });
  11. };
  12. var __generator = (this && this.__generator) || function (thisArg, body) {
  13. var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
  14. return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
  15. function verb(n) { return function (v) { return step([n, v]); }; }
  16. function step(op) {
  17. if (f) throw new TypeError("Generator is already executing.");
  18. while (_) try {
  19. if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
  20. if (y = 0, t) op = [op[0] & 2, t.value];
  21. switch (op[0]) {
  22. case 0: case 1: t = op; break;
  23. case 4: _.label++; return { value: op[1], done: false };
  24. case 5: _.label++; y = op[1]; op = [0]; continue;
  25. case 7: op = _.ops.pop(); _.trys.pop(); continue;
  26. default:
  27. if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
  28. if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
  29. if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
  30. if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
  31. if (t[2]) _.ops.pop();
  32. _.trys.pop(); continue;
  33. }
  34. op = body.call(thisArg, _);
  35. } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
  36. if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
  37. }
  38. };
  39. Object.defineProperty(exports, "__esModule", { value: true });
  40. var HandshakeProtocol_1 = require("./HandshakeProtocol");
  41. var IHubProtocol_1 = require("./IHubProtocol");
  42. var ILogger_1 = require("./ILogger");
  43. var Utils_1 = require("./Utils");
  44. var DEFAULT_TIMEOUT_IN_MS = 30 * 1000;
  45. var DEFAULT_PING_INTERVAL_IN_MS = 15 * 1000;
  46. /** Describes the current state of the {@link HubConnection} to the server. */
  47. var HubConnectionState;
  48. (function (HubConnectionState) {
  49. /** The hub connection is disconnected. */
  50. HubConnectionState[HubConnectionState["Disconnected"] = 0] = "Disconnected";
  51. /** The hub connection is connected. */
  52. HubConnectionState[HubConnectionState["Connected"] = 1] = "Connected";
  53. })(HubConnectionState = exports.HubConnectionState || (exports.HubConnectionState = {}));
  54. /** Represents a connection to a SignalR Hub. */
  55. var HubConnection = /** @class */ (function () {
  56. function HubConnection(connection, logger, protocol) {
  57. var _this = this;
  58. Utils_1.Arg.isRequired(connection, "connection");
  59. Utils_1.Arg.isRequired(logger, "logger");
  60. Utils_1.Arg.isRequired(protocol, "protocol");
  61. this.serverTimeoutInMilliseconds = DEFAULT_TIMEOUT_IN_MS;
  62. this.keepAliveIntervalInMilliseconds = DEFAULT_PING_INTERVAL_IN_MS;
  63. this.logger = logger;
  64. this.protocol = protocol;
  65. this.connection = connection;
  66. this.handshakeProtocol = new HandshakeProtocol_1.HandshakeProtocol();
  67. this.connection.onreceive = function (data) { return _this.processIncomingData(data); };
  68. this.connection.onclose = function (error) { return _this.connectionClosed(error); };
  69. this.callbacks = {};
  70. this.methods = {};
  71. this.closedCallbacks = [];
  72. this.id = 0;
  73. this.receivedHandshakeResponse = false;
  74. this.connectionState = HubConnectionState.Disconnected;
  75. this.cachedPingMessage = this.protocol.writeMessage({ type: IHubProtocol_1.MessageType.Ping });
  76. }
  77. /** @internal */
  78. // Using a public static factory method means we can have a private constructor and an _internal_
  79. // create method that can be used by HubConnectionBuilder. An "internal" constructor would just
  80. // be stripped away and the '.d.ts' file would have no constructor, which is interpreted as a
  81. // public parameter-less constructor.
  82. HubConnection.create = function (connection, logger, protocol) {
  83. return new HubConnection(connection, logger, protocol);
  84. };
  85. Object.defineProperty(HubConnection.prototype, "state", {
  86. /** Indicates the state of the {@link HubConnection} to the server. */
  87. get: function () {
  88. return this.connectionState;
  89. },
  90. enumerable: true,
  91. configurable: true
  92. });
  93. /** Starts the connection.
  94. *
  95. * @returns {Promise<void>} A Promise that resolves when the connection has been successfully established, or rejects with an error.
  96. */
  97. HubConnection.prototype.start = function () {
  98. return __awaiter(this, void 0, void 0, function () {
  99. var handshakeRequest, handshakePromise;
  100. var _this = this;
  101. return __generator(this, function (_a) {
  102. switch (_a.label) {
  103. case 0:
  104. handshakeRequest = {
  105. protocol: this.protocol.name,
  106. version: this.protocol.version,
  107. };
  108. this.logger.log(ILogger_1.LogLevel.Debug, "Starting HubConnection.");
  109. this.receivedHandshakeResponse = false;
  110. handshakePromise = new Promise(function (resolve, reject) {
  111. _this.handshakeResolver = resolve;
  112. _this.handshakeRejecter = reject;
  113. });
  114. return [4 /*yield*/, this.connection.start(this.protocol.transferFormat)];
  115. case 1:
  116. _a.sent();
  117. this.logger.log(ILogger_1.LogLevel.Debug, "Sending handshake request.");
  118. return [4 /*yield*/, this.sendMessage(this.handshakeProtocol.writeHandshakeRequest(handshakeRequest))];
  119. case 2:
  120. _a.sent();
  121. this.logger.log(ILogger_1.LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'.");
  122. // defensively cleanup timeout in case we receive a message from the server before we finish start
  123. this.cleanupTimeout();
  124. this.resetTimeoutPeriod();
  125. this.resetKeepAliveInterval();
  126. // Wait for the handshake to complete before marking connection as connected
  127. return [4 /*yield*/, handshakePromise];
  128. case 3:
  129. // Wait for the handshake to complete before marking connection as connected
  130. _a.sent();
  131. this.connectionState = HubConnectionState.Connected;
  132. return [2 /*return*/];
  133. }
  134. });
  135. });
  136. };
  137. /** Stops the connection.
  138. *
  139. * @returns {Promise<void>} A Promise that resolves when the connection has been successfully terminated, or rejects with an error.
  140. */
  141. HubConnection.prototype.stop = function () {
  142. this.logger.log(ILogger_1.LogLevel.Debug, "Stopping HubConnection.");
  143. this.cleanupTimeout();
  144. this.cleanupPingTimer();
  145. return this.connection.stop();
  146. };
  147. /** Invokes a streaming hub method on the server using the specified name and arguments.
  148. *
  149. * @typeparam T The type of the items returned by the server.
  150. * @param {string} methodName The name of the server method to invoke.
  151. * @param {any[]} args The arguments used to invoke the server method.
  152. * @returns {IStreamResult<T>} An object that yields results from the server as they are received.
  153. */
  154. HubConnection.prototype.stream = function (methodName) {
  155. var _this = this;
  156. var args = [];
  157. for (var _i = 1; _i < arguments.length; _i++) {
  158. args[_i - 1] = arguments[_i];
  159. }
  160. var invocationDescriptor = this.createStreamInvocation(methodName, args);
  161. var promiseQueue;
  162. var subject = new Utils_1.Subject();
  163. subject.cancelCallback = function () {
  164. var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId);
  165. var cancelMessage = _this.protocol.writeMessage(cancelInvocation);
  166. delete _this.callbacks[invocationDescriptor.invocationId];
  167. return promiseQueue.then(function () {
  168. return _this.sendMessage(cancelMessage);
  169. });
  170. };
  171. this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) {
  172. if (error) {
  173. subject.error(error);
  174. return;
  175. }
  176. else if (invocationEvent) {
  177. // invocationEvent will not be null when an error is not passed to the callback
  178. if (invocationEvent.type === IHubProtocol_1.MessageType.Completion) {
  179. if (invocationEvent.error) {
  180. subject.error(new Error(invocationEvent.error));
  181. }
  182. else {
  183. subject.complete();
  184. }
  185. }
  186. else {
  187. subject.next((invocationEvent.item));
  188. }
  189. }
  190. };
  191. var message = this.protocol.writeMessage(invocationDescriptor);
  192. promiseQueue = this.sendMessage(message)
  193. .catch(function (e) {
  194. subject.error(e);
  195. delete _this.callbacks[invocationDescriptor.invocationId];
  196. });
  197. return subject;
  198. };
  199. HubConnection.prototype.sendMessage = function (message) {
  200. this.resetKeepAliveInterval();
  201. return this.connection.send(message);
  202. };
  203. /** Invokes a hub method on the server using the specified name and arguments. Does not wait for a response from the receiver.
  204. *
  205. * The Promise returned by this method resolves when the client has sent the invocation to the server. The server may still
  206. * be processing the invocation.
  207. *
  208. * @param {string} methodName The name of the server method to invoke.
  209. * @param {any[]} args The arguments used to invoke the server method.
  210. * @returns {Promise<void>} A Promise that resolves when the invocation has been successfully sent, or rejects with an error.
  211. */
  212. HubConnection.prototype.send = function (methodName) {
  213. var args = [];
  214. for (var _i = 1; _i < arguments.length; _i++) {
  215. args[_i - 1] = arguments[_i];
  216. }
  217. var invocationDescriptor = this.createInvocation(methodName, args, true);
  218. var message = this.protocol.writeMessage(invocationDescriptor);
  219. return this.sendMessage(message);
  220. };
  221. /** Invokes a hub method on the server using the specified name and arguments.
  222. *
  223. * The Promise returned by this method resolves when the server indicates it has finished invoking the method. When the promise
  224. * resolves, the server has finished invoking the method. If the server method returns a result, it is produced as the result of
  225. * resolving the Promise.
  226. *
  227. * @typeparam T The expected return type.
  228. * @param {string} methodName The name of the server method to invoke.
  229. * @param {any[]} args The arguments used to invoke the server method.
  230. * @returns {Promise<T>} A Promise that resolves with the result of the server method (if any), or rejects with an error.
  231. */
  232. HubConnection.prototype.invoke = function (methodName) {
  233. var _this = this;
  234. var args = [];
  235. for (var _i = 1; _i < arguments.length; _i++) {
  236. args[_i - 1] = arguments[_i];
  237. }
  238. var invocationDescriptor = this.createInvocation(methodName, args, false);
  239. var p = new Promise(function (resolve, reject) {
  240. // invocationId will always have a value for a non-blocking invocation
  241. _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) {
  242. if (error) {
  243. reject(error);
  244. return;
  245. }
  246. else if (invocationEvent) {
  247. // invocationEvent will not be null when an error is not passed to the callback
  248. if (invocationEvent.type === IHubProtocol_1.MessageType.Completion) {
  249. if (invocationEvent.error) {
  250. reject(new Error(invocationEvent.error));
  251. }
  252. else {
  253. resolve(invocationEvent.result);
  254. }
  255. }
  256. else {
  257. reject(new Error("Unexpected message type: " + invocationEvent.type));
  258. }
  259. }
  260. };
  261. var message = _this.protocol.writeMessage(invocationDescriptor);
  262. _this.sendMessage(message)
  263. .catch(function (e) {
  264. reject(e);
  265. // invocationId will always have a value for a non-blocking invocation
  266. delete _this.callbacks[invocationDescriptor.invocationId];
  267. });
  268. });
  269. return p;
  270. };
  271. /** Registers a handler that will be invoked when the hub method with the specified method name is invoked.
  272. *
  273. * @param {string} methodName The name of the hub method to define.
  274. * @param {Function} newMethod The handler that will be raised when the hub method is invoked.
  275. */
  276. HubConnection.prototype.on = function (methodName, newMethod) {
  277. if (!methodName || !newMethod) {
  278. return;
  279. }
  280. methodName = methodName.toLowerCase();
  281. if (!this.methods[methodName]) {
  282. this.methods[methodName] = [];
  283. }
  284. // Preventing adding the same handler multiple times.
  285. if (this.methods[methodName].indexOf(newMethod) !== -1) {
  286. return;
  287. }
  288. this.methods[methodName].push(newMethod);
  289. };
  290. HubConnection.prototype.off = function (methodName, method) {
  291. if (!methodName) {
  292. return;
  293. }
  294. methodName = methodName.toLowerCase();
  295. var handlers = this.methods[methodName];
  296. if (!handlers) {
  297. return;
  298. }
  299. if (method) {
  300. var removeIdx = handlers.indexOf(method);
  301. if (removeIdx !== -1) {
  302. handlers.splice(removeIdx, 1);
  303. if (handlers.length === 0) {
  304. delete this.methods[methodName];
  305. }
  306. }
  307. }
  308. else {
  309. delete this.methods[methodName];
  310. }
  311. };
  312. /** Registers a handler that will be invoked when the connection is closed.
  313. *
  314. * @param {Function} callback The handler that will be invoked when the connection is closed. Optionally receives a single argument containing the error that caused the connection to close (if any).
  315. */
  316. HubConnection.prototype.onclose = function (callback) {
  317. if (callback) {
  318. this.closedCallbacks.push(callback);
  319. }
  320. };
  321. HubConnection.prototype.processIncomingData = function (data) {
  322. this.cleanupTimeout();
  323. if (!this.receivedHandshakeResponse) {
  324. data = this.processHandshakeResponse(data);
  325. this.receivedHandshakeResponse = true;
  326. }
  327. // Data may have all been read when processing handshake response
  328. if (data) {
  329. // Parse the messages
  330. var messages = this.protocol.parseMessages(data, this.logger);
  331. for (var _i = 0, messages_1 = messages; _i < messages_1.length; _i++) {
  332. var message = messages_1[_i];
  333. switch (message.type) {
  334. case IHubProtocol_1.MessageType.Invocation:
  335. this.invokeClientMethod(message);
  336. break;
  337. case IHubProtocol_1.MessageType.StreamItem:
  338. case IHubProtocol_1.MessageType.Completion:
  339. var callback = this.callbacks[message.invocationId];
  340. if (callback != null) {
  341. if (message.type === IHubProtocol_1.MessageType.Completion) {
  342. delete this.callbacks[message.invocationId];
  343. }
  344. callback(message);
  345. }
  346. break;
  347. case IHubProtocol_1.MessageType.Ping:
  348. // Don't care about pings
  349. break;
  350. case IHubProtocol_1.MessageType.Close:
  351. this.logger.log(ILogger_1.LogLevel.Information, "Close message received from server.");
  352. // We don't want to wait on the stop itself.
  353. // tslint:disable-next-line:no-floating-promises
  354. this.connection.stop(message.error ? new Error("Server returned an error on close: " + message.error) : undefined);
  355. break;
  356. default:
  357. this.logger.log(ILogger_1.LogLevel.Warning, "Invalid message type: " + message.type + ".");
  358. break;
  359. }
  360. }
  361. }
  362. this.resetTimeoutPeriod();
  363. };
  364. HubConnection.prototype.processHandshakeResponse = function (data) {
  365. var _a;
  366. var responseMessage;
  367. var remainingData;
  368. try {
  369. _a = this.handshakeProtocol.parseHandshakeResponse(data), remainingData = _a[0], responseMessage = _a[1];
  370. }
  371. catch (e) {
  372. var message = "Error parsing handshake response: " + e;
  373. this.logger.log(ILogger_1.LogLevel.Error, message);
  374. var error = new Error(message);
  375. // We don't want to wait on the stop itself.
  376. // tslint:disable-next-line:no-floating-promises
  377. this.connection.stop(error);
  378. this.handshakeRejecter(error);
  379. throw error;
  380. }
  381. if (responseMessage.error) {
  382. var message = "Server returned handshake error: " + responseMessage.error;
  383. this.logger.log(ILogger_1.LogLevel.Error, message);
  384. this.handshakeRejecter(message);
  385. // We don't want to wait on the stop itself.
  386. // tslint:disable-next-line:no-floating-promises
  387. this.connection.stop(new Error(message));
  388. throw new Error(message);
  389. }
  390. else {
  391. this.logger.log(ILogger_1.LogLevel.Debug, "Server handshake complete.");
  392. }
  393. this.handshakeResolver();
  394. return remainingData;
  395. };
  396. HubConnection.prototype.resetKeepAliveInterval = function () {
  397. var _this = this;
  398. this.cleanupPingTimer();
  399. this.pingServerHandle = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
  400. var _a;
  401. return __generator(this, function (_b) {
  402. switch (_b.label) {
  403. case 0:
  404. if (!(this.connectionState === HubConnectionState.Connected)) return [3 /*break*/, 4];
  405. _b.label = 1;
  406. case 1:
  407. _b.trys.push([1, 3, , 4]);
  408. return [4 /*yield*/, this.sendMessage(this.cachedPingMessage)];
  409. case 2:
  410. _b.sent();
  411. return [3 /*break*/, 4];
  412. case 3:
  413. _a = _b.sent();
  414. // We don't care about the error. It should be seen elsewhere in the client.
  415. // The connection is probably in a bad or closed state now, cleanup the timer so it stops triggering
  416. this.cleanupPingTimer();
  417. return [3 /*break*/, 4];
  418. case 4: return [2 /*return*/];
  419. }
  420. });
  421. }); }, this.keepAliveIntervalInMilliseconds);
  422. };
  423. HubConnection.prototype.resetTimeoutPeriod = function () {
  424. var _this = this;
  425. if (!this.connection.features || !this.connection.features.inherentKeepAlive) {
  426. // Set the timeout timer
  427. this.timeoutHandle = setTimeout(function () { return _this.serverTimeout(); }, this.serverTimeoutInMilliseconds);
  428. }
  429. };
  430. HubConnection.prototype.serverTimeout = function () {
  431. // The server hasn't talked to us in a while. It doesn't like us anymore ... :(
  432. // Terminate the connection, but we don't need to wait on the promise.
  433. // tslint:disable-next-line:no-floating-promises
  434. this.connection.stop(new Error("Server timeout elapsed without receiving a message from the server."));
  435. };
  436. HubConnection.prototype.invokeClientMethod = function (invocationMessage) {
  437. var _this = this;
  438. var methods = this.methods[invocationMessage.target.toLowerCase()];
  439. if (methods) {
  440. methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); });
  441. if (invocationMessage.invocationId) {
  442. // This is not supported in v1. So we return an error to avoid blocking the server waiting for the response.
  443. var message = "Server requested a response, which is not supported in this version of the client.";
  444. this.logger.log(ILogger_1.LogLevel.Error, message);
  445. // We don't need to wait on this Promise.
  446. // tslint:disable-next-line:no-floating-promises
  447. this.connection.stop(new Error(message));
  448. }
  449. }
  450. else {
  451. this.logger.log(ILogger_1.LogLevel.Warning, "No client method with the name '" + invocationMessage.target + "' found.");
  452. }
  453. };
  454. HubConnection.prototype.connectionClosed = function (error) {
  455. var _this = this;
  456. var callbacks = this.callbacks;
  457. this.callbacks = {};
  458. this.connectionState = HubConnectionState.Disconnected;
  459. // if handshake is in progress start will be waiting for the handshake promise, so we complete it
  460. // if it has already completed this should just noop
  461. if (this.handshakeRejecter) {
  462. this.handshakeRejecter(error);
  463. }
  464. Object.keys(callbacks)
  465. .forEach(function (key) {
  466. var callback = callbacks[key];
  467. callback(null, error ? error : new Error("Invocation canceled due to connection being closed."));
  468. });
  469. this.cleanupTimeout();
  470. this.cleanupPingTimer();
  471. this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); });
  472. };
  473. HubConnection.prototype.cleanupPingTimer = function () {
  474. if (this.pingServerHandle) {
  475. clearTimeout(this.pingServerHandle);
  476. }
  477. };
  478. HubConnection.prototype.cleanupTimeout = function () {
  479. if (this.timeoutHandle) {
  480. clearTimeout(this.timeoutHandle);
  481. }
  482. };
  483. HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) {
  484. if (nonblocking) {
  485. return {
  486. arguments: args,
  487. target: methodName,
  488. type: IHubProtocol_1.MessageType.Invocation,
  489. };
  490. }
  491. else {
  492. var id = this.id;
  493. this.id++;
  494. return {
  495. arguments: args,
  496. invocationId: id.toString(),
  497. target: methodName,
  498. type: IHubProtocol_1.MessageType.Invocation,
  499. };
  500. }
  501. };
  502. HubConnection.prototype.createStreamInvocation = function (methodName, args) {
  503. var id = this.id;
  504. this.id++;
  505. return {
  506. arguments: args,
  507. invocationId: id.toString(),
  508. target: methodName,
  509. type: IHubProtocol_1.MessageType.StreamInvocation,
  510. };
  511. };
  512. HubConnection.prototype.createCancelInvocation = function (id) {
  513. return {
  514. invocationId: id,
  515. type: IHubProtocol_1.MessageType.CancelInvocation,
  516. };
  517. };
  518. return HubConnection;
  519. }());
  520. exports.HubConnection = HubConnection;
  521. //# sourceMappingURL=HubConnection.js.map