Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
3.9 KiB
Text
140 lines
3.9 KiB
Text
/**
|
|
* @license Copyright 2020 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import * as LH from '../../types/lh.js';
|
|
|
|
/**
|
|
* @fileoverview Fetcher is a utility for making requests to any arbitrary resource,
|
|
* ignoring normal browser constraints such as CORS.
|
|
*/
|
|
|
|
/** @typedef {{content: string|null, status: number|null}} FetchResponse */
|
|
|
|
class Fetcher {
|
|
/**
|
|
* @param {LH.Gatherer.ProtocolSession} session
|
|
*/
|
|
constructor(session) {
|
|
this.session = session;
|
|
}
|
|
|
|
/**
|
|
* Fetches any resource using the network directly.
|
|
*
|
|
* @param {string} url
|
|
* @param {{timeout: number}=} options timeout is in ms
|
|
* @return {Promise<FetchResponse>}
|
|
*/
|
|
async fetchResource(url, options = {timeout: 2_000}) {
|
|
// In Lightrider, `Network.loadNetworkResource` is not implemented, but fetch
|
|
// is configured to work for any resource.
|
|
if (global.isLightrider) {
|
|
return this._wrapWithTimeout(this._fetchWithFetchApi(url), options.timeout);
|
|
}
|
|
|
|
return this._fetchResourceOverProtocol(url, options);
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @return {Promise<FetchResponse>}
|
|
*/
|
|
async _fetchWithFetchApi(url) {
|
|
const response = await fetch(url);
|
|
|
|
let content = null;
|
|
try {
|
|
content = await response.text();
|
|
} catch {}
|
|
|
|
return {
|
|
content,
|
|
status: response.status,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} handle
|
|
* @param {{timeout: number}=} options,
|
|
* @return {Promise<string>}
|
|
*/
|
|
async _readIOStream(handle, options = {timeout: 2_000}) {
|
|
const startTime = Date.now();
|
|
|
|
let ioResponse;
|
|
let data = '';
|
|
while (!ioResponse || !ioResponse.eof) {
|
|
const elapsedTime = Date.now() - startTime;
|
|
if (elapsedTime > options.timeout) {
|
|
throw new Error('Waiting for the end of the IO stream exceeded the allotted time.');
|
|
}
|
|
ioResponse = await this.session.sendCommand('IO.read', {handle});
|
|
const responseData = ioResponse.base64Encoded ?
|
|
Buffer.from(ioResponse.data, 'base64').toString('utf-8') :
|
|
ioResponse.data;
|
|
data = data.concat(responseData);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @return {Promise<{stream: LH.Crdp.IO.StreamHandle|null, status: number|null}>}
|
|
*/
|
|
async _loadNetworkResource(url) {
|
|
const frameTreeResponse = await this.session.sendCommand('Page.getFrameTree');
|
|
const networkResponse = await this.session.sendCommand('Network.loadNetworkResource', {
|
|
frameId: frameTreeResponse.frameTree.frame.id,
|
|
url,
|
|
options: {
|
|
disableCache: true,
|
|
includeCredentials: true,
|
|
},
|
|
});
|
|
|
|
return {
|
|
stream: networkResponse.resource.success ? (networkResponse.resource.stream || null) : null,
|
|
status: networkResponse.resource.httpStatusCode || null,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {string} url
|
|
* @param {{timeout: number}} options timeout is in ms
|
|
* @return {Promise<FetchResponse>}
|
|
*/
|
|
async _fetchResourceOverProtocol(url, options) {
|
|
const startTime = Date.now();
|
|
const response = await this._wrapWithTimeout(this._loadNetworkResource(url), options.timeout);
|
|
|
|
const isOk = response.status && response.status >= 200 && response.status <= 299;
|
|
if (!response.stream || !isOk) return {status: response.status, content: null};
|
|
|
|
const timeout = options.timeout - (Date.now() - startTime);
|
|
const content = await this._readIOStream(response.stream, {timeout});
|
|
return {status: response.status, content};
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
* @param {Promise<T>} promise
|
|
* @param {number} ms
|
|
*/
|
|
async _wrapWithTimeout(promise, ms) {
|
|
/** @type {NodeJS.Timeout} */
|
|
let timeoutHandle;
|
|
const timeoutPromise = new Promise((_, reject) => {
|
|
timeoutHandle = setTimeout(reject, ms, new Error('Timed out fetching resource'));
|
|
});
|
|
|
|
/** @type {Promise<T>} */
|
|
const wrappedPromise = await Promise.race([promise, timeoutPromise])
|
|
.finally(() => clearTimeout(timeoutHandle));
|
|
return wrappedPromise;
|
|
}
|
|
}
|
|
|
|
export {Fetcher};
|