|
6 | 6 | import { bufferToStream, VSBuffer } from 'vs/base/common/buffer'; |
7 | 7 | import { CancellationToken } from 'vs/base/common/cancellation'; |
8 | 8 | import { canceled } from 'vs/base/common/errors'; |
9 | | -import { IRequestContext, IRequestOptions, OfflineError } from 'vs/base/parts/request/common/request'; |
| 9 | +import { IHeaders, IRequestContext, IRequestOptions, OfflineError } from 'vs/base/parts/request/common/request'; |
10 | 10 |
|
11 | | -export function request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> { |
12 | | - if (options.proxyAuthorization) { |
13 | | - options.headers = { |
14 | | - ...(options.headers || {}), |
15 | | - 'Proxy-Authorization': options.proxyAuthorization |
16 | | - }; |
| 11 | +export async function request(options: IRequestOptions, token: CancellationToken): Promise<IRequestContext> { |
| 12 | + if (token.isCancellationRequested) { |
| 13 | + throw canceled(); |
17 | 14 | } |
18 | 15 |
|
19 | | - const xhr = new XMLHttpRequest(); |
20 | | - return new Promise<IRequestContext>((resolve, reject) => { |
21 | | - |
22 | | - xhr.open(options.type || 'GET', options.url || '', true, options.user, options.password); |
23 | | - setRequestHeaders(xhr, options); |
| 16 | + const cancellation = new AbortController(); |
| 17 | + const disposable = token.onCancellationRequested(() => cancellation.abort()); |
| 18 | + const signal = options.timeout ? AbortSignal.any([ |
| 19 | + cancellation.signal, |
| 20 | + AbortSignal.timeout(options.timeout), |
| 21 | + ]) : cancellation.signal; |
24 | 22 |
|
25 | | - xhr.responseType = 'arraybuffer'; |
26 | | - xhr.onerror = e => reject( |
27 | | - navigator.onLine ? new Error(xhr.statusText && ('XHR failed: ' + xhr.statusText) || 'XHR failed') : new OfflineError() |
28 | | - ); |
29 | | - xhr.onload = (e) => { |
30 | | - resolve({ |
31 | | - res: { |
32 | | - statusCode: xhr.status, |
33 | | - headers: getResponseHeaders(xhr) |
34 | | - }, |
35 | | - stream: bufferToStream(VSBuffer.wrap(new Uint8Array(xhr.response))) |
36 | | - }); |
| 23 | + try { |
| 24 | + const res = await fetch(options.url || '', { |
| 25 | + method: options.type || 'GET', |
| 26 | + headers: getRequestHeaders(options), |
| 27 | + body: options.data, |
| 28 | + signal, |
| 29 | + }); |
| 30 | + return { |
| 31 | + res: { |
| 32 | + statusCode: res.status, |
| 33 | + headers: getResponseHeaders(res), |
| 34 | + }, |
| 35 | + stream: bufferToStream(VSBuffer.wrap(new Uint8Array(await res.arrayBuffer()))), |
37 | 36 | }; |
38 | | - xhr.ontimeout = e => reject(new Error(`XHR timeout: ${options.timeout}ms`)); |
39 | | - |
40 | | - if (options.timeout) { |
41 | | - xhr.timeout = options.timeout; |
| 37 | + } catch (err) { |
| 38 | + if (!navigator.onLine) { |
| 39 | + throw new OfflineError(); |
42 | 40 | } |
43 | | - |
44 | | - xhr.send(options.data); |
45 | | - |
46 | | - // cancel |
47 | | - token.onCancellationRequested(() => { |
48 | | - xhr.abort(); |
49 | | - reject(canceled()); |
50 | | - }); |
51 | | - }); |
| 41 | + if (err?.name === 'AbortError') { |
| 42 | + throw canceled(); |
| 43 | + } |
| 44 | + if (err?.name === 'TimeoutError') { |
| 45 | + throw new Error(`Fetch timeout: ${options.timeout}ms`); |
| 46 | + } |
| 47 | + throw err; |
| 48 | + } finally { |
| 49 | + disposable.dispose(); |
| 50 | + } |
52 | 51 | } |
53 | 52 |
|
54 | | -function setRequestHeaders(xhr: XMLHttpRequest, options: IRequestOptions): void { |
55 | | - if (options.headers) { |
| 53 | +function getRequestHeaders(options: IRequestOptions) { |
| 54 | + if (options.headers || options.user || options.password || options.proxyAuthorization) { |
| 55 | + const headers: HeadersInit = new Headers(); |
56 | 56 | outer: for (const k in options.headers) { |
57 | | - switch (k) { |
58 | | - case 'User-Agent': |
59 | | - case 'Accept-Encoding': |
60 | | - case 'Content-Length': |
| 57 | + switch (k.toLowerCase()) { |
| 58 | + case 'user-agent': |
| 59 | + case 'accept-encoding': |
| 60 | + case 'content-length': |
61 | 61 | // unsafe headers |
62 | 62 | continue outer; |
63 | 63 | } |
64 | 64 | const header = options.headers[k]; |
65 | 65 | if (typeof header === 'string') { |
66 | | - xhr.setRequestHeader(k, header); |
| 66 | + headers.set(k, header); |
67 | 67 | } else if (Array.isArray(header)) { |
68 | 68 | for (const h of header) { |
69 | | - xhr.setRequestHeader(k, h); |
| 69 | + headers.append(k, h); |
70 | 70 | } |
71 | 71 | } |
72 | 72 | } |
| 73 | + if (options.user || options.password) { |
| 74 | + headers.set('Authorization', 'Basic ' + btoa(`${options.user || ''}:${options.password || ''}`)); |
| 75 | + } |
| 76 | + if (options.proxyAuthorization) { |
| 77 | + headers.set('Proxy-Authorization', options.proxyAuthorization); |
| 78 | + } |
| 79 | + return headers; |
73 | 80 | } |
| 81 | + return undefined; |
74 | 82 | } |
75 | 83 |
|
76 | | -function getResponseHeaders(xhr: XMLHttpRequest): { [name: string]: string } { |
77 | | - const headers: { [name: string]: string } = Object.create(null); |
78 | | - for (const line of xhr.getAllResponseHeaders().split(/\r\n|\n|\r/g)) { |
79 | | - if (line) { |
80 | | - const idx = line.indexOf(':'); |
81 | | - headers[line.substr(0, idx).trim().toLowerCase()] = line.substr(idx + 1).trim(); |
| 84 | +function getResponseHeaders(res: Response): IHeaders { |
| 85 | + const headers: IHeaders = Object.create(null); |
| 86 | + res.headers.forEach((value, key) => { |
| 87 | + if (headers[key]) { |
| 88 | + if (Array.isArray(headers[key])) { |
| 89 | + headers[key].push(value); |
| 90 | + } else { |
| 91 | + headers[key] = [headers[key], value]; |
| 92 | + } |
| 93 | + } else { |
| 94 | + headers[key] = value; |
82 | 95 | } |
83 | | - } |
| 96 | + }); |
84 | 97 | return headers; |
85 | 98 | } |
0 commit comments