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>
410 lines
12 KiB
Text
410 lines
12 KiB
Text
/**
|
|
* @license
|
|
* Copyright 2024 Google Inc.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
import * as Bidi from 'webdriver-bidi-protocol';
|
|
|
|
import type {JSHandle} from '../api/JSHandle.js';
|
|
import {Realm} from '../api/Realm.js';
|
|
import {ARIAQueryHandler} from '../common/AriaQueryHandler.js';
|
|
import {LazyArg} from '../common/LazyArg.js';
|
|
import {scriptInjector} from '../common/ScriptInjector.js';
|
|
import type {TimeoutSettings} from '../common/TimeoutSettings.js';
|
|
import type {EvaluateFunc, HandleFor} from '../common/types.js';
|
|
import {
|
|
debugError,
|
|
getSourcePuppeteerURLIfAvailable,
|
|
getSourceUrlComment,
|
|
isString,
|
|
PuppeteerURL,
|
|
SOURCE_URL_REGEX,
|
|
} from '../common/util.js';
|
|
import type {PuppeteerInjectedUtil} from '../injected/injected.js';
|
|
import {AsyncIterableUtil} from '../util/AsyncIterableUtil.js';
|
|
import {stringifyFunction} from '../util/Function.js';
|
|
|
|
import type {
|
|
Realm as BidiRealmCore,
|
|
DedicatedWorkerRealm,
|
|
SharedWorkerRealm,
|
|
} from './core/Realm.js';
|
|
import type {WindowRealm} from './core/Realm.js';
|
|
import {BidiDeserializer} from './Deserializer.js';
|
|
import {BidiElementHandle} from './ElementHandle.js';
|
|
import {ExposableFunction} from './ExposedFunction.js';
|
|
import type {BidiFrame} from './Frame.js';
|
|
import {BidiJSHandle} from './JSHandle.js';
|
|
import {BidiSerializer} from './Serializer.js';
|
|
import {createEvaluationError} from './util.js';
|
|
import type {BidiWebWorker} from './WebWorker.js';
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
export abstract class BidiRealm extends Realm {
|
|
readonly realm: BidiRealmCore;
|
|
|
|
constructor(realm: BidiRealmCore, timeoutSettings: TimeoutSettings) {
|
|
super(timeoutSettings);
|
|
this.realm = realm;
|
|
}
|
|
|
|
protected initialize(): void {
|
|
this.realm.on('destroyed', ({reason}) => {
|
|
this.taskManager.terminateAll(new Error(reason));
|
|
this.dispose();
|
|
});
|
|
this.realm.on('updated', () => {
|
|
this.internalPuppeteerUtil = undefined;
|
|
void this.taskManager.rerunAll();
|
|
});
|
|
}
|
|
|
|
protected internalPuppeteerUtil?: Promise<
|
|
BidiJSHandle<PuppeteerInjectedUtil>
|
|
>;
|
|
get puppeteerUtil(): Promise<BidiJSHandle<PuppeteerInjectedUtil>> {
|
|
const promise = Promise.resolve() as Promise<unknown>;
|
|
scriptInjector.inject(script => {
|
|
if (this.internalPuppeteerUtil) {
|
|
void this.internalPuppeteerUtil.then(handle => {
|
|
void handle.dispose();
|
|
});
|
|
}
|
|
this.internalPuppeteerUtil = promise.then(() => {
|
|
return this.evaluateHandle(script) as Promise<
|
|
BidiJSHandle<PuppeteerInjectedUtil>
|
|
>;
|
|
});
|
|
}, !this.internalPuppeteerUtil);
|
|
return this.internalPuppeteerUtil as Promise<
|
|
BidiJSHandle<PuppeteerInjectedUtil>
|
|
>;
|
|
}
|
|
|
|
override async evaluateHandle<
|
|
Params extends unknown[],
|
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
>(
|
|
pageFunction: Func | string,
|
|
...args: Params
|
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>> {
|
|
return await this.#evaluate(false, pageFunction, ...args);
|
|
}
|
|
|
|
override async evaluate<
|
|
Params extends unknown[],
|
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
>(
|
|
pageFunction: Func | string,
|
|
...args: Params
|
|
): Promise<Awaited<ReturnType<Func>>> {
|
|
return await this.#evaluate(true, pageFunction, ...args);
|
|
}
|
|
|
|
async #evaluate<
|
|
Params extends unknown[],
|
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
>(
|
|
returnByValue: true,
|
|
pageFunction: Func | string,
|
|
...args: Params
|
|
): Promise<Awaited<ReturnType<Func>>>;
|
|
async #evaluate<
|
|
Params extends unknown[],
|
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
>(
|
|
returnByValue: false,
|
|
pageFunction: Func | string,
|
|
...args: Params
|
|
): Promise<HandleFor<Awaited<ReturnType<Func>>>>;
|
|
async #evaluate<
|
|
Params extends unknown[],
|
|
Func extends EvaluateFunc<Params> = EvaluateFunc<Params>,
|
|
>(
|
|
returnByValue: boolean,
|
|
pageFunction: Func | string,
|
|
...args: Params
|
|
): Promise<HandleFor<Awaited<ReturnType<Func>>> | Awaited<ReturnType<Func>>> {
|
|
const sourceUrlComment = getSourceUrlComment(
|
|
getSourcePuppeteerURLIfAvailable(pageFunction)?.toString() ??
|
|
PuppeteerURL.INTERNAL_URL,
|
|
);
|
|
|
|
let responsePromise;
|
|
const resultOwnership = returnByValue
|
|
? Bidi.Script.ResultOwnership.None
|
|
: Bidi.Script.ResultOwnership.Root;
|
|
const serializationOptions: Bidi.Script.SerializationOptions = returnByValue
|
|
? {}
|
|
: {
|
|
maxObjectDepth: 0,
|
|
maxDomDepth: 0,
|
|
};
|
|
if (isString(pageFunction)) {
|
|
const expression = SOURCE_URL_REGEX.test(pageFunction)
|
|
? pageFunction
|
|
: `${pageFunction}\n${sourceUrlComment}\n`;
|
|
|
|
responsePromise = this.realm.evaluate(expression, true, {
|
|
resultOwnership,
|
|
userActivation: true,
|
|
serializationOptions,
|
|
});
|
|
} else {
|
|
let functionDeclaration = stringifyFunction(pageFunction);
|
|
functionDeclaration = SOURCE_URL_REGEX.test(functionDeclaration)
|
|
? functionDeclaration
|
|
: `${functionDeclaration}\n${sourceUrlComment}\n`;
|
|
responsePromise = this.realm.callFunction(
|
|
functionDeclaration,
|
|
/* awaitPromise= */ true,
|
|
{
|
|
// LazyArgs are used only internally and should not affect the order
|
|
// evaluate calls for the public APIs.
|
|
arguments: args.some(arg => {
|
|
return arg instanceof LazyArg;
|
|
})
|
|
? await Promise.all(
|
|
args.map(arg => {
|
|
return this.serializeAsync(arg);
|
|
}),
|
|
)
|
|
: args.map(arg => {
|
|
return this.serialize(arg);
|
|
}),
|
|
resultOwnership,
|
|
userActivation: true,
|
|
serializationOptions,
|
|
},
|
|
);
|
|
}
|
|
|
|
const result = await responsePromise;
|
|
|
|
if ('type' in result && result.type === 'exception') {
|
|
throw createEvaluationError(result.exceptionDetails);
|
|
}
|
|
|
|
if (returnByValue) {
|
|
return BidiDeserializer.deserialize(result.result);
|
|
}
|
|
|
|
return this.createHandle(result.result) as unknown as HandleFor<
|
|
Awaited<ReturnType<Func>>
|
|
>;
|
|
}
|
|
|
|
createHandle(
|
|
result: Bidi.Script.RemoteValue,
|
|
): BidiJSHandle<unknown> | BidiElementHandle<Node> {
|
|
if (
|
|
(result.type === 'node' || result.type === 'window') &&
|
|
this instanceof BidiFrameRealm
|
|
) {
|
|
return BidiElementHandle.from(result, this);
|
|
}
|
|
return BidiJSHandle.from(result, this);
|
|
}
|
|
|
|
async serializeAsync(arg: unknown): Promise<Bidi.Script.LocalValue> {
|
|
if (arg instanceof LazyArg) {
|
|
arg = await arg.get(this);
|
|
}
|
|
return this.serialize(arg);
|
|
}
|
|
|
|
serialize(arg: unknown): Bidi.Script.LocalValue {
|
|
if (arg instanceof BidiJSHandle || arg instanceof BidiElementHandle) {
|
|
if (arg.realm !== this) {
|
|
if (
|
|
!(arg.realm instanceof BidiFrameRealm) ||
|
|
!(this instanceof BidiFrameRealm)
|
|
) {
|
|
throw new Error(
|
|
"Trying to evaluate JSHandle from different global types. Usually this means you're using a handle from a worker in a page or vice versa.",
|
|
);
|
|
}
|
|
if (arg.realm.environment !== this.environment) {
|
|
throw new Error(
|
|
"Trying to evaluate JSHandle from different frames. Usually this means you're using a handle from a page on a different page.",
|
|
);
|
|
}
|
|
}
|
|
if (arg.disposed) {
|
|
throw new Error('JSHandle is disposed!');
|
|
}
|
|
return arg.remoteValue() as Bidi.Script.RemoteReference;
|
|
}
|
|
|
|
return BidiSerializer.serialize(arg);
|
|
}
|
|
|
|
async destroyHandles(handles: Array<BidiJSHandle<unknown>>): Promise<void> {
|
|
if (this.disposed) {
|
|
return;
|
|
}
|
|
|
|
const handleIds = handles
|
|
.map(({id}) => {
|
|
return id;
|
|
})
|
|
.filter((id): id is string => {
|
|
return id !== undefined;
|
|
});
|
|
|
|
if (handleIds.length === 0) {
|
|
return;
|
|
}
|
|
|
|
await this.realm.disown(handleIds).catch(error => {
|
|
// Exceptions might happen in case of a page been navigated or closed.
|
|
// Swallow these since they are harmless and we don't leak anything in this case.
|
|
debugError(error);
|
|
});
|
|
}
|
|
|
|
override async adoptHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
|
return (await this.evaluateHandle(node => {
|
|
return node;
|
|
}, handle)) as unknown as T;
|
|
}
|
|
|
|
override async transferHandle<T extends JSHandle<Node>>(
|
|
handle: T,
|
|
): Promise<T> {
|
|
if (handle.realm === this) {
|
|
return handle;
|
|
}
|
|
const transferredHandle = this.adoptHandle(handle);
|
|
await handle.dispose();
|
|
return await transferredHandle;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
export class BidiFrameRealm extends BidiRealm {
|
|
static from(realm: WindowRealm, frame: BidiFrame): BidiFrameRealm {
|
|
const frameRealm = new BidiFrameRealm(realm, frame);
|
|
frameRealm.#initialize();
|
|
return frameRealm;
|
|
}
|
|
declare readonly realm: WindowRealm;
|
|
|
|
readonly #frame: BidiFrame;
|
|
|
|
private constructor(realm: WindowRealm, frame: BidiFrame) {
|
|
super(realm, frame.timeoutSettings);
|
|
this.#frame = frame;
|
|
}
|
|
|
|
#initialize() {
|
|
super.initialize();
|
|
|
|
// This should run first.
|
|
this.realm.on('updated', () => {
|
|
this.environment.clearDocumentHandle();
|
|
this.#bindingsInstalled = false;
|
|
});
|
|
}
|
|
|
|
#bindingsInstalled = false;
|
|
override get puppeteerUtil(): Promise<BidiJSHandle<PuppeteerInjectedUtil>> {
|
|
let promise = Promise.resolve() as Promise<unknown>;
|
|
if (!this.#bindingsInstalled) {
|
|
promise = Promise.all([
|
|
ExposableFunction.from(
|
|
this.environment,
|
|
'__ariaQuerySelector',
|
|
ARIAQueryHandler.queryOne,
|
|
!!this.sandbox,
|
|
),
|
|
ExposableFunction.from(
|
|
this.environment,
|
|
'__ariaQuerySelectorAll',
|
|
async (
|
|
element: BidiElementHandle<Node>,
|
|
selector: string,
|
|
): Promise<JSHandle<Node[]>> => {
|
|
const results = ARIAQueryHandler.queryAll(element, selector);
|
|
return await element.realm.evaluateHandle(
|
|
(...elements) => {
|
|
return elements;
|
|
},
|
|
...(await AsyncIterableUtil.collect(results)),
|
|
);
|
|
},
|
|
!!this.sandbox,
|
|
),
|
|
]);
|
|
this.#bindingsInstalled = true;
|
|
}
|
|
return promise.then(() => {
|
|
return super.puppeteerUtil;
|
|
});
|
|
}
|
|
|
|
get sandbox(): string | undefined {
|
|
return this.realm.sandbox;
|
|
}
|
|
|
|
override get environment(): BidiFrame {
|
|
return this.#frame;
|
|
}
|
|
|
|
override async adoptBackendNode(
|
|
backendNodeId?: number | undefined,
|
|
): Promise<JSHandle<Node>> {
|
|
const {object} = await this.#frame.client.send('DOM.resolveNode', {
|
|
backendNodeId,
|
|
executionContextId: await this.realm.resolveExecutionContextId(),
|
|
});
|
|
using handle = BidiElementHandle.from(
|
|
{
|
|
handle: object.objectId,
|
|
type: 'node',
|
|
},
|
|
this,
|
|
);
|
|
// We need the sharedId, so we perform the following to obtain it.
|
|
return await handle.evaluateHandle(element => {
|
|
return element;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @internal
|
|
*/
|
|
export class BidiWorkerRealm extends BidiRealm {
|
|
static from(
|
|
realm: DedicatedWorkerRealm | SharedWorkerRealm,
|
|
worker: BidiWebWorker,
|
|
): BidiWorkerRealm {
|
|
const workerRealm = new BidiWorkerRealm(realm, worker);
|
|
workerRealm.initialize();
|
|
return workerRealm;
|
|
}
|
|
declare readonly realm: DedicatedWorkerRealm | SharedWorkerRealm;
|
|
|
|
readonly #worker: BidiWebWorker;
|
|
|
|
private constructor(
|
|
realm: DedicatedWorkerRealm | SharedWorkerRealm,
|
|
frame: BidiWebWorker,
|
|
) {
|
|
super(realm, frame.timeoutSettings);
|
|
this.#worker = frame;
|
|
}
|
|
|
|
override get environment(): BidiWebWorker {
|
|
return this.#worker;
|
|
}
|
|
|
|
override async adoptBackendNode(): Promise<JSHandle<Node>> {
|
|
throw new Error('Cannot adopt DOM nodes into a worker.');
|
|
}
|
|
}
|