UNPKG

6.23 kBPlain TextView Raw
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
4import { HttpClient } from "./HttpClient";
5import { ILogger, LogLevel } from "./ILogger";
6import { NullLogger } from "./Loggers";
7import { IStreamSubscriber, ISubscription } from "./Stream";
8import { Subject } from "./Subject";
9
10/** @private */
11export class Arg {
12 public static isRequired(val: any, name: string): void {
13 if (val === null || val === undefined) {
14 throw new Error(`The '${name}' argument is required.`);
15 }
16 }
17
18 public static isIn(val: any, values: any, name: string): void {
19 // TypeScript enums have keys for **both** the name and the value of each enum member on the type itself.
20 if (!(val in values)) {
21 throw new Error(`Unknown ${name} value: ${val}.`);
22 }
23 }
24}
25
26/** @private */
27export class Platform {
28
29 public static get isBrowser(): boolean {
30 return typeof window === "object";
31 }
32
33 public static get isWebWorker(): boolean {
34 return typeof self === "object" && "importScripts" in self;
35 }
36
37 public static get isNode(): boolean {
38 return !this.isBrowser && !this.isWebWorker;
39 }
40}
41
42/** @private */
43export function getDataDetail(data: any, includeContent: boolean): string {
44 let detail = "";
45 if (isArrayBuffer(data)) {
46 detail = `Binary data of length ${data.byteLength}`;
47 if (includeContent) {
48 detail += `. Content: '${formatArrayBuffer(data)}'`;
49 }
50 } else if (typeof data === "string") {
51 detail = `String data of length ${data.length}`;
52 if (includeContent) {
53 detail += `. Content: '${data}'`;
54 }
55 }
56 return detail;
57}
58
59/** @private */
60export function formatArrayBuffer(data: ArrayBuffer): string {
61 const view = new Uint8Array(data);
62
63 // Uint8Array.map only supports returning another Uint8Array?
64 let str = "";
65 view.forEach((num) => {
66 const pad = num < 16 ? "0" : "";
67 str += `0x${pad}${num.toString(16)} `;
68 });
69
70 // Trim of trailing space.
71 return str.substr(0, str.length - 1);
72}
73
74// Also in signalr-protocol-msgpack/Utils.ts
75/** @private */
76export function isArrayBuffer(val: any): val is ArrayBuffer {
77 return val && typeof ArrayBuffer !== "undefined" &&
78 (val instanceof ArrayBuffer ||
79 // Sometimes we get an ArrayBuffer that doesn't satisfy instanceof
80 (val.constructor && val.constructor.name === "ArrayBuffer"));
81}
82
83/** @private */
84export async function sendMessage(logger: ILogger, transportName: string, httpClient: HttpClient, url: string, accessTokenFactory: (() => string | Promise<string>) | undefined, content: string | ArrayBuffer, logMessageContent: boolean): Promise<void> {
85 let headers;
86 if (accessTokenFactory) {
87 const token = await accessTokenFactory();
88 if (token) {
89 headers = {
90 ["Authorization"]: `Bearer ${token}`,
91 };
92 }
93 }
94
95 logger.log(LogLevel.Trace, `(${transportName} transport) sending data. ${getDataDetail(content, logMessageContent)}.`);
96
97 const responseType = isArrayBuffer(content) ? "arraybuffer" : "text";
98 const response = await httpClient.post(url, {
99 content,
100 headers,
101 responseType,
102 });
103
104 logger.log(LogLevel.Trace, `(${transportName} transport) request complete. Response status: ${response.statusCode}.`);
105}
106
107/** @private */
108export function createLogger(logger?: ILogger | LogLevel) {
109 if (logger === undefined) {
110 return new ConsoleLogger(LogLevel.Information);
111 }
112
113 if (logger === null) {
114 return NullLogger.instance;
115 }
116
117 if ((logger as ILogger).log) {
118 return logger as ILogger;
119 }
120
121 return new ConsoleLogger(logger as LogLevel);
122}
123
124/** @private */
125export class SubjectSubscription<T> implements ISubscription<T> {
126 private subject: Subject<T>;
127 private observer: IStreamSubscriber<T>;
128
129 constructor(subject: Subject<T>, observer: IStreamSubscriber<T>) {
130 this.subject = subject;
131 this.observer = observer;
132 }
133
134 public dispose(): void {
135 const index: number = this.subject.observers.indexOf(this.observer);
136 if (index > -1) {
137 this.subject.observers.splice(index, 1);
138 }
139
140 if (this.subject.observers.length === 0 && this.subject.cancelCallback) {
141 this.subject.cancelCallback().catch((_) => { });
142 }
143 }
144}
145
146/** @private */
147export class ConsoleLogger implements ILogger {
148 private readonly minimumLogLevel: LogLevel;
149
150 // Public for testing purposes.
151 public outputConsole: {
152 error(message: any): void,
153 warn(message: any): void,
154 info(message: any): void,
155 log(message: any): void,
156 };
157
158 constructor(minimumLogLevel: LogLevel) {
159 this.minimumLogLevel = minimumLogLevel;
160 this.outputConsole = console;
161 }
162
163 public log(logLevel: LogLevel, message: string): void {
164 if (logLevel >= this.minimumLogLevel) {
165 switch (logLevel) {
166 case LogLevel.Critical:
167 case LogLevel.Error:
168 this.outputConsole.error(`[${new Date().toISOString()}] ${LogLevel[logLevel]}: ${message}`);
169 break;
170 case LogLevel.Warning:
171 this.outputConsole.warn(`[${new Date().toISOString()}] ${LogLevel[logLevel]}: ${message}`);
172 break;
173 case LogLevel.Information:
174 this.outputConsole.info(`[${new Date().toISOString()}] ${LogLevel[logLevel]}: ${message}`);
175 break;
176 default:
177 // console.debug only goes to attached debuggers in Node, so we use console.log for Trace and Debug
178 this.outputConsole.log(`[${new Date().toISOString()}] ${LogLevel[logLevel]}: ${message}`);
179 break;
180 }
181 }
182 }
183}