-
Notifications
You must be signed in to change notification settings - Fork 140
/
Copy pathFetchWorkerTool.ts
218 lines (194 loc) · 6.81 KB
/
FetchWorkerTool.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
import {Headers, applyMetadata} from './scratchFetch';
import {ScratchGetRequest, Tool} from './Tool';
interface DeferredJob {
id: string,
resolve: (buffer: ArrayBuffer) => void;
reject: (error: unknown) => void;
}
/**
* Get and send assets with a worker that uses fetch.
*/
class PrivateFetchWorkerTool implements Tool {
private _workerSupport: {fetch: boolean};
private _supportError: unknown;
private worker: Worker | null;
private jobs: Record<string, DeferredJob | undefined>;
constructor () {
/**
* What does the worker support of the APIs we need?
* @type {{fetch:boolean}}
*/
this._workerSupport = {
fetch: typeof fetch !== 'undefined'
};
/**
* A possible error occurred standing up the worker.
* @type {Error?}
*/
this._supportError = null;
/**
* The worker that runs fetch and returns data for us.
* @type {Worker?}
*/
this.worker = null;
/**
* A map of ids to fetch job objects.
* @type {object}
*/
this.jobs = {};
try {
if (this.isGetSupported) {
// Yes, this is a browser API and we've specified `browser: false` in the eslint env,
// but `isGetSupported` checks for the presence of Worker and uses it only if present.
// Also see https://webpack.js.org/guides/web-workers/
// eslint-disable-next-line no-undef
const worker = new Worker(
/* webpackChunkName: "fetch-worker" */ new URL('./FetchWorkerTool.worker', import.meta.url)
);
worker.addEventListener('message', ({data}) => {
if (data.support) {
this._workerSupport = data.support;
return;
}
for (const message of data) {
const job = this.jobs[message.id];
if (job) {
if (message.error) {
job.reject(message.error);
} else {
job.resolve(message.buffer);
}
delete this.jobs[message.id];
}
}
});
this.worker = worker;
}
} catch (error) {
this._supportError = error;
}
}
/**
* Is get supported?
*
* false if the environment does not workers, fetch, or fetch from inside a
* worker. Finding out the worker supports fetch is asynchronous and will
* guess that it does if the window does until the worker can inform us.
* @returns {boolean} Is get supported?
*/
get isGetSupported (): boolean {
return (
typeof Worker !== 'undefined' &&
this._workerSupport.fetch &&
!this._supportError
);
}
/**
* Request data from a server with a worker using fetch.
* @param {{url:string}} reqConfig - Request configuration for data to get.
* @param {{method:string}} options - Additional options to configure fetch.
* @returns {Promise.<Buffer|Uint8Array|null>} Resolve to Buffer of data from server.
*/
get ({url, ...options}: ScratchGetRequest): Promise<Uint8Array | null> {
const worker = this.worker;
if (!worker) {
return Promise.reject(new Error('The worker could not be initialized'));
}
return new Promise<ArrayBuffer>((resolve, reject) => {
// TODO: Use a Scratch standard ID generator ...
const id = Math.random().toString(16)
.substring(2);
const augmentedOptions = applyMetadata(
Object.assign({method: 'GET'}, options)
);
// the Fetch spec says options.headers could be:
// "A Headers object, an object literal, or an array of two-item arrays to set request's headers."
// structured clone (postMessage) doesn't support Headers objects
// so turn it into an array of two-item arrays to make it to the worker intact
if (augmentedOptions && augmentedOptions.headers instanceof Headers) {
augmentedOptions.headers = Array.from(augmentedOptions.headers.entries());
}
worker.postMessage({
id,
url,
options: augmentedOptions
});
this.jobs[id] = {
id,
resolve,
reject
};
})
/* eslint no-confusing-arrow: ["error", {"allowParens": true}] */
.then(body => (body ? new Uint8Array(body) : null));
}
/**
* Is sending supported? always false for FetchWorkerTool.
* @returns {boolean} Is sending supported?
*/
get isSendSupported (): boolean {
return false;
}
/**
* Send data to a server.
* @throws {Error} A not implemented error.
*/
send (): never {
throw new Error('Not implemented.');
}
private static _instance?: PrivateFetchWorkerTool;
/**
* Return a static PrivateFetchWorkerTool instance on demand.
* @returns {PrivateFetchWorkerTool} A static PrivateFetchWorkerTool
* instance
*/
static get instance () {
if (!this._instance) {
this._instance = new PrivateFetchWorkerTool();
}
return this._instance;
}
}
/**
* Get and send assets with a worker that uses fetch.
*/
export default class PublicFetchWorkerTool {
private inner: PrivateFetchWorkerTool;
constructor () {
/**
* Shared instance of an internal worker. PublicFetchWorkerTool proxies
* it.
* @type {PrivateFetchWorkerTool}
*/
this.inner = PrivateFetchWorkerTool.instance;
}
/**
* Is get supported?
* @returns {boolean} Is get supported?
*/
get isGetSupported (): boolean {
return this.inner.isGetSupported;
}
/**
* Request data from a server with a worker that uses fetch.
* @param {{url:string}} reqConfig - Request configuration for data to get.
* @returns {Promise.<Buffer|Uint8Array|null>} Resolve to Buffer of data from server.
*/
get (reqConfig: ScratchGetRequest): Promise<Uint8Array | null> {
return this.inner.get(reqConfig);
}
/**
* Is sending supported?
* @returns {boolean} Is sending supported?
*/
get isSendSupported (): boolean {
return false;
}
/**
* Send data to a server with a worker that uses fetch.
* @throws {Error} A not implemented error.
*/
send (): never {
throw new Error('Not implemented.');
}
}