ServerSentEventsTransport.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. // Licensed to the .NET Foundation under one or more agreements.
  2. // The .NET Foundation licenses this file to you under the MIT license.
  3. import { LogLevel } from "./ILogger";
  4. import { TransferFormat } from "./ITransport";
  5. import { Arg, getDataDetail, getUserAgentHeader, Platform, sendMessage } from "./Utils";
  6. /** @private */
  7. export class ServerSentEventsTransport {
  8. constructor(httpClient, accessToken, logger, options) {
  9. this._httpClient = httpClient;
  10. this._accessToken = accessToken;
  11. this._logger = logger;
  12. this._options = options;
  13. this.onreceive = null;
  14. this.onclose = null;
  15. }
  16. async connect(url, transferFormat) {
  17. Arg.isRequired(url, "url");
  18. Arg.isRequired(transferFormat, "transferFormat");
  19. Arg.isIn(transferFormat, TransferFormat, "transferFormat");
  20. this._logger.log(LogLevel.Trace, "(SSE transport) Connecting.");
  21. // set url before accessTokenFactory because this._url is only for send and we set the auth header instead of the query string for send
  22. this._url = url;
  23. if (this._accessToken) {
  24. url += (url.indexOf("?") < 0 ? "?" : "&") + `access_token=${encodeURIComponent(this._accessToken)}`;
  25. }
  26. return new Promise((resolve, reject) => {
  27. let opened = false;
  28. if (transferFormat !== TransferFormat.Text) {
  29. reject(new Error("The Server-Sent Events transport only supports the 'Text' transfer format"));
  30. return;
  31. }
  32. let eventSource;
  33. if (Platform.isBrowser || Platform.isWebWorker) {
  34. eventSource = new this._options.EventSource(url, { withCredentials: this._options.withCredentials });
  35. }
  36. else {
  37. // Non-browser passes cookies via the dictionary
  38. const cookies = this._httpClient.getCookieString(url);
  39. const headers = {};
  40. headers.Cookie = cookies;
  41. const [name, value] = getUserAgentHeader();
  42. headers[name] = value;
  43. eventSource = new this._options.EventSource(url, { withCredentials: this._options.withCredentials, headers: { ...headers, ...this._options.headers } });
  44. }
  45. try {
  46. eventSource.onmessage = (e) => {
  47. if (this.onreceive) {
  48. try {
  49. this._logger.log(LogLevel.Trace, `(SSE transport) data received. ${getDataDetail(e.data, this._options.logMessageContent)}.`);
  50. this.onreceive(e.data);
  51. }
  52. catch (error) {
  53. this._close(error);
  54. return;
  55. }
  56. }
  57. };
  58. // @ts-ignore: not using event on purpose
  59. eventSource.onerror = (e) => {
  60. // EventSource doesn't give any useful information about server side closes.
  61. if (opened) {
  62. this._close();
  63. }
  64. else {
  65. reject(new Error("EventSource failed to connect. The connection could not be found on the server,"
  66. + " either the connection ID is not present on the server, or a proxy is refusing/buffering the connection."
  67. + " If you have multiple servers check that sticky sessions are enabled."));
  68. }
  69. };
  70. eventSource.onopen = () => {
  71. this._logger.log(LogLevel.Information, `SSE connected to ${this._url}`);
  72. this._eventSource = eventSource;
  73. opened = true;
  74. resolve();
  75. };
  76. }
  77. catch (e) {
  78. reject(e);
  79. return;
  80. }
  81. });
  82. }
  83. async send(data) {
  84. if (!this._eventSource) {
  85. return Promise.reject(new Error("Cannot send until the transport is connected"));
  86. }
  87. return sendMessage(this._logger, "SSE", this._httpClient, this._url, data, this._options);
  88. }
  89. stop() {
  90. this._close();
  91. return Promise.resolve();
  92. }
  93. _close(e) {
  94. if (this._eventSource) {
  95. this._eventSource.close();
  96. this._eventSource = undefined;
  97. if (this.onclose) {
  98. this.onclose(e);
  99. }
  100. }
  101. }
  102. }
  103. //# sourceMappingURL=ServerSentEventsTransport.js.map