Utils.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  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 { NullLogger } from "./Loggers";
  5. // Version token that will be replaced by the prepack command
  6. /** The version of the SignalR client. */
  7. export const VERSION = "7.0.14";
  8. /** @private */
  9. export class Arg {
  10. static isRequired(val, name) {
  11. if (val === null || val === undefined) {
  12. throw new Error(`The '${name}' argument is required.`);
  13. }
  14. }
  15. static isNotEmpty(val, name) {
  16. if (!val || val.match(/^\s*$/)) {
  17. throw new Error(`The '${name}' argument should not be empty.`);
  18. }
  19. }
  20. static isIn(val, values, name) {
  21. // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself.
  22. if (!(val in values)) {
  23. throw new Error(`Unknown ${name} value: ${val}.`);
  24. }
  25. }
  26. }
  27. /** @private */
  28. export class Platform {
  29. // react-native has a window but no document so we should check both
  30. static get isBrowser() {
  31. return typeof window === "object" && typeof window.document === "object";
  32. }
  33. // WebWorkers don't have a window object so the isBrowser check would fail
  34. static get isWebWorker() {
  35. return typeof self === "object" && "importScripts" in self;
  36. }
  37. // react-native has a window but no document
  38. static get isReactNative() {
  39. return typeof window === "object" && typeof window.document === "undefined";
  40. }
  41. // Node apps shouldn't have a window object, but WebWorkers don't either
  42. // so we need to check for both WebWorker and window
  43. static get isNode() {
  44. return !this.isBrowser && !this.isWebWorker && !this.isReactNative;
  45. }
  46. }
  47. /** @private */
  48. export function getDataDetail(data, includeContent) {
  49. let detail = "";
  50. if (isArrayBuffer(data)) {
  51. detail = `Binary data of length ${data.byteLength}`;
  52. if (includeContent) {
  53. detail += `. Content: '${formatArrayBuffer(data)}'`;
  54. }
  55. }
  56. else if (typeof data === "string") {
  57. detail = `String data of length ${data.length}`;
  58. if (includeContent) {
  59. detail += `. Content: '${data}'`;
  60. }
  61. }
  62. return detail;
  63. }
  64. /** @private */
  65. export function formatArrayBuffer(data) {
  66. const view = new Uint8Array(data);
  67. // Uint8Array.map only supports returning another Uint8Array?
  68. let str = "";
  69. view.forEach((num) => {
  70. const pad = num < 16 ? "0" : "";
  71. str += `0x${pad}${num.toString(16)} `;
  72. });
  73. // Trim of trailing space.
  74. return str.substr(0, str.length - 1);
  75. }
  76. // Also in signalr-protocol-msgpack/Utils.ts
  77. /** @private */
  78. export function isArrayBuffer(val) {
  79. return val && typeof ArrayBuffer !== "undefined" &&
  80. (val instanceof ArrayBuffer ||
  81. // Sometimes we get an ArrayBuffer that doesn't satisfy instanceof
  82. (val.constructor && val.constructor.name === "ArrayBuffer"));
  83. }
  84. /** @private */
  85. export async function sendMessage(logger, transportName, httpClient, url, content, options) {
  86. const headers = {};
  87. const [name, value] = getUserAgentHeader();
  88. headers[name] = value;
  89. logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, options.logMessageContent)}.`);
  90. const responseType = isArrayBuffer(content) ? "arraybuffer" : "text";
  91. const response = await httpClient.post(url, {
  92. content,
  93. headers: { ...headers, ...options.headers },
  94. responseType,
  95. timeout: options.timeout,
  96. withCredentials: options.withCredentials,
  97. });
  98. logger.log(LogLevel.Trace, `(${transportName} transport) request complete. Response status: ${response.statusCode}.`);
  99. }
  100. /** @private */
  101. export function createLogger(logger) {
  102. if (logger === undefined) {
  103. return new ConsoleLogger(LogLevel.Information);
  104. }
  105. if (logger === null) {
  106. return NullLogger.instance;
  107. }
  108. if (logger.log !== undefined) {
  109. return logger;
  110. }
  111. return new ConsoleLogger(logger);
  112. }
  113. /** @private */
  114. export class SubjectSubscription {
  115. constructor(subject, observer) {
  116. this._subject = subject;
  117. this._observer = observer;
  118. }
  119. dispose() {
  120. const index = this._subject.observers.indexOf(this._observer);
  121. if (index > -1) {
  122. this._subject.observers.splice(index, 1);
  123. }
  124. if (this._subject.observers.length === 0 && this._subject.cancelCallback) {
  125. this._subject.cancelCallback().catch((_) => { });
  126. }
  127. }
  128. }
  129. /** @private */
  130. export class ConsoleLogger {
  131. constructor(minimumLogLevel) {
  132. this._minLevel = minimumLogLevel;
  133. this.out = console;
  134. }
  135. log(logLevel, message) {
  136. if (logLevel >= this._minLevel) {
  137. const msg = `[${new Date().toISOString()}] ${LogLevel[logLevel]}: ${message}`;
  138. switch (logLevel) {
  139. case LogLevel.Critical:
  140. case LogLevel.Error:
  141. this.out.error(msg);
  142. break;
  143. case LogLevel.Warning:
  144. this.out.warn(msg);
  145. break;
  146. case LogLevel.Information:
  147. this.out.info(msg);
  148. break;
  149. default:
  150. // console.debug only goes to attached debuggers in Node, so we use console.log for Trace and Debug
  151. this.out.log(msg);
  152. break;
  153. }
  154. }
  155. }
  156. }
  157. /** @private */
  158. export function getUserAgentHeader() {
  159. let userAgentHeaderName = "X-SignalR-User-Agent";
  160. if (Platform.isNode) {
  161. userAgentHeaderName = "User-Agent";
  162. }
  163. return [userAgentHeaderName, constructUserAgent(VERSION, getOsName(), getRuntime(), getRuntimeVersion())];
  164. }
  165. /** @private */
  166. export function constructUserAgent(version, os, runtime, runtimeVersion) {
  167. // Microsoft SignalR/[Version] ([Detailed Version]; [Operating System]; [Runtime]; [Runtime Version])
  168. let userAgent = "Microsoft SignalR/";
  169. const majorAndMinor = version.split(".");
  170. userAgent += `${majorAndMinor[0]}.${majorAndMinor[1]}`;
  171. userAgent += ` (${version}; `;
  172. if (os && os !== "") {
  173. userAgent += `${os}; `;
  174. }
  175. else {
  176. userAgent += "Unknown OS; ";
  177. }
  178. userAgent += `${runtime}`;
  179. if (runtimeVersion) {
  180. userAgent += `; ${runtimeVersion}`;
  181. }
  182. else {
  183. userAgent += "; Unknown Runtime Version";
  184. }
  185. userAgent += ")";
  186. return userAgent;
  187. }
  188. // eslint-disable-next-line spaced-comment
  189. /*#__PURE__*/ function getOsName() {
  190. if (Platform.isNode) {
  191. switch (process.platform) {
  192. case "win32":
  193. return "Windows NT";
  194. case "darwin":
  195. return "macOS";
  196. case "linux":
  197. return "Linux";
  198. default:
  199. return process.platform;
  200. }
  201. }
  202. else {
  203. return "";
  204. }
  205. }
  206. // eslint-disable-next-line spaced-comment
  207. /*#__PURE__*/ function getRuntimeVersion() {
  208. if (Platform.isNode) {
  209. return process.versions.node;
  210. }
  211. return undefined;
  212. }
  213. function getRuntime() {
  214. if (Platform.isNode) {
  215. return "NodeJS";
  216. }
  217. else {
  218. return "Browser";
  219. }
  220. }
  221. /** @private */
  222. export function getErrorString(e) {
  223. if (e.stack) {
  224. return e.stack;
  225. }
  226. else if (e.message) {
  227. return e.message;
  228. }
  229. return `${e}`;
  230. }
  231. /** @private */
  232. export function getGlobalThis() {
  233. // globalThis is semi-new and not available in Node until v12
  234. if (typeof globalThis !== "undefined") {
  235. return globalThis;
  236. }
  237. if (typeof self !== "undefined") {
  238. return self;
  239. }
  240. if (typeof window !== "undefined") {
  241. return window;
  242. }
  243. if (typeof global !== "undefined") {
  244. return global;
  245. }
  246. throw new Error("could not find global");
  247. }
  248. //# sourceMappingURL=Utils.js.map