refactor!: use ReadableStreams (#11805)

This commit is contained in:
Nikolay Vitkov 2024-02-02 13:25:02 +01:00 committed by GitHub
parent 514e2d5241
commit 84d9a94d62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 65 additions and 61 deletions

View File

@ -10,7 +10,9 @@ Generates a PDF of the page with the `print` CSS media type.
```typescript ```typescript
class Page { class Page {
abstract createPDFStream(options?: PDFOptions): Promise<Readable>; abstract createPDFStream(
options?: PDFOptions
): Promise<ReadableStream<Uint8Array>>;
} }
``` ```
@ -22,7 +24,7 @@ class Page {
**Returns:** **Returns:**
Promise&lt;Readable&gt; Promise&lt;ReadableStream&lt;Uint8Array&gt;&gt;
## Remarks ## Remarks

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import type {Readable} from 'stream';
import type {Protocol} from 'devtools-protocol'; import type {Protocol} from 'devtools-protocol';
import { import {
@ -2574,7 +2572,9 @@ export abstract class Page extends EventEmitter<PageEvents> {
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust | `-webkit-print-color-adjust`} * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-print-color-adjust | `-webkit-print-color-adjust`}
* property to force rendering of exact colors. * property to force rendering of exact colors.
*/ */
abstract createPDFStream(options?: PDFOptions): Promise<Readable>; abstract createPDFStream(
options?: PDFOptions
): Promise<ReadableStream<Uint8Array>>;
/** /**
* {@inheritDoc Page.createPDFStream} * {@inheritDoc Page.createPDFStream}

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import type {Readable} from 'stream';
import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js'; import * as Bidi from 'chromium-bidi/lib/cjs/protocol/protocol.js';
import type Protocol from 'devtools-protocol'; import type Protocol from 'devtools-protocol';
@ -638,19 +636,15 @@ export class BidiPage extends Page {
override async createPDFStream( override async createPDFStream(
options?: PDFOptions | undefined options?: PDFOptions | undefined
): Promise<Readable> { ): Promise<ReadableStream<Uint8Array>> {
const buffer = await this.pdf(options); const buffer = await this.pdf(options);
try {
const {Readable} = await import('stream'); return new ReadableStream({
return Readable.from(buffer); start(controller) {
} catch (error) { controller.enqueue(buffer);
if (error instanceof TypeError) { controller.close();
throw new Error( },
'Can only pass a file path in a Node-like environment.' });
);
}
throw error;
}
} }
override async _screenshot( override async _screenshot(

View File

@ -4,8 +4,6 @@
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
import type {Readable} from 'stream';
import type {Protocol} from 'devtools-protocol'; import type {Protocol} from 'devtools-protocol';
import {firstValueFrom, from, raceWith} from '../../third_party/rxjs/rxjs.js'; import {firstValueFrom, from, raceWith} from '../../third_party/rxjs/rxjs.js';
@ -1105,7 +1103,9 @@ export class CdpPage extends Page {
return data; return data;
} }
override async createPDFStream(options: PDFOptions = {}): Promise<Readable> { override async createPDFStream(
options: PDFOptions = {}
): Promise<ReadableStream<Uint8Array>> {
const {timeout: ms = this._timeoutSettings.timeout()} = options; const {timeout: ms = this._timeoutSettings.timeout()} = options;
const { const {
landscape, landscape,

View File

@ -5,13 +5,10 @@
*/ */
import type FS from 'fs/promises'; import type FS from 'fs/promises';
import type {Readable} from 'stream';
import {map, NEVER, Observable, timer} from '../../third_party/rxjs/rxjs.js'; import {map, NEVER, Observable, timer} from '../../third_party/rxjs/rxjs.js';
import type {CDPSession} from '../api/CDPSession.js'; import type {CDPSession} from '../api/CDPSession.js';
import {isNode} from '../environment.js';
import {assert} from '../util/assert.js'; import {assert} from '../util/assert.js';
import {isErrorLike} from '../util/ErrorLike.js';
import {debug} from './Debug.js'; import {debug} from './Debug.js';
import {TimeoutError} from './Errors.js'; import {TimeoutError} from './Errors.js';
@ -209,69 +206,74 @@ export async function importFSPromises(): Promise<typeof FS> {
* @internal * @internal
*/ */
export async function getReadableAsBuffer( export async function getReadableAsBuffer(
readable: Readable, readable: ReadableStream<Uint8Array>,
path?: string path?: string
): Promise<Buffer | null> { ): Promise<Buffer | null> {
const buffers = []; const buffers = [];
const reader = readable.getReader();
if (path) { if (path) {
const fs = await importFSPromises(); const fs = await importFSPromises();
const fileHandle = await fs.open(path, 'w+'); const fileHandle = await fs.open(path, 'w+');
try { try {
for await (const chunk of readable) { while (true) {
buffers.push(chunk); const {done, value} = await reader.read();
await fileHandle.writeFile(chunk); if (done) {
break;
}
buffers.push(value);
await fileHandle.writeFile(value);
} }
} finally { } finally {
await fileHandle.close(); await fileHandle.close();
} }
} else { } else {
for await (const chunk of readable) { while (true) {
buffers.push(chunk); const {done, value} = await reader.read();
if (done) {
break;
}
buffers.push(value);
} }
} }
try { try {
return Buffer.concat(buffers); return Buffer.concat(buffers);
} catch (error) { } catch (error) {
debugError(error);
return null; return null;
} }
} }
/**
* @internal
*/
/** /**
* @internal * @internal
*/ */
export async function getReadableFromProtocolStream( export async function getReadableFromProtocolStream(
client: CDPSession, client: CDPSession,
handle: string handle: string
): Promise<Readable> { ): Promise<ReadableStream<Uint8Array>> {
// TODO: Once Node 18 becomes the lowest supported version, we can migrate to return new ReadableStream({
// ReadableStream. async pull(controller) {
if (!isNode) { function getUnit8Array(data: string, isBase64: boolean): Uint8Array {
throw new Error('Cannot create a stream outside of Node.js environment.'); if (isBase64) {
} return Uint8Array.from(atob(data), m => {
return m.codePointAt(0)!;
const {Readable} = await import('stream'); });
}
let eof = false; const encoder = new TextEncoder();
return new Readable({ return encoder.encode(data);
async read(size: number) {
if (eof) {
return;
} }
try { const {data, base64Encoded, eof} = await client.send('IO.read', {
const response = await client.send('IO.read', {handle, size}); handle,
this.push(response.data, response.base64Encoded ? 'base64' : undefined); });
if (response.eof) {
eof = true; controller.enqueue(getUnit8Array(data, base64Encoded ?? false));
await client.send('IO.close', {handle}); if (eof) {
this.push(null); await client.send('IO.close', {handle});
} controller.close();
} catch (error) {
if (isErrorLike(error)) {
this.destroy(error);
return;
}
throw error;
} }
}, },
}); });

View File

@ -1964,9 +1964,15 @@ describe('Page', function () {
const stream = await page.createPDFStream(); const stream = await page.createPDFStream();
let size = 0; let size = 0;
for await (const chunk of stream) { const reader = stream.getReader();
size += chunk.length; while (true) {
const {done, value} = await reader.read();
if (done) {
break;
}
size += value.length;
} }
expect(size).toBeGreaterThan(0); expect(size).toBeGreaterThan(0);
}); });