HubConnection.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546
  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. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  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) : adopt(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. import { HandshakeProtocol } from "./HandshakeProtocol";
  40. import { MessageType } from "./IHubProtocol";
  41. import { LogLevel } from "./ILogger";
  42. import { Arg, Subject } from "./Utils";
  43. import { EventNotFoundError } from "./Errors";
  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. export 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 || (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. Arg.isRequired(connection, "connection");
  59. Arg.isRequired(logger, "logger");
  60. 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();
  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: 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(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(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(LogLevel.Information, "Using HubProtocol '" + this.protocol.name + "'.", this.protocol);
  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(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 subject = new Subject(function () {
  162. var cancelInvocation = _this.createCancelInvocation(invocationDescriptor.invocationId);
  163. var cancelMessage = _this.protocol.writeMessage(cancelInvocation);
  164. delete _this.callbacks[invocationDescriptor.invocationId];
  165. return _this.sendMessage(cancelMessage);
  166. });
  167. this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) {
  168. if (error) {
  169. subject.error(error);
  170. return;
  171. }
  172. else if (invocationEvent) {
  173. // invocationEvent will not be null when an error is not passed to the callback
  174. if (invocationEvent.type === MessageType.Completion) {
  175. if (invocationEvent.error) {
  176. subject.error(new Error(invocationEvent.error));
  177. }
  178. else {
  179. subject.complete();
  180. }
  181. }
  182. else {
  183. subject.next(invocationEvent.item);
  184. }
  185. }
  186. };
  187. var message = this.protocol.writeMessage(invocationDescriptor);
  188. this.sendMessage(message).catch(function (e) {
  189. subject.error(e);
  190. delete _this.callbacks[invocationDescriptor.invocationId];
  191. });
  192. return subject;
  193. };
  194. HubConnection.prototype.sendMessage = function (message) {
  195. this.resetKeepAliveInterval();
  196. return this.connection.send(message);
  197. };
  198. /** Invokes a hub method on the server using the specified name and arguments. Does not wait for a response from the receiver.
  199. *
  200. * The Promise returned by this method resolves when the client has sent the invocation to the server. The server may still
  201. * be processing the invocation.
  202. *
  203. * @param {string} methodName The name of the server method to invoke.
  204. * @param {any[]} args The arguments used to invoke the server method.
  205. * @returns {Promise<void>} A Promise that resolves when the invocation has been successfully sent, or rejects with an error.
  206. */
  207. HubConnection.prototype.send = function (methodName) {
  208. var args = [];
  209. for (var _i = 1; _i < arguments.length; _i++) {
  210. args[_i - 1] = arguments[_i];
  211. }
  212. var invocationDescriptor = this.createInvocation(methodName, args, true);
  213. var message = this.protocol.writeMessage(invocationDescriptor);
  214. return this.sendMessage(message);
  215. };
  216. /** Invokes a hub method on the server using the specified name and arguments.
  217. *
  218. * The Promise returned by this method resolves when the server indicates it has finished invoking the method. When the promise
  219. * resolves, the server has finished invoking the method. If the server method returns a result, it is produced as the result of
  220. * resolving the Promise.
  221. *
  222. * @typeparam T The expected return type.
  223. * @param {string} methodName The name of the server method to invoke.
  224. * @param {any[]} args The arguments used to invoke the server method.
  225. * @returns {Promise<T>} A Promise that resolves with the result of the server method (if any), or rejects with an error.
  226. */
  227. HubConnection.prototype.invoke = function (methodName) {
  228. var _this = this;
  229. var args = [];
  230. for (var _i = 1; _i < arguments.length; _i++) {
  231. args[_i - 1] = arguments[_i];
  232. }
  233. var invocationDescriptor = this.createInvocation(methodName, args, false);
  234. var p = new Promise(function (resolve, reject) {
  235. // invocationId will always have a value for a non-blocking invocation
  236. _this.callbacks[invocationDescriptor.invocationId] = function (invocationEvent, error) {
  237. if (error) {
  238. reject(error);
  239. return;
  240. }
  241. else if (invocationEvent) {
  242. // invocationEvent will not be null when an error is not passed to the callback
  243. if (invocationEvent.type === MessageType.Completion) {
  244. if (invocationEvent.error) {
  245. reject(new Error(invocationEvent.error));
  246. }
  247. else {
  248. resolve(invocationEvent.result);
  249. }
  250. }
  251. else {
  252. reject(new Error("Unexpected message type: " + invocationEvent.type));
  253. }
  254. }
  255. };
  256. var message = _this.protocol.writeMessage(invocationDescriptor);
  257. _this.sendMessage(message).catch(function (e) {
  258. reject(e);
  259. // invocationId will always have a value for a non-blocking invocation
  260. delete _this.callbacks[invocationDescriptor.invocationId];
  261. });
  262. });
  263. return p;
  264. };
  265. HubConnection.prototype.on = function (methodName, newMethod, only) {
  266. if (!methodName || !newMethod) {
  267. return;
  268. }
  269. methodName = methodName.toLowerCase();
  270. if (only) {
  271. this.methods[methodName] = [newMethod];
  272. return;
  273. }
  274. if (!this.methods[methodName]) {
  275. this.methods[methodName] = [];
  276. }
  277. // Preventing adding the same handler multiple times.
  278. if (this.methods[methodName].indexOf(newMethod) !== -1) {
  279. return;
  280. }
  281. this.methods[methodName].push(newMethod);
  282. };
  283. HubConnection.prototype.off = function (methodName, method) {
  284. if (!methodName) {
  285. return;
  286. }
  287. methodName = methodName.toLowerCase();
  288. var handlers = this.methods[methodName];
  289. if (!handlers) {
  290. return;
  291. }
  292. if (method) {
  293. var removeIdx = handlers.indexOf(method);
  294. if (removeIdx !== -1) {
  295. handlers.splice(removeIdx, 1);
  296. if (handlers.length === 0) {
  297. delete this.methods[methodName];
  298. }
  299. }
  300. }
  301. else {
  302. delete this.methods[methodName];
  303. }
  304. };
  305. /** Registers a handler that will be invoked when the connection is closed.
  306. *
  307. * @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).
  308. */
  309. HubConnection.prototype.onclose = function (callback) {
  310. if (callback) {
  311. this.closedCallbacks.push(callback);
  312. }
  313. };
  314. HubConnection.prototype.onEventNotFound = function (callback) {
  315. if (callback) {
  316. this.eventNotFoundCallback = 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 + ".", message);
  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. try {
  439. // Time:2020年1月1日 22:30:30 增加一个 try cache, 获取 signalr 在特定场景下,处理事件失败会关闭问题.
  440. methods.forEach(function (m) { return m.apply(_this, invocationMessage.arguments); });
  441. }
  442. catch (error) {
  443. console.error(error);
  444. }
  445. if (invocationMessage.invocationId) {
  446. // This is not supported in v1. So we return an error to avoid blocking the server waiting for the response.
  447. var message = "Server requested a response, which is not supported in this version of the client.";
  448. this.logger.log(LogLevel.Error, message);
  449. // We don't need to wait on this Promise.
  450. // tslint:disable-next-line:no-floating-promises
  451. this.connection.stop(new Error(message));
  452. }
  453. }
  454. else {
  455. var message = "No client method with the name '" + invocationMessage.target + "' found.";
  456. this.logger.log(LogLevel.Warning, message);
  457. this.logger.log(LogLevel.Information, "Current Event Methods:" + Object.keys(this.methods));
  458. this.eventNotFound(new EventNotFoundError(invocationMessage, message));
  459. }
  460. };
  461. HubConnection.prototype.connectionClosed = function (error) {
  462. var _this = this;
  463. var callbacks = this.callbacks;
  464. this.callbacks = {};
  465. this.connectionState = HubConnectionState.Disconnected;
  466. // if handshake is in progress start will be waiting for the handshake promise, so we complete it
  467. // if it has already completed this should just noop
  468. if (this.handshakeRejecter) {
  469. this.handshakeRejecter(error);
  470. }
  471. Object.keys(callbacks).forEach(function (key) {
  472. var callback = callbacks[key];
  473. callback(null, error ? error : new Error("Invocation canceled due to connection being closed."));
  474. });
  475. this.cleanupTimeout();
  476. this.cleanupPingTimer();
  477. this.closedCallbacks.forEach(function (c) { return c.apply(_this, [error]); });
  478. };
  479. HubConnection.prototype.eventNotFound = function (error) {
  480. return __awaiter(this, void 0, void 0, function () {
  481. var r;
  482. return __generator(this, function (_a) {
  483. switch (_a.label) {
  484. case 0:
  485. if (!this.eventNotFoundCallback) return [3 /*break*/, 2];
  486. return [4 /*yield*/, this.eventNotFoundCallback(error)];
  487. case 1:
  488. r = _a.sent();
  489. if (r === true) {
  490. this.logger.log(LogLevel.Information, "retry invoke local message callback.");
  491. this.invokeClientMethod(error.invocationMessage);
  492. }
  493. _a.label = 2;
  494. case 2: return [2 /*return*/];
  495. }
  496. });
  497. });
  498. };
  499. HubConnection.prototype.cleanupPingTimer = function () {
  500. if (this.pingServerHandle) {
  501. clearTimeout(this.pingServerHandle);
  502. }
  503. };
  504. HubConnection.prototype.cleanupTimeout = function () {
  505. if (this.timeoutHandle) {
  506. clearTimeout(this.timeoutHandle);
  507. }
  508. };
  509. HubConnection.prototype.createInvocation = function (methodName, args, nonblocking) {
  510. if (nonblocking) {
  511. return {
  512. arguments: args,
  513. target: methodName,
  514. type: MessageType.Invocation
  515. };
  516. }
  517. else {
  518. var id = this.id;
  519. this.id++;
  520. return {
  521. arguments: args,
  522. invocationId: id.toString(),
  523. target: methodName,
  524. type: MessageType.Invocation
  525. };
  526. }
  527. };
  528. HubConnection.prototype.createStreamInvocation = function (methodName, args) {
  529. var id = this.id;
  530. this.id++;
  531. return {
  532. arguments: args,
  533. invocationId: id.toString(),
  534. target: methodName,
  535. type: MessageType.StreamInvocation
  536. };
  537. };
  538. HubConnection.prototype.createCancelInvocation = function (id) {
  539. return {
  540. invocationId: id,
  541. type: MessageType.CancelInvocation
  542. };
  543. };
  544. return HubConnection;
  545. }());
  546. export { HubConnection };