Skip to content

Commit

Permalink
Attempt to polyfill BYOB ReadableStream on Safari
Browse files Browse the repository at this point in the history
  • Loading branch information
adamziel committed Jan 8, 2024
1 parent bb3de73 commit 2b0acd0
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 12 deletions.
38 changes: 27 additions & 11 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"comlink": "^4.4.1",
"express": "4.18.2",
"express-fileupload": "1.4.0",
"fetch-readablestream": "0.2.0",
"file-saver": "^2.0.5",
"follow-redirects": "1.15.2",
"fs-extra": "11.1.1",
Expand All @@ -69,6 +70,7 @@
"react-modal": "^3.16.1",
"unzipper": "0.10.11",
"vite-plugin-api": "1.0.4",
"web-streams-polyfill": "3.3.2",
"yargs": "17.7.2"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Safari doesn't support BYOB streams yet. The stream-compression
// package ships a polyfill – let's start by importing it, then:
import '@php-wasm/stream-compression';
import { concatUint8Array } from '@php-wasm/stream-compression';

Check failure on line 4 in packages/php-wasm/progress/src/lib/emscripten-download-monitor.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'concatUint8Array' is defined but never used

/*
* An approximate total file size to use when the actual
* total number of bytes is missing.
Expand Down
3 changes: 3 additions & 0 deletions packages/php-wasm/stream-compression/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import '@php-wasm/node-polyfills';

import './polyfills';

export { collectBytes } from './utils/collect-bytes';
export { collectFile } from './utils/collect-file';
export { iteratorToStream } from './utils/iterator-to-stream';
export { StreamedFile } from './utils/streamed-file';
export { encodeZip, decodeZip, decodeRemoteZip } from './zip';
export { concatUint8Array } from './utils/concat-uint8-array';
113 changes: 113 additions & 0 deletions packages/php-wasm/stream-compression/src/polyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { collectBytes } from './utils/collect-bytes';

Check failure on line 1 in packages/php-wasm/stream-compression/src/polyfills.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

'collectBytes' is defined but never used

function areByobStreamsSupported() {
return (
typeof ReadableStreamBYOBReader !== 'undefined' &&
typeof ReadableStreamBYOBRequest !== 'undefined'
);
}

/**
* Safari does not support BYOB streams, so we need to polyfill them.
*/
if (!areByobStreamsSupported()) {
// Bring spec-compliant streams to global scope
const streams = await import('web-streams-polyfill/ponyfill');
for (const [key, value] of Object.entries(streams)) {
(globalThis as any)[key] = value;
}

function bufferToStream(buffer) {

Check failure on line 20 in packages/php-wasm/stream-compression/src/polyfills.ts

View workflow job for this annotation

GitHub Actions / Lint and typecheck

Move function declaration to program root
return new ReadableStream({
type: 'bytes',
// 0.5 MB seems like a reasonable chunk size, let's adjust
// this if needed.
autoAllocateChunkSize: 512 * 1024,
/**
* We could write directly to controller.byobRequest.view
* here. Unfortunately, in Chrome it detaches on the first
* `await` and cannot be reused once we actually have the data.
*/
async pull(controller) {
// Read data until we have enough to fill the BYOB request:
const view = controller.byobRequest!.view!;
const uint8array = new Uint8Array(view.byteLength);
uint8array.set(buffer);
buffer = buffer.slice(uint8array.byteLength);
console.log(
'Pull',
{ uint8array, buf: buffer },
view.byteLength
);

// Emit that chunk:
controller.byobRequest?.respondWithNewView(uint8array);
if (buffer.byteLength === 0) {
controller.close();
controller.byobRequest?.respond(0);
}
},
});
}

class ByobResponse extends Response {
constructor(arrayBuffer: BodyInit | null, init?: ResponseInit) {
const _bodyStream = bufferToStream(arrayBuffer);
super(_bodyStream, init);
this._bodyStream = _bodyStream;
}
override get body() {
return this._bodyStream;
}
}

const _fetchBackup = (globalThis as any).fetch;
(globalThis as any).fetch = async function (...args): Promise<Response> {
console.log({ args });
if (args[0].includes('.wasm')) {
return _fetchBackup(...args);
}
console.log('Getch called');
const response = await _fetchBackup(...args);
return new ByobResponse(await response.arrayBuffer(), {
status: response.status,
statusText: response.statusText,
headers: response.headers,
});
};
// console.log(
// await (await window.fetch('/index.html')).body
// ?.getReader({ mode: 'byob' })
// .read(new Uint8Array(200))
// );
// (globalThis as any).fetch = async function fetch(
// input: RequestInfo,
// init?: RequestInit
// ): Promise<Response> {
// const response = await (globalThis as any).fetch(input, init);
// response.bo;
// const body = await response.arrayBuffer();
// return new Response(body, response);
// };
// Add support for BYOB streams to ReadableStream
// const _getReader = ReadableStream.prototype.getReader;
// ReadableStream.prototype.getReader = function getReader(options: any) {
// if (options && options.mode === 'byob') {
// return streamToByobStream(this).getReader();
// }
// return _getReader.call(this, options);
// };

// // ReadableStream.prototype.pipeTo = function pipeTo(target, options) {
// // return streamToByobStream(this).pipeTo(target, options);
// // };
// ReadableStream.prototype.pipeThrough = function pipeThrough(
// target,
// options
// ) {
// return streamToByobStream(this).pipeThrough(target, options);
// };
}

// Make this file a module
export {};
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function concatBytes(totalBytes?: number) {
return new TransformStream<Uint8Array, Uint8Array>({
transform(chunk) {
const view = new Uint8Array(buffer);
console.log({ offset, chunk, view });
view.set(chunk, offset);
offset += chunk.byteLength;
},
Expand Down
2 changes: 1 addition & 1 deletion packages/php-wasm/web/src/lib/register-service-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function registerServiceWorker<
navigator.serviceWorker.addEventListener(
'message',
async function onMessage(event) {
console.debug('[window][sw] Message from ServiceWorker', event);
// console.debug('[window][sw] Message from ServiceWorker', event);
/**
* Ignore events meant for other PHP instances to
* avoid handling the same event twice.
Expand Down

0 comments on commit 2b0acd0

Please sign in to comment.