HubConnection.js 24 KB

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