LongPollingTransport.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  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 { AbortController } from "./AbortController";
  39. import { HttpError, TimeoutError } from "./Errors";
  40. import { LogLevel } from "./ILogger";
  41. import { TransferFormat } from "./ITransport";
  42. import { Arg, getDataDetail, sendMessage } from "./Utils";
  43. // Not exported from 'index', this type is internal.
  44. /** @private */
  45. var LongPollingTransport = /** @class */ (function () {
  46. function LongPollingTransport(httpClient, accessTokenFactory, logger, logMessageContent) {
  47. this.httpClient = httpClient;
  48. this.accessTokenFactory = accessTokenFactory;
  49. this.logger = logger;
  50. this.pollAbort = new AbortController();
  51. this.logMessageContent = logMessageContent;
  52. this.running = false;
  53. this.onreceive = null;
  54. this.onclose = null;
  55. }
  56. Object.defineProperty(LongPollingTransport.prototype, "pollAborted", {
  57. // This is an internal type, not exported from 'index' so this is really just internal.
  58. get: function () {
  59. return this.pollAbort.aborted;
  60. },
  61. enumerable: true,
  62. configurable: true
  63. });
  64. LongPollingTransport.prototype.connect = function (url, transferFormat) {
  65. return __awaiter(this, void 0, void 0, function () {
  66. var pollOptions, token, pollUrl, response;
  67. return __generator(this, function (_a) {
  68. switch (_a.label) {
  69. case 0:
  70. Arg.isRequired(url, "url");
  71. Arg.isRequired(transferFormat, "transferFormat");
  72. Arg.isIn(transferFormat, TransferFormat, "transferFormat");
  73. this.url = url;
  74. this.logger.log(LogLevel.Trace, "(LongPolling transport) Connecting.");
  75. // Allow binary format on Node and Browsers that support binary content (indicated by the presence of responseType property)
  76. if (transferFormat === TransferFormat.Binary &&
  77. (typeof XMLHttpRequest !== "undefined" && typeof new XMLHttpRequest().responseType !== "string")) {
  78. throw new Error("Binary protocols over XmlHttpRequest not implementing advanced features are not supported.");
  79. }
  80. pollOptions = {
  81. abortSignal: this.pollAbort.signal,
  82. headers: {},
  83. timeout: 100000,
  84. };
  85. if (transferFormat === TransferFormat.Binary) {
  86. pollOptions.responseType = "arraybuffer";
  87. }
  88. return [4 /*yield*/, this.getAccessToken()];
  89. case 1:
  90. token = _a.sent();
  91. this.updateHeaderToken(pollOptions, token);
  92. pollUrl = url + "&_=" + Date.now();
  93. this.logger.log(LogLevel.Trace, "(LongPolling transport) polling: " + pollUrl + ".");
  94. return [4 /*yield*/, this.httpClient.get(pollUrl, pollOptions)];
  95. case 2:
  96. response = _a.sent();
  97. if (response.statusCode !== 200) {
  98. this.logger.log(LogLevel.Error, "(LongPolling transport) Unexpected response code: " + response.statusCode + ".");
  99. // Mark running as false so that the poll immediately ends and runs the close logic
  100. this.closeError = new HttpError(response.statusText || "", response.statusCode);
  101. this.running = false;
  102. }
  103. else {
  104. this.running = true;
  105. }
  106. this.receiving = this.poll(this.url, pollOptions);
  107. return [2 /*return*/];
  108. }
  109. });
  110. });
  111. };
  112. LongPollingTransport.prototype.getAccessToken = function () {
  113. return __awaiter(this, void 0, void 0, function () {
  114. return __generator(this, function (_a) {
  115. switch (_a.label) {
  116. case 0:
  117. if (!this.accessTokenFactory) return [3 /*break*/, 2];
  118. return [4 /*yield*/, this.accessTokenFactory()];
  119. case 1: return [2 /*return*/, _a.sent()];
  120. case 2: return [2 /*return*/, null];
  121. }
  122. });
  123. });
  124. };
  125. LongPollingTransport.prototype.updateHeaderToken = function (request, token) {
  126. if (!request.headers) {
  127. request.headers = {};
  128. }
  129. if (token) {
  130. // tslint:disable-next-line:no-string-literal
  131. request.headers["Authorization"] = "Bearer " + token;
  132. return;
  133. }
  134. // tslint:disable-next-line:no-string-literal
  135. if (request.headers["Authorization"]) {
  136. // tslint:disable-next-line:no-string-literal
  137. delete request.headers["Authorization"];
  138. }
  139. };
  140. LongPollingTransport.prototype.poll = function (url, pollOptions) {
  141. return __awaiter(this, void 0, void 0, function () {
  142. var token, pollUrl, response, e_1;
  143. return __generator(this, function (_a) {
  144. switch (_a.label) {
  145. case 0:
  146. _a.trys.push([0, , 8, 9]);
  147. _a.label = 1;
  148. case 1:
  149. if (!this.running) return [3 /*break*/, 7];
  150. return [4 /*yield*/, this.getAccessToken()];
  151. case 2:
  152. token = _a.sent();
  153. this.updateHeaderToken(pollOptions, token);
  154. _a.label = 3;
  155. case 3:
  156. _a.trys.push([3, 5, , 6]);
  157. pollUrl = url + "&_=" + Date.now();
  158. this.logger.log(LogLevel.Trace, "(LongPolling transport) polling: " + pollUrl + ".");
  159. return [4 /*yield*/, this.httpClient.get(pollUrl, pollOptions)];
  160. case 4:
  161. response = _a.sent();
  162. if (response.statusCode === 204) {
  163. this.logger.log(LogLevel.Information, "(LongPolling transport) Poll terminated by server.");
  164. this.running = false;
  165. }
  166. else if (response.statusCode !== 200) {
  167. this.logger.log(LogLevel.Error, "(LongPolling transport) Unexpected response code: " + response.statusCode + ".");
  168. // Unexpected status code
  169. this.closeError = new HttpError(response.statusText || "", response.statusCode);
  170. this.running = false;
  171. }
  172. else {
  173. // Process the response
  174. if (response.content) {
  175. this.logger.log(LogLevel.Trace, "(LongPolling transport) data received. " + getDataDetail(response.content, this.logMessageContent) + ".");
  176. if (this.onreceive) {
  177. this.onreceive(response.content);
  178. }
  179. }
  180. else {
  181. // This is another way timeout manifest.
  182. this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
  183. }
  184. }
  185. return [3 /*break*/, 6];
  186. case 5:
  187. e_1 = _a.sent();
  188. if (!this.running) {
  189. // Log but disregard errors that occur after stopping
  190. this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll errored after shutdown: " + e_1.message);
  191. }
  192. else {
  193. if (e_1 instanceof TimeoutError) {
  194. // Ignore timeouts and reissue the poll.
  195. this.logger.log(LogLevel.Trace, "(LongPolling transport) Poll timed out, reissuing.");
  196. }
  197. else {
  198. // Close the connection with the error as the result.
  199. this.closeError = e_1;
  200. this.running = false;
  201. }
  202. }
  203. return [3 /*break*/, 6];
  204. case 6: return [3 /*break*/, 1];
  205. case 7: return [3 /*break*/, 9];
  206. case 8:
  207. this.logger.log(LogLevel.Trace, "(LongPolling transport) Polling complete.");
  208. // We will reach here with pollAborted==false when the server returned a response causing the transport to stop.
  209. // If pollAborted==true then client initiated the stop and the stop method will raise the close event after DELETE is sent.
  210. if (!this.pollAborted) {
  211. this.raiseOnClose();
  212. }
  213. return [7 /*endfinally*/];
  214. case 9: return [2 /*return*/];
  215. }
  216. });
  217. });
  218. };
  219. LongPollingTransport.prototype.send = function (data) {
  220. return __awaiter(this, void 0, void 0, function () {
  221. return __generator(this, function (_a) {
  222. if (!this.running) {
  223. return [2 /*return*/, Promise.reject(new Error("Cannot send until the transport is connected"))];
  224. }
  225. return [2 /*return*/, sendMessage(this.logger, "LongPolling", this.httpClient, this.url, this.accessTokenFactory, data, this.logMessageContent)];
  226. });
  227. });
  228. };
  229. LongPollingTransport.prototype.stop = function () {
  230. return __awaiter(this, void 0, void 0, function () {
  231. var deleteOptions, token;
  232. return __generator(this, function (_a) {
  233. switch (_a.label) {
  234. case 0:
  235. this.logger.log(LogLevel.Trace, "(LongPolling transport) Stopping polling.");
  236. // Tell receiving loop to stop, abort any current request, and then wait for it to finish
  237. this.running = false;
  238. this.pollAbort.abort();
  239. _a.label = 1;
  240. case 1:
  241. _a.trys.push([1, , 5, 6]);
  242. return [4 /*yield*/, this.receiving];
  243. case 2:
  244. _a.sent();
  245. // Send DELETE to clean up long polling on the server
  246. this.logger.log(LogLevel.Trace, "(LongPolling transport) sending DELETE request to " + this.url + ".");
  247. deleteOptions = {
  248. headers: {},
  249. };
  250. return [4 /*yield*/, this.getAccessToken()];
  251. case 3:
  252. token = _a.sent();
  253. this.updateHeaderToken(deleteOptions, token);
  254. return [4 /*yield*/, this.httpClient.delete(this.url, deleteOptions)];
  255. case 4:
  256. _a.sent();
  257. this.logger.log(LogLevel.Trace, "(LongPolling transport) DELETE request sent.");
  258. return [3 /*break*/, 6];
  259. case 5:
  260. this.logger.log(LogLevel.Trace, "(LongPolling transport) Stop finished.");
  261. // Raise close event here instead of in polling
  262. // It needs to happen after the DELETE request is sent
  263. this.raiseOnClose();
  264. return [7 /*endfinally*/];
  265. case 6: return [2 /*return*/];
  266. }
  267. });
  268. });
  269. };
  270. LongPollingTransport.prototype.raiseOnClose = function () {
  271. if (this.onclose) {
  272. var logMessage = "(LongPolling transport) Firing onclose event.";
  273. if (this.closeError) {
  274. logMessage += " Error: " + this.closeError;
  275. }
  276. this.logger.log(LogLevel.Trace, logMessage);
  277. this.onclose(this.closeError);
  278. }
  279. };
  280. return LongPollingTransport;
  281. }());
  282. export { LongPollingTransport };
  283. //# sourceMappingURL=LongPollingTransport.js.map