@percy/core 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +94 -0
- package/dist/browser.js +292 -0
- package/dist/config.js +551 -0
- package/dist/discovery.js +118 -0
- package/dist/index.js +5 -0
- package/dist/install.js +156 -0
- package/dist/network.js +298 -0
- package/dist/page.js +264 -0
- package/dist/percy.js +484 -0
- package/dist/queue.js +152 -0
- package/dist/server.js +430 -0
- package/dist/session.js +103 -0
- package/dist/snapshot.js +433 -0
- package/dist/utils.js +127 -0
- package/package.json +10 -10
- package/post-install.js +20 -0
- package/test/helpers/server.js +33 -0
- package/types/index.d.ts +101 -0
package/dist/install.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import url from 'url';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import logger from '@percy/logger';
|
|
6
|
+
import { ProxyHttpsAgent } from '@percy/client/utils'; // Formats a raw byte integer as a string
|
|
7
|
+
|
|
8
|
+
function formatBytes(int) {
|
|
9
|
+
let units = ['kB', 'MB', 'GB'];
|
|
10
|
+
let base = 1024;
|
|
11
|
+
let u = -1;
|
|
12
|
+
if (Math.abs(int) < base) return `${int}B`;
|
|
13
|
+
|
|
14
|
+
while (Math.abs(int) >= base && u++ < 2) int /= base;
|
|
15
|
+
|
|
16
|
+
return `${int.toFixed(1)}${units[u]}`;
|
|
17
|
+
} // Formats milleseconds as "MM:SS"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
function formatTime(ms) {
|
|
21
|
+
let minutes = (ms / 1000 / 60).toString().split('.')[0].padStart(2, '0');
|
|
22
|
+
let seconds = (ms / 1000 % 60).toFixed().padStart(2, '0');
|
|
23
|
+
return `${minutes}:${seconds}`;
|
|
24
|
+
} // Formats progress as ":prefix [:bar] :ratio :percent :eta"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function formatProgress(prefix, total, start, progress) {
|
|
28
|
+
let width = 20;
|
|
29
|
+
let ratio = progress === total ? 1 : Math.min(Math.max(progress / total, 0), 1);
|
|
30
|
+
let percent = Math.floor(ratio * 100).toFixed(0);
|
|
31
|
+
let barLen = Math.round(width * ratio);
|
|
32
|
+
let barContent = Array(Math.max(0, barLen + 1)).join('=') + Array(Math.max(0, width - barLen + 1)).join(' ');
|
|
33
|
+
let elapsed = Date.now() - start;
|
|
34
|
+
let eta = ratio >= 1 ? 0 : elapsed * (total / progress - 1);
|
|
35
|
+
return `${prefix} [${barContent}] ` + `${formatBytes(progress)}/${formatBytes(total)} ` + `${percent}% ${formatTime(eta)}`;
|
|
36
|
+
} // Returns an item from the map keyed by the current platform
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
export function selectByPlatform(map) {
|
|
40
|
+
let {
|
|
41
|
+
platform,
|
|
42
|
+
arch
|
|
43
|
+
} = process;
|
|
44
|
+
if (platform === 'win32' && arch === 'x64') platform = 'win64';
|
|
45
|
+
if (platform === 'darwin' && arch === 'arm64') platform = 'darwinArm';
|
|
46
|
+
return map[platform];
|
|
47
|
+
} // Downloads and extracts an executable from a url into a local directory, returning the full path
|
|
48
|
+
// to the extracted binary. Skips installation if the executable already exists at the binary path.
|
|
49
|
+
|
|
50
|
+
export async function download({
|
|
51
|
+
name,
|
|
52
|
+
revision,
|
|
53
|
+
url,
|
|
54
|
+
extract,
|
|
55
|
+
directory,
|
|
56
|
+
executable
|
|
57
|
+
}) {
|
|
58
|
+
let outdir = path.join(directory, revision);
|
|
59
|
+
let archive = path.join(outdir, decodeURIComponent(url.split('/').pop()));
|
|
60
|
+
let exec = path.join(outdir, executable);
|
|
61
|
+
|
|
62
|
+
if (!fs.existsSync(exec)) {
|
|
63
|
+
let log = logger('core:install');
|
|
64
|
+
let premsg = `Downloading ${name} ${revision}`;
|
|
65
|
+
log.progress(`${premsg}...`);
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// ensure the out directory exists
|
|
69
|
+
await fs.promises.mkdir(outdir, {
|
|
70
|
+
recursive: true
|
|
71
|
+
}); // download the file at the given URL
|
|
72
|
+
|
|
73
|
+
await new Promise((resolve, reject) => https.get(url, {
|
|
74
|
+
agent: new ProxyHttpsAgent() // allow proxied requests
|
|
75
|
+
|
|
76
|
+
}, response => {
|
|
77
|
+
// on failure, resume the response before rejecting
|
|
78
|
+
if (response.statusCode !== 200) {
|
|
79
|
+
response.resume();
|
|
80
|
+
reject(new Error(`Download failed: ${response.statusCode} - ${url}`));
|
|
81
|
+
return;
|
|
82
|
+
} // log progress
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if (log.shouldLog('info') && logger.stdout.isTTY) {
|
|
86
|
+
let total = parseInt(response.headers['content-length'], 10);
|
|
87
|
+
let start, progress;
|
|
88
|
+
response.on('data', chunk => {
|
|
89
|
+
start ?? (start = Date.now());
|
|
90
|
+
progress = (progress ?? 0) + chunk.length;
|
|
91
|
+
log.progress(formatProgress(premsg, total, start, progress));
|
|
92
|
+
});
|
|
93
|
+
} // pipe the response directly to a file
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
response.pipe(fs.createWriteStream(archive).on('finish', resolve).on('error', reject));
|
|
97
|
+
}).on('error', reject)); // extract the downloaded file
|
|
98
|
+
|
|
99
|
+
await extract(archive, outdir); // log success
|
|
100
|
+
|
|
101
|
+
log.info(`Successfully downloaded ${name} ${revision}`);
|
|
102
|
+
} finally {
|
|
103
|
+
// always cleanup the archive
|
|
104
|
+
if (fs.existsSync(archive)) {
|
|
105
|
+
await fs.promises.unlink(archive);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} // return the path to the executable
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
return exec;
|
|
112
|
+
} // Installs a revision of Chromium to a local directory
|
|
113
|
+
|
|
114
|
+
export function chromium({
|
|
115
|
+
// default directory is within @percy/core package root
|
|
116
|
+
directory = path.resolve(url.fileURLToPath(import.meta.url), '../../.local-chromium'),
|
|
117
|
+
// default chromium revision by platform (see chromium.revisions)
|
|
118
|
+
revision = selectByPlatform(chromium.revisions)
|
|
119
|
+
} = {}) {
|
|
120
|
+
let extract = (i, o) => import('extract-zip').then(ex => ex.default(i, {
|
|
121
|
+
dir: o
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
let url = 'https://storage.googleapis.com/chromium-browser-snapshots/' + selectByPlatform({
|
|
125
|
+
linux: `Linux_x64/${revision}/chrome-linux.zip`,
|
|
126
|
+
darwin: `Mac/${revision}/chrome-mac.zip`,
|
|
127
|
+
darwinArm: `Mac_Arm/${revision}/chrome-mac.zip`,
|
|
128
|
+
win64: `Win_x64/${revision}/chrome-win.zip`,
|
|
129
|
+
win32: `Win/${revision}/chrome-win.zip`
|
|
130
|
+
});
|
|
131
|
+
let executable = selectByPlatform({
|
|
132
|
+
linux: path.join('chrome-linux', 'chrome'),
|
|
133
|
+
win64: path.join('chrome-win', 'chrome.exe'),
|
|
134
|
+
win32: path.join('chrome-win', 'chrome.exe'),
|
|
135
|
+
darwin: path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'),
|
|
136
|
+
darwinArm: path.join('chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium')
|
|
137
|
+
});
|
|
138
|
+
return download({
|
|
139
|
+
name: 'Chromium',
|
|
140
|
+
revision,
|
|
141
|
+
url,
|
|
142
|
+
extract,
|
|
143
|
+
directory,
|
|
144
|
+
executable
|
|
145
|
+
});
|
|
146
|
+
} // default chromium revisions corresponds to v92.0.4515.x
|
|
147
|
+
|
|
148
|
+
chromium.revisions = {
|
|
149
|
+
linux: '885264',
|
|
150
|
+
win64: '885282',
|
|
151
|
+
win32: '885263',
|
|
152
|
+
darwin: '885263',
|
|
153
|
+
darwinArm: '885282'
|
|
154
|
+
}; // export the namespace by default
|
|
155
|
+
|
|
156
|
+
export * as default from './install.js';
|
package/dist/network.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import logger from '@percy/logger';
|
|
2
|
+
import { waitFor } from './utils.js';
|
|
3
|
+
import { createRequestHandler, createRequestFinishedHandler, createRequestFailedHandler } from './discovery.js'; // The Interceptor class creates common handlers for dealing with intercepting asset requests
|
|
4
|
+
// for a given page using various devtools protocol events and commands.
|
|
5
|
+
|
|
6
|
+
export class Network {
|
|
7
|
+
static TIMEOUT = 30000;
|
|
8
|
+
log = logger('core:network');
|
|
9
|
+
#pending = new Map();
|
|
10
|
+
#requests = new Map();
|
|
11
|
+
#intercepts = new Map();
|
|
12
|
+
#authentications = new Set();
|
|
13
|
+
|
|
14
|
+
constructor(page, options) {
|
|
15
|
+
this.page = page;
|
|
16
|
+
this.timeout = options.networkIdleTimeout ?? 100;
|
|
17
|
+
this.authorization = options.authorization;
|
|
18
|
+
this.requestHeaders = options.requestHeaders ?? {};
|
|
19
|
+
this.userAgent = options.userAgent ?? // by default, emulate a non-headless browser
|
|
20
|
+
page.session.browser.version.userAgent.replace('Headless', '');
|
|
21
|
+
this.interceptEnabled = !!options.intercept;
|
|
22
|
+
this.meta = options.meta;
|
|
23
|
+
|
|
24
|
+
if (this.interceptEnabled) {
|
|
25
|
+
this.onRequest = createRequestHandler(this, options.intercept);
|
|
26
|
+
this.onRequestFinished = createRequestFinishedHandler(this, options.intercept);
|
|
27
|
+
this.onRequestFailed = createRequestFailedHandler(this, options.intercept);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
watch(session) {
|
|
32
|
+
session.on('Network.requestWillBeSent', this._handleRequestWillBeSent);
|
|
33
|
+
session.on('Network.responseReceived', this._handleResponseReceived.bind(this, session));
|
|
34
|
+
session.on('Network.eventSourceMessageReceived', this._handleEventSourceMessageReceived);
|
|
35
|
+
session.on('Network.loadingFinished', this._handleLoadingFinished);
|
|
36
|
+
session.on('Network.loadingFailed', this._handleLoadingFailed);
|
|
37
|
+
let commands = [session.send('Network.enable'), session.send('Network.setBypassServiceWorker', {
|
|
38
|
+
bypass: true
|
|
39
|
+
}), session.send('Network.setCacheDisabled', {
|
|
40
|
+
cacheDisabled: true
|
|
41
|
+
}), session.send('Network.setUserAgentOverride', {
|
|
42
|
+
userAgent: this.userAgent
|
|
43
|
+
}), session.send('Network.setExtraHTTPHeaders', {
|
|
44
|
+
headers: this.requestHeaders
|
|
45
|
+
})];
|
|
46
|
+
|
|
47
|
+
if (this.interceptEnabled && session.isDocument) {
|
|
48
|
+
session.on('Fetch.requestPaused', this._handleRequestPaused.bind(this, session));
|
|
49
|
+
session.on('Fetch.authRequired', this._handleAuthRequired.bind(this, session));
|
|
50
|
+
commands.push(session.send('Fetch.enable', {
|
|
51
|
+
handleAuthRequests: true,
|
|
52
|
+
patterns: [{
|
|
53
|
+
urlPattern: '*'
|
|
54
|
+
}]
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return Promise.all(commands);
|
|
59
|
+
} // Resolves after the timeout when there are no more in-flight requests.
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async idle(filter = () => true, timeout = this.timeout) {
|
|
63
|
+
let requests = [];
|
|
64
|
+
this.log.debug(`Wait for ${timeout}ms idle`, this.meta);
|
|
65
|
+
await waitFor(() => {
|
|
66
|
+
if (this.page.session.closedReason) {
|
|
67
|
+
throw new Error(`Network error: ${this.page.session.closedReason}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
requests = Array.from(this.#requests.values()).filter(filter);
|
|
71
|
+
return requests.length === 0;
|
|
72
|
+
}, {
|
|
73
|
+
timeout: Network.TIMEOUT,
|
|
74
|
+
idle: timeout
|
|
75
|
+
}).catch(error => {
|
|
76
|
+
// throw a better timeout error
|
|
77
|
+
if (error.message.startsWith('Timeout')) {
|
|
78
|
+
let msg = 'Timed out waiting for network requests to idle.';
|
|
79
|
+
|
|
80
|
+
if (this.log.shouldLog('debug')) {
|
|
81
|
+
msg += `\n\n ${['Active requests:', ...requests.map(r => r.url)].join('\n - ')}\n`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(msg);
|
|
85
|
+
} else {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
} // Called when a request should be removed from various trackers
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
_forgetRequest({
|
|
93
|
+
requestId,
|
|
94
|
+
interceptId
|
|
95
|
+
}, keepPending) {
|
|
96
|
+
this.#requests.delete(requestId);
|
|
97
|
+
this.#authentications.delete(interceptId);
|
|
98
|
+
|
|
99
|
+
if (!keepPending) {
|
|
100
|
+
this.#pending.delete(requestId);
|
|
101
|
+
this.#intercepts.delete(requestId);
|
|
102
|
+
}
|
|
103
|
+
} // Called when a request requires authentication. Responds to the auth request with any
|
|
104
|
+
// provided authorization credentials.
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
_handleAuthRequired = async (session, event) => {
|
|
108
|
+
let {
|
|
109
|
+
username,
|
|
110
|
+
password
|
|
111
|
+
} = this.authorization ?? {};
|
|
112
|
+
let {
|
|
113
|
+
requestId
|
|
114
|
+
} = event;
|
|
115
|
+
let response = 'Default';
|
|
116
|
+
|
|
117
|
+
if (this.#authentications.has(requestId)) {
|
|
118
|
+
response = 'CancelAuth';
|
|
119
|
+
} else if (username || password) {
|
|
120
|
+
response = 'ProvideCredentials';
|
|
121
|
+
this.#authentications.add(requestId);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await session.send('Fetch.continueWithAuth', {
|
|
125
|
+
requestId: event.requestId,
|
|
126
|
+
authChallengeResponse: {
|
|
127
|
+
response,
|
|
128
|
+
username,
|
|
129
|
+
password
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}; // Called when a request is made. The request is paused until it is fulfilled, continued, or
|
|
133
|
+
// aborted. If the request is already pending, handle it; otherwise set it to be intercepted.
|
|
134
|
+
|
|
135
|
+
_handleRequestPaused = async (session, event) => {
|
|
136
|
+
let {
|
|
137
|
+
networkId: requestId,
|
|
138
|
+
requestId: interceptId,
|
|
139
|
+
resourceType
|
|
140
|
+
} = event;
|
|
141
|
+
let pending = this.#pending.get(requestId);
|
|
142
|
+
this.#pending.delete(requestId); // guard against redirects with the same requestId
|
|
143
|
+
|
|
144
|
+
if ((pending === null || pending === void 0 ? void 0 : pending.request.url) === event.request.url && pending.request.method === event.request.method) {
|
|
145
|
+
await this._handleRequest(session, { ...pending,
|
|
146
|
+
resourceType,
|
|
147
|
+
interceptId
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
// track the session that intercepted the request
|
|
151
|
+
this.#intercepts.set(requestId, { ...event,
|
|
152
|
+
session
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}; // Called when a request will be sent. If the request has already been intercepted, handle it;
|
|
156
|
+
// otherwise set it to be pending until it is paused.
|
|
157
|
+
|
|
158
|
+
_handleRequestWillBeSent = async event => {
|
|
159
|
+
let {
|
|
160
|
+
requestId,
|
|
161
|
+
request
|
|
162
|
+
} = event; // do not handle data urls
|
|
163
|
+
|
|
164
|
+
if (request.url.startsWith('data:')) return;
|
|
165
|
+
|
|
166
|
+
if (this.interceptEnabled) {
|
|
167
|
+
let intercept = this.#intercepts.get(requestId);
|
|
168
|
+
this.#pending.set(requestId, event);
|
|
169
|
+
|
|
170
|
+
if (intercept) {
|
|
171
|
+
// handle the request with the session that intercepted it
|
|
172
|
+
let {
|
|
173
|
+
session,
|
|
174
|
+
requestId: interceptId,
|
|
175
|
+
resourceType
|
|
176
|
+
} = intercept;
|
|
177
|
+
await this._handleRequest(session, { ...event,
|
|
178
|
+
resourceType,
|
|
179
|
+
interceptId
|
|
180
|
+
});
|
|
181
|
+
this.#intercepts.delete(requestId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}; // Called when a pending request is paused. Handles associating redirected requests with
|
|
185
|
+
// responses and calls this.onrequest with request info and callbacks to continue, respond,
|
|
186
|
+
// or abort a request. One of the callbacks is required to be called and only one.
|
|
187
|
+
|
|
188
|
+
_handleRequest = async (session, event) => {
|
|
189
|
+
var _this$onRequest;
|
|
190
|
+
|
|
191
|
+
let {
|
|
192
|
+
request,
|
|
193
|
+
requestId,
|
|
194
|
+
interceptId,
|
|
195
|
+
resourceType
|
|
196
|
+
} = event;
|
|
197
|
+
let redirectChain = []; // if handling a redirected request, associate the response and add to its redirect chain
|
|
198
|
+
|
|
199
|
+
if (event.redirectResponse && this.#requests.has(requestId)) {
|
|
200
|
+
let req = this.#requests.get(requestId);
|
|
201
|
+
redirectChain = [...req.redirectChain, req]; // clean up interim requests
|
|
202
|
+
|
|
203
|
+
this._forgetRequest(req, true);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
request.type = resourceType;
|
|
207
|
+
request.requestId = requestId;
|
|
208
|
+
request.interceptId = interceptId;
|
|
209
|
+
request.redirectChain = redirectChain;
|
|
210
|
+
this.#requests.set(requestId, request);
|
|
211
|
+
await ((_this$onRequest = this.onRequest) === null || _this$onRequest === void 0 ? void 0 : _this$onRequest.call(this, { ...request,
|
|
212
|
+
// call to continue the request as-is
|
|
213
|
+
continue: () => session.send('Fetch.continueRequest', {
|
|
214
|
+
requestId: interceptId
|
|
215
|
+
}),
|
|
216
|
+
// call to respond with a specific status, content, and headers
|
|
217
|
+
respond: ({
|
|
218
|
+
status,
|
|
219
|
+
content,
|
|
220
|
+
headers
|
|
221
|
+
}) => session.send('Fetch.fulfillRequest', {
|
|
222
|
+
requestId: interceptId,
|
|
223
|
+
responseCode: status || 200,
|
|
224
|
+
body: Buffer.from(content).toString('base64'),
|
|
225
|
+
responseHeaders: Object.entries(headers || {}).map(([name, value]) => {
|
|
226
|
+
return {
|
|
227
|
+
name: name.toLowerCase(),
|
|
228
|
+
value: String(value)
|
|
229
|
+
};
|
|
230
|
+
})
|
|
231
|
+
}),
|
|
232
|
+
// call to fail or abort the request
|
|
233
|
+
abort: error => session.send('Fetch.failRequest', {
|
|
234
|
+
requestId: interceptId,
|
|
235
|
+
// istanbul note: this check used to be necessary and might be again in the future if we
|
|
236
|
+
// ever need to abort a request due to reasons other than failures
|
|
237
|
+
errorReason: error ? 'Failed' :
|
|
238
|
+
/* istanbul ignore next */
|
|
239
|
+
'Aborted'
|
|
240
|
+
})
|
|
241
|
+
}));
|
|
242
|
+
}; // Called when a response has been received for a specific request. Associates the response with
|
|
243
|
+
// the request data and adds a buffer method to fetch the response body when needed.
|
|
244
|
+
|
|
245
|
+
_handleResponseReceived = (session, event) => {
|
|
246
|
+
let {
|
|
247
|
+
requestId,
|
|
248
|
+
response
|
|
249
|
+
} = event;
|
|
250
|
+
let request = this.#requests.get(requestId);
|
|
251
|
+
/* istanbul ignore if: race condition paranioa */
|
|
252
|
+
|
|
253
|
+
if (!request) return;
|
|
254
|
+
request.response = response;
|
|
255
|
+
|
|
256
|
+
request.response.buffer = async () => {
|
|
257
|
+
let result = await session.send('Network.getResponseBody', {
|
|
258
|
+
requestId
|
|
259
|
+
});
|
|
260
|
+
return Buffer.from(result.body, result.base64Encoded ? 'base64' : 'utf-8');
|
|
261
|
+
};
|
|
262
|
+
}; // Called when a request streams events. These types of requests break asset discovery because
|
|
263
|
+
// they never finish loading, so we untrack them to signal idle after the first event.
|
|
264
|
+
|
|
265
|
+
_handleEventSourceMessageReceived = event => {
|
|
266
|
+
let request = this.#requests.get(event.requestId);
|
|
267
|
+
/* istanbul ignore else: race condition paranioa */
|
|
268
|
+
|
|
269
|
+
if (request) this._forgetRequest(request);
|
|
270
|
+
}; // Called when a request has finished loading which triggers the this.onrequestfinished
|
|
271
|
+
// callback. The request should have an associated response and be finished with any redirects.
|
|
272
|
+
|
|
273
|
+
_handleLoadingFinished = async event => {
|
|
274
|
+
var _this$onRequestFinish;
|
|
275
|
+
|
|
276
|
+
let request = this.#requests.get(event.requestId);
|
|
277
|
+
/* istanbul ignore if: race condition paranioa */
|
|
278
|
+
|
|
279
|
+
if (!request) return;
|
|
280
|
+
await ((_this$onRequestFinish = this.onRequestFinished) === null || _this$onRequestFinish === void 0 ? void 0 : _this$onRequestFinish.call(this, request));
|
|
281
|
+
|
|
282
|
+
this._forgetRequest(request);
|
|
283
|
+
}; // Called when a request has failed loading and triggers the this.onrequestfailed callback.
|
|
284
|
+
|
|
285
|
+
_handleLoadingFailed = async event => {
|
|
286
|
+
var _this$onRequestFailed;
|
|
287
|
+
|
|
288
|
+
let request = this.#requests.get(event.requestId);
|
|
289
|
+
/* istanbul ignore if: race condition paranioa */
|
|
290
|
+
|
|
291
|
+
if (!request) return;
|
|
292
|
+
request.error = event.errorText;
|
|
293
|
+
await ((_this$onRequestFailed = this.onRequestFailed) === null || _this$onRequestFailed === void 0 ? void 0 : _this$onRequestFailed.call(this, request));
|
|
294
|
+
|
|
295
|
+
this._forgetRequest(request);
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
export default Network;
|