diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e884a6..23c8447 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,5 +36,5 @@ jobs: run: chrome --version - name: Run tests run: | - deno task test + CAROL_DEBUG=1 deno task test diff --git a/carol.ts b/carol.ts index 56b3eae..46b1828 100644 --- a/carol.ts +++ b/carol.ts @@ -69,6 +69,14 @@ interface ExtendedSelf extends Self { receivedFromParent(message: Messages.Any): void; } +// deno-lint-ignore no-explicit-any +type PageFunctionArgs = any[]; + +interface PageFunction { + // deno-lint-ignore no-explicit-any + (...args: PageFunctionArgs): any; +} + interface Carol { loadParams(): Promise; } @@ -222,8 +230,11 @@ class Application extends EventEmitter implements types.Application { ); } - // deno-lint-ignore no-explicit-any, ban-types - evaluate(pageFunction: Function | string, ...args: unknown[]): Promise { + evaluate( + pageFunction: PageFunction | string, + ...args: PageFunctionArgs + // deno-lint-ignore no-explicit-any + ): Promise { return this.mainWindow().evaluate(pageFunction, ...args); } @@ -322,7 +333,7 @@ class Window extends EventEmitter implements types.Window { private loadParams_!: unknown[]; private domContentLoadedCallback_?: () => void; - private windowId_: unknown; + private windowId_!: number; private interceptionInitialized_ = false; private hostHandle_: HandleProxy; private receivedFromChild_!: (message: Messages.Any) => void; @@ -352,7 +363,7 @@ class Window extends EventEmitter implements types.Window { */ async init_(): Promise { this.logger_.debug("[app] Configuring window"); - const targetId = this.page_.target()._targetInfo.targetId; + const targetId = this.page_.target()._targetId; assert(this.options_.bgcolor); const bgcolor = Color.parse(this.options_.bgcolor); assert(bgcolor, "Invalid bgcolor"); @@ -394,9 +405,8 @@ class Window extends EventEmitter implements types.Window { } async evaluate( - // deno-lint-ignore ban-types - pageFunction: string | Function, - ...args: unknown[] + pageFunction: string | PageFunction, + ...args: PageFunctionArgs // deno-lint-ignore no-explicit-any ): Promise { try { @@ -444,7 +454,7 @@ class Window extends EventEmitter implements types.Window { // Await here to process exceptions. await this.page_.goto( new URL(this.loadURI_, "https://domain/").toString(), - { timeout: 0, waitFor: "domcontentloaded" }, + { timeout: 0, waitUntil: "domcontentloaded" }, ); // Available in Chrome M73+. this.session_.send("Page.resetNavigationHistory").catch((_e) => {}); @@ -453,7 +463,7 @@ class Window extends EventEmitter implements types.Window { return result; } - initBounds_(result: { windowId: unknown }) { + initBounds_(result: { windowId: number }) { this.windowId_ = result.windowId; return this.setBounds( { @@ -664,7 +674,7 @@ class Window extends EventEmitter implements types.Window { } async fullscreen(): Promise { - const bounds = { windowState: "fullscreen" }; + const bounds = { windowState: "fullscreen" } as const; await this.app_.session_.send( "Browser.setWindowBounds", { windowId: this.windowId_, bounds }, @@ -672,7 +682,7 @@ class Window extends EventEmitter implements types.Window { } async minimize(): Promise { - const bounds = { windowState: "minimized" }; + const bounds = { windowState: "minimized" } as const; await this.app_.session_.send( "Browser.setWindowBounds", { windowId: this.windowId_, bounds }, @@ -680,7 +690,7 @@ class Window extends EventEmitter implements types.Window { } async maximize(): Promise { - const bounds = { windowState: "maximized" }; + const bounds = { windowState: "maximized" } as const; await this.app_.session_.send( "Browser.setWindowBounds", { windowId: this.windowId_, bounds }, @@ -833,9 +843,11 @@ class HostWindow { "Runtime.evaluate", { expression }, ); + const { objectId } = result; + assert(objectId != null, "objectId must be set"); return this.window_.session_.send( "DOM.getFileInfo", - { objectId: result.objectId }, + { objectId }, ); } } diff --git a/deps.ts b/deps.ts index a5e80f3..c34f5a6 100644 --- a/deps.ts +++ b/deps.ts @@ -27,11 +27,15 @@ export { fail, } from "https://deno.land/std@0.149.0/testing/asserts.ts"; -export { default as puppeteer } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/web.js"; -export { EventEmitter } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/EventEmitter.js"; -export { BrowserWebSocketTransport } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/BrowserWebSocketTransport.js"; - -export type { Browser } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/Browser.js"; -export type { Target } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/Target.js"; -export type { CDPSession } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/Connection.js"; -export type { Page } from "https://unpkg.com/puppeteer@13.3.2/lib/esm/puppeteer/common/Page.js"; +export { + default as puppeteer, +} from "https://deno.land/x/puppeteer@14.1.1/mod.ts"; +export type { + Browser, + CDPSession, + Page, + Protocol, + Target, +} from "https://deno.land/x/puppeteer@14.1.1/mod.ts"; +export { EventEmitter } from "https://deno.land/x/puppeteer@14.1.1/vendor/puppeteer-core/puppeteer/common/EventEmitter.js"; +export { BrowserWebSocketTransport } from "https://deno.land/x/puppeteer@14.1.1/vendor/puppeteer-core/puppeteer/common/BrowserWebSocketTransport.js"; diff --git a/http_request.ts b/http_request.ts index a01694e..eba3b9f 100644 --- a/http_request.ts +++ b/http_request.ts @@ -18,8 +18,13 @@ */ import { concat, encode, encodeToBase64 } from "./deps.ts"; -import type { CDPSession } from "./deps.ts"; +import type { CDPSession, Protocol } from "./deps.ts"; import type * as types from "./types.ts"; +import { assert } from "./util.ts"; + +type ContinueInterceptedRequestRequest = Partial< + Protocol.Network.ContinueInterceptedRequestRequest +>; const statusTexts = { "100": "Continue", @@ -87,7 +92,8 @@ const statusTexts = { export interface HttpRequestParams { request: Request; resourceType: string; - interceptionId: number; + interceptionId: + Protocol.Network.ContinueInterceptedRequestRequest["interceptionId"]; } interface Request { @@ -96,15 +102,6 @@ interface Request { headers: Record; } -interface ResolveParams { - url?: string; - method?: string; - headers?: Record; - interceptionId?: number; - rawResponse?: string; - errorReason?: string; -} - export class HttpRequest implements types.HttpRequest { done_ = false; @@ -150,7 +147,7 @@ export class HttpRequest implements types.HttpRequest { deferToBrowser(overrides?: types.Overrides) { this.logger_.debug("[server] deferToBrowser", this.url()); - const params = {} as ResolveParams; + const params = {} as ContinueInterceptedRequestRequest; if (overrides && overrides.url) params.url = overrides.url; if (overrides && overrides.method) params.method = overrides.method; if (overrides && overrides.headers) params.headers = overrides.headers; @@ -207,11 +204,15 @@ export class HttpRequest implements types.HttpRequest { this.resolve_({}); } - resolve_(params: ResolveParams): Promise { + resolve_(params: ContinueInterceptedRequestRequest): Promise { this.logger_.debug("[server] resolve", this.url()); if (this.done_) throw new Error("Already resolved given request"); params.interceptionId = this.params_.interceptionId; this.done_ = true; - return this.session_.send("Network.continueInterceptedRequest", params); + assert(params.interceptionId != null); + return this.session_.send( + "Network.continueInterceptedRequest", + params as Protocol.Network.ContinueInterceptedRequestRequest, + ); } } diff --git a/puppeteer.ts b/puppeteer.ts index c8c883c..6573e1c 100644 --- a/puppeteer.ts +++ b/puppeteer.ts @@ -27,11 +27,6 @@ import type { Browser, Target } from "./deps.ts"; import type { AppOptions } from "./types.ts"; import { getLocalDataDir } from "./util.ts"; -interface LaunchResult { - browser: Browser; - chromeProcess: Deno.Process; -} - class ExtendedBrowserWebSocketTransport extends BrowserWebSocketTransport { #ws: WebSocket; @@ -40,13 +35,18 @@ class ExtendedBrowserWebSocketTransport extends BrowserWebSocketTransport { this.#ws = ws; } - close(): void { + async close(): Promise { if (this.#ws.readyState === this.#ws.OPEN) { - super.close(); + await super.close(); } } } +interface LaunchResult { + browser: Browser; + chromeProcess: Deno.Process; +} + export async function launch( executablePath: string, headless: boolean, @@ -58,16 +58,24 @@ export async function launch( stderr: "piped", }); const wsEndpoint = await waitForWSEndpoint(chromeProcess.stderr); - const transport = await ExtendedBrowserWebSocketTransport.create(wsEndpoint); - const browser = await puppeteer.connect({ - ignoreHTTPSErrors: true, - transport, - }); - await browser.waitForTarget((t: Target) => t.type() === "page"); - return { - browser, - chromeProcess, - }; + try { + const transport = await ExtendedBrowserWebSocketTransport.create( + wsEndpoint, + ); + const browser = await puppeteer.connect({ + ignoreHTTPSErrors: true, + transport, + }); + await browser.waitForTarget((t: Target) => t.type() === "page"); + return { + browser, + chromeProcess, + }; + } catch (error) { + console.error(error); + chromeProcess.close(); + throw error; + } } function prepareChromeArgs( diff --git a/types.ts b/types.ts index 89a0e58..f54589f 100644 --- a/types.ts +++ b/types.ts @@ -220,10 +220,10 @@ export interface Window { } export interface Bounds { - left: number; - top: number; - width: number; - height: number; + left?: number; + top?: number; + width?: number; + height?: number; } export interface HttpHandler { diff --git a/util.ts b/util.ts index c00e6ad..bf32c3a 100644 --- a/util.ts +++ b/util.ts @@ -1,5 +1,11 @@ import { dirname, fromFileUrl, join } from "./deps.ts"; +export function assert(expr: unknown, msg = ""): asserts expr { + if (!expr) { + throw new Error("assertion failed: " + msg); + } +} + export function getLocalDataDir(): string { const __dirname = import.meta.url.startsWith("file://") ? dirname(fromFileUrl(import.meta.url))