Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: decorate undici classes as platform interfaces #55178

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions lib/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const {
} = primordials;

const { validateInteger } = require('internal/validators');
const { lazyUndici } = require('internal/util');
const httpAgent = require('_http_agent');
const { ClientRequest } = require('_http_client');
const { methods, parsers } = require('_http_common');
Expand All @@ -42,7 +43,6 @@ const {
ServerResponse,
} = require('_http_server');
let maxHeaderSize;
let undici;

/**
* Returns a new instance of `http.Server`.
Expand Down Expand Up @@ -115,14 +115,6 @@ function get(url, options, cb) {
return req;
}

/**
* Lazy loads WebSocket, CloseEvent and MessageEvent classes from undici
* @returns {object} An object containing WebSocket, CloseEvent, and MessageEvent classes.
*/
function lazyUndici() {
return undici ??= require('internal/deps/undici/undici');
}

module.exports = {
_connectionListener,
METHODS: methods.toSorted(),
Expand Down
38 changes: 37 additions & 1 deletion lib/internal/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const {
ObjectSetPrototypeOf,
ObjectValues,
Promise,
Proxy,
ReflectApply,
ReflectConstruct,
RegExpPrototypeExec,
Expand Down Expand Up @@ -60,6 +61,7 @@ const {
privateSymbols: {
arrow_message_private_symbol,
decorated_private_symbol,
transfer_mode_private_symbol,
},
sleep: _sleep,
} = internalBinding('util');
Expand Down Expand Up @@ -614,6 +616,31 @@ function exposeGetterAndSetter(target, name, getter, setter = undefined) {
});
}

let undici;
/**
* Lazy load Undici module by decorating every class with serializable
* private symbol so its instances can be recognized as platform objects
*/
function lazyUndici() {
if (!undici) {
undici = require('internal/deps/undici/undici');
for (const mod of [
'WebSocket', 'EventSource', 'FormData', 'Headers',
'Request', 'Response', 'MessageEvent', 'CloseEvent']) {
undici[mod] = new Proxy(undici[mod], {
__proto__: null,
construct(target, args, newTarget) {
const obj = ReflectConstruct(target, args, newTarget);
// 0 means not cloneable, nor transferable
obj[transfer_mode_private_symbol] = 0;
return obj;
},
});
}
}
return undici;
}

function defineLazyProperties(target, id, keys, enumerable = true) {
const descriptors = { __proto__: null };
let mod;
Expand All @@ -632,7 +659,15 @@ function defineLazyProperties(target, id, keys, enumerable = true) {
value: `set ${key}`,
});
function get() {
mod ??= require(id);
// Undici is special as it comes from deps and we need to load it with decoration
// TODO(jazelly): not hardcode this. Ideally, every deps module that is
// platform specific needs to be decorated
if (id === 'internal/deps/undici/undici') {
mod = lazyUndici();
} else {
mod ??= require(id);
}

if (lazyLoadedValue === undefined) {
lazyLoadedValue = mod[key];
set(lazyLoadedValue);
Expand Down Expand Up @@ -916,6 +951,7 @@ module.exports = {
join,
lazyDOMException,
lazyDOMExceptionClass,
lazyUndici,
normalizeEncoding,
once,
promisify,
Expand Down
6 changes: 2 additions & 4 deletions lib/internal/wasm_web_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ const {
ERR_WEBASSEMBLY_RESPONSE,
} = require('internal/errors').codes;

let undici;
function lazyUndici() {
return undici ??= require('internal/deps/undici/undici');
}
const { lazyUndici } = require('internal/util');


// This is essentially an implementation of a v8::WasmStreamingCallback, except
// that it is implemented in JavaScript because the fetch() implementation is
Expand Down
18 changes: 18 additions & 0 deletions test/parallel/test-structuredClone-global.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
'use strict';

// Flags: --experimental-eventsource --no-experimental-websocket --experimental-websocket

require('../common');
const assert = require('assert');

Expand Down Expand Up @@ -30,6 +32,22 @@ for (const StreamClass of [ReadableStream, WritableStream, TransformStream]) {
assert.ok(extendedTransfer instanceof StreamClass);
}

// Platform object that is not serializable should throw
[
{ platformClass: Response, brand: 'Response' },
{ platformClass: Request, value: 'http://localhost', brand: 'Request' },
{ platformClass: FormData, brand: 'FormData' },
{ platformClass: MessageEvent, value: 'message', brand: 'MessageEvent' },
{ platformClass: CloseEvent, value: 'dummy type', brand: 'CloseEvent' },
{ platformClass: WebSocket, value: 'http://localhost', brand: 'WebSocket' },
{ platformClass: EventSource, value: 'http://localhost', brand: 'EventSource' },
].forEach((platformEntity) => {
assert.throws(() => structuredClone(new platformEntity.platformClass(platformEntity.value)),
new DOMException('Cannot clone object of unsupported type.', 'DataCloneError'),
`Cloning ${platformEntity.brand} should throw DOMException`);

});

for (const Transferrable of [File, Blob]) {
const a2 = Transferrable === File ? '' : {};
const original = new Transferrable([], a2);
Expand Down
Loading