1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

Compare commits

...

3 commits

24 changed files with 1997 additions and 25 deletions

15
Cargo.lock generated
View file

@ -2013,9 +2013,12 @@ dependencies = [
"quinn", "quinn",
"rustls-tokio-stream", "rustls-tokio-stream",
"serde", "serde",
"sha2",
"socket2", "socket2",
"thiserror 2.0.3", "thiserror 2.0.3",
"tokio", "tokio",
"url",
"web-transport-proto",
] ]
[[package]] [[package]]
@ -8869,6 +8872,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-transport-proto"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a3806ea43df5817f0d90618c842d28db5946bc18a5db0659b2275c2be48d472"
dependencies = [
"bytes",
"http 1.1.0",
"thiserror 1.0.64",
"url",
]
[[package]] [[package]]
name = "webpki-root-certs" name = "webpki-root-certs"
version = "0.26.6" version = "0.26.6"

View file

@ -115,10 +115,16 @@ exec deno {} "$@"
Ok(()) Ok(())
} }
fn get_installer_root() -> Result<PathBuf, io::Error> { fn get_installer_root() -> Result<PathBuf, AnyError> {
if let Ok(env_dir) = env::var("DENO_INSTALL_ROOT") { if let Some(env_dir) = env::var_os("DENO_INSTALL_ROOT") {
if !env_dir.is_empty() { if !env_dir.is_empty() {
return canonicalize_path_maybe_not_exists(&PathBuf::from(env_dir)); let env_dir = PathBuf::from(env_dir);
return canonicalize_path_maybe_not_exists(&env_dir).with_context(|| {
format!(
"Canonicalizing DENO_INSTALL_ROOT ('{}').",
env_dir.display()
)
});
} }
} }
// Note: on Windows, the $HOME environment variable may be set by users or by // Note: on Windows, the $HOME environment variable may be set by users or by
@ -587,11 +593,22 @@ async fn resolve_shim_data(
let copy_path = get_hidden_file_with_ext(&file_path, "deno.json"); let copy_path = get_hidden_file_with_ext(&file_path, "deno.json");
executable_args.push("--config".to_string()); executable_args.push("--config".to_string());
executable_args.push(copy_path.to_str().unwrap().to_string()); executable_args.push(copy_path.to_str().unwrap().to_string());
extra_files.push(( let mut config_text = fs::read_to_string(config_path)
copy_path, .with_context(|| format!("error reading {config_path}"))?;
fs::read_to_string(config_path) // always remove the import map field because when someone specifies `--import-map` we
.with_context(|| format!("error reading {config_path}"))?, // don't want that file to be attempted to be loaded and when they don't specify that
)); // (which is just something we haven't implemented yet)
if let Some(new_text) = remove_import_map_field_from_text(&config_text) {
if flags.import_map_path.is_none() {
log::warn!(
"{} \"importMap\" field in the specified config file we be ignored. Use the --import-map flag instead.",
crate::colors::yellow("Warning"),
);
}
config_text = new_text;
}
extra_files.push((copy_path, config_text));
} else { } else {
executable_args.push("--no-config".to_string()); executable_args.push("--no-config".to_string());
} }
@ -631,6 +648,16 @@ async fn resolve_shim_data(
}) })
} }
fn remove_import_map_field_from_text(config_text: &str) -> Option<String> {
let value =
jsonc_parser::cst::CstRootNode::parse(config_text, &Default::default())
.ok()?;
let root_value = value.object_value()?;
let import_map_value = root_value.get("importMap")?;
import_map_value.remove();
Some(value.to_string())
}
fn get_hidden_file_with_ext(file_path: &Path, ext: &str) -> PathBuf { fn get_hidden_file_with_ext(file_path: &Path, ext: &str) -> PathBuf {
// use a dot file to prevent the file from showing up in some // use a dot file to prevent the file from showing up in some
// users shell auto-complete since this directory is on the PATH // users shell auto-complete since this directory is on the PATH
@ -1585,4 +1612,17 @@ mod tests {
assert!(!file_path.exists()); assert!(!file_path.exists());
} }
} }
#[test]
fn test_remove_import_map_field_from_text() {
assert_eq!(
remove_import_map_field_from_text(
r#"{
"importMap": "./value.json"
}"#,
)
.unwrap(),
"{}"
);
}
} }

View file

@ -1481,9 +1481,15 @@ delete Object.prototype.__proto__;
options: SNAPSHOT_COMPILE_OPTIONS, options: SNAPSHOT_COMPILE_OPTIONS,
host, host,
}); });
const errors = ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM);
assert( assert(
ts.getPreEmitDiagnostics(TS_SNAPSHOT_PROGRAM).length === 0, errors.length === 0,
"lib.d.ts files have errors", `lib.d.ts files have errors:\n${
ts.formatDiagnosticsWithColorAndContext(
errors,
host,
)
}`,
); );
// remove this now that we don't need it anymore for warming up tsc // remove this now that we don't need it anymore for warming up tsc

View file

@ -34,6 +34,8 @@ import {
op_quic_send_stream_get_id, op_quic_send_stream_get_id,
op_quic_send_stream_get_priority, op_quic_send_stream_get_priority,
op_quic_send_stream_set_priority, op_quic_send_stream_set_priority,
op_webtransport_accept,
op_webtransport_connect,
} from "ext:core/ops"; } from "ext:core/ops";
import { import {
getReadableStreamResourceBacking, getReadableStreamResourceBacking,
@ -50,6 +52,7 @@ const {
const { const {
ObjectPrototypeIsPrototypeOf, ObjectPrototypeIsPrototypeOf,
PromisePrototypeThen, PromisePrototypeThen,
ReflectConstruct,
Symbol, Symbol,
SymbolAsyncIterator, SymbolAsyncIterator,
SafePromisePrototypeFinally, SafePromisePrototypeFinally,
@ -205,6 +208,9 @@ class QuicIncoming {
} }
} }
let webtransportConnect;
let webtransportAccept;
class QuicConn { class QuicConn {
#resource; #resource;
#bidiStream = null; #bidiStream = null;
@ -309,6 +315,43 @@ class QuicConn {
close({ closeCode = 0, reason = "" } = { __proto__: null }) { close({ closeCode = 0, reason = "" } = { __proto__: null }) {
op_quic_connection_close(this.#resource, closeCode, reason); op_quic_connection_close(this.#resource, closeCode, reason);
} }
static {
webtransportConnect = async function webtransportConnect(conn, url) {
const {
0: connectTxRid,
1: connectRxRid,
2: settingsTxRid,
3: settingsRxRid,
} = await op_webtransport_connect(conn.#resource, url);
const connect = new QuicBidirectionalStream(
connectTxRid,
connectRxRid,
conn.closed,
);
const settingsTx = writableStream(settingsTxRid, conn.closed);
const settingsRx = readableStream(settingsRxRid, conn.closed);
return { connect, settingsTx, settingsRx };
};
webtransportAccept = async function webtransportAccept(conn) {
const {
0: url,
1: connectTxRid,
2: connectRxRid,
3: settingsTxRid,
4: settingsRxRid,
} = await op_webtransport_accept(conn.#resource);
const connect = new QuicBidirectionalStream(
connectTxRid,
connectRxRid,
conn.closed,
);
const settingsTx = writableStream(settingsTxRid, conn.closed);
const settingsRx = readableStream(settingsRxRid, conn.closed);
return { url, connect, settingsTx, settingsRx };
};
}
} }
class QuicSendStream extends WritableStream { class QuicSendStream extends WritableStream {
@ -345,7 +388,11 @@ function readableStream(rid, closed) {
SafePromisePrototypeFinally(closed, () => { SafePromisePrototypeFinally(closed, () => {
core.tryClose(rid); core.tryClose(rid);
}); });
return readableStreamForRid(rid, true, QuicReceiveStream); return readableStreamForRid(
rid,
true,
(...args) => ReflectConstruct(QuicReceiveStream, args),
);
} }
function writableStream(rid, closed) { function writableStream(rid, closed) {
@ -353,7 +400,11 @@ function writableStream(rid, closed) {
SafePromisePrototypeFinally(closed, () => { SafePromisePrototypeFinally(closed, () => {
core.tryClose(rid); core.tryClose(rid);
}); });
return writableStreamForRid(rid, true, QuicSendStream); return writableStreamForRid(
rid,
true,
(...args) => ReflectConstruct(QuicSendStream, args),
);
} }
class QuicBidirectionalStream { class QuicBidirectionalStream {
@ -421,6 +472,7 @@ function connectQuic(options) {
caCerts: options.caCerts, caCerts: options.caCerts,
alpnProtocols: options.alpnProtocols, alpnProtocols: options.alpnProtocols,
serverName: options.serverName, serverName: options.serverName,
serverCertificateHashes: options.serverCertificateHashes,
}, },
transportOptions(options), transportOptions(options),
keyPair, keyPair,
@ -448,4 +500,6 @@ export {
QuicListener, QuicListener,
QuicReceiveStream, QuicReceiveStream,
QuicSendStream, QuicSendStream,
webtransportAccept,
webtransportConnect,
}; };

View file

@ -24,6 +24,9 @@ pin-project.workspace = true
quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio", "rustls", "ring"] } quinn = { version = "0.11.6", default-features = false, features = ["runtime-tokio", "rustls", "ring"] }
rustls-tokio-stream.workspace = true rustls-tokio-stream.workspace = true
serde.workspace = true serde.workspace = true
sha2.workspace = true
socket2.workspace = true socket2.workspace = true
thiserror.workspace = true thiserror.workspace = true
tokio.workspace = true tokio.workspace = true
url.workspace = true
web-transport-proto = "0.2.3"

View file

@ -196,6 +196,8 @@ deno_core::extension!(deno_net,
quic::op_quic_send_stream_get_id, quic::op_quic_send_stream_get_id,
quic::op_quic_send_stream_get_priority, quic::op_quic_send_stream_get_priority,
quic::op_quic_send_stream_set_priority, quic::op_quic_send_stream_set_priority,
quic::webtransport::op_webtransport_accept,
quic::webtransport::op_webtransport_connect,
], ],
esm = [ "01_net.js", "02_tls.js" ], esm = [ "01_net.js", "02_tls.js" ],
lazy_loaded_esm = [ "03_quic.js" ], lazy_loaded_esm = [ "03_quic.js" ],

View file

@ -93,12 +93,27 @@ pub enum QuicError {
#[class("BadResource")] #[class("BadResource")]
#[error("{0}")] #[error("{0}")]
ClosedStream(#[from] quinn::ClosedStream), ClosedStream(#[from] quinn::ClosedStream),
#[class(generic)]
#[error("{0}")]
ReadError(#[from] quinn::ReadError),
#[class(generic)]
#[error("{0}")]
WriteError(#[from] quinn::WriteError),
#[class("BadResource")] #[class("BadResource")]
#[error("Invalid {0} resource")] #[error("Invalid {0} resource")]
BadResource(&'static str), BadResource(&'static str),
#[class(range)] #[class(range)]
#[error("Connection has reached the maximum number of concurrent outgoing {0} streams")] #[error("Connection has reached the maximum number of concurrent outgoing {0} streams")]
MaxStreams(&'static str), MaxStreams(&'static str),
#[class(generic)]
#[error("Peer does not support WebTransport")]
WebTransportPeerUnsupported,
#[class(generic)]
#[error("{0}")]
WebTransportSettingsError(#[from] web_transport_proto::SettingsError),
#[class(generic)]
#[error("{0}")]
WebTransportConnectError(#[from] web_transport_proto::ConnectError),
#[class(inherit)] #[class(inherit)]
#[error(transparent)] #[error(transparent)]
Other(#[from] JsErrorBox), Other(#[from] JsErrorBox),
@ -475,6 +490,14 @@ struct ConnectArgs {
ca_certs: Option<Vec<String>>, ca_certs: Option<Vec<String>>,
alpn_protocols: Option<Vec<String>>, alpn_protocols: Option<Vec<String>>,
server_name: Option<String>, server_name: Option<String>,
server_certificate_hashes: Option<Vec<CertificateHash>>,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct CertificateHash {
algorithm: String,
value: JsBuffer,
} }
#[op2] #[op2]
@ -515,13 +538,28 @@ where
.map(|s| s.into_bytes()) .map(|s| s.into_bytes())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut tls_config = create_client_config( let mut tls_config = if let Some(hashes) = args.server_certificate_hashes {
deno_tls::rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(
webtransport::ServerFingerprints::new(
hashes
.into_iter()
.filter(|h| h.algorithm.to_lowercase() == "sha-256")
.map(|h| h.value.to_vec())
.collect(),
),
))
.with_no_client_auth()
} else {
create_client_config(
root_cert_store, root_cert_store,
ca_certs, ca_certs,
unsafely_ignore_certificate_errors, unsafely_ignore_certificate_errors,
key_pair.take(), key_pair.take(),
SocketUse::GeneralSsl, SocketUse::GeneralSsl,
)?; )?
};
if let Some(alpn_protocols) = args.alpn_protocols { if let Some(alpn_protocols) = args.alpn_protocols {
tls_config.alpn_protocols = tls_config.alpn_protocols =
@ -925,3 +963,305 @@ pub(crate) fn op_quic_recv_stream_get_id(
let stream_id = quinn::VarInt::from(resource.stream_id).into_inner(); let stream_id = quinn::VarInt::from(resource.stream_id).into_inner();
Ok(stream_id) Ok(stream_id)
} }
pub(crate) mod webtransport {
// MIT License
//
// Copyright (c) 2023 Luke Curley
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// https://github.com/kixelated/web-transport-rs
use deno_core::futures::try_join;
use deno_tls::rustls;
use rustls::client::danger::ServerCertVerifier;
use rustls::crypto::verify_tls12_signature;
use rustls::crypto::verify_tls13_signature;
use rustls::crypto::CryptoProvider;
use sha2::Digest;
use sha2::Sha256;
use super::*;
async fn exchange_settings(
state: Rc<RefCell<OpState>>,
conn: quinn::Connection,
) -> Result<(u32, u32), QuicError> {
use web_transport_proto::Settings;
use web_transport_proto::SettingsError;
let settings_tx_rid = async {
let mut tx = conn.open_uni().await?;
let mut settings = Settings::default();
settings.enable_webtransport(1);
let mut buf = vec![];
settings.encode(&mut buf);
tx.write_all(&buf).await?;
let rid = state
.borrow_mut()
.resource_table
.add(SendStreamResource::new(tx));
Ok(rid)
};
let settings_rx_rid = async {
let mut rx = conn.accept_uni().await?;
let mut buf = Vec::new();
loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let settings = match Settings::decode(&mut limit) {
Ok(settings) => settings,
Err(SettingsError::UnexpectedEnd) => continue,
Err(e) => return Err(e.into()),
};
if settings.supports_webtransport() == 0 {
return Err(QuicError::WebTransportPeerUnsupported);
}
break;
}
let rid = state
.borrow_mut()
.resource_table
.add(RecvStreamResource::new(rx));
Ok(rid)
};
let (settings_tx_rid, settings_rx_rid) =
try_join!(settings_tx_rid, settings_rx_rid)?;
Ok((settings_tx_rid, settings_rx_rid))
}
#[op2(async)]
#[serde]
pub(crate) async fn op_webtransport_connect(
state: Rc<RefCell<OpState>>,
#[cppgc] connection_resource: &ConnectionResource,
#[string] url: String,
) -> Result<(u32, u32, u32, u32), QuicError> {
use web_transport_proto::ConnectError;
use web_transport_proto::ConnectRequest;
use web_transport_proto::ConnectResponse;
let conn = connection_resource.0.clone();
let url = url::Url::parse(&url).unwrap();
let (settings_tx_rid, settings_rx_rid) =
exchange_settings(state.clone(), conn.clone()).await?;
let (connect_tx_rid, connect_rx_rid) = {
let (mut tx, mut rx) = conn.open_bi().await?;
let request = ConnectRequest { url: url.clone() };
let mut buf = Vec::new();
request.encode(&mut buf);
tx.write_all(&buf).await?;
buf.clear();
loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let res = match ConnectResponse::decode(&mut limit) {
Ok(res) => res,
Err(ConnectError::UnexpectedEnd) => {
continue;
}
Err(e) => return Err(e.into()),
};
if res.status != 200 {
return Err(QuicError::WebTransportPeerUnsupported);
}
break;
}
let mut state = state.borrow_mut();
let tx_rid = state.resource_table.add(SendStreamResource::new(tx));
let rx_rid = state.resource_table.add(RecvStreamResource::new(rx));
(tx_rid, rx_rid)
};
Ok((
connect_tx_rid,
connect_rx_rid,
settings_tx_rid,
settings_rx_rid,
))
}
#[op2(async)]
#[serde]
pub(crate) async fn op_webtransport_accept(
state: Rc<RefCell<OpState>>,
#[cppgc] connection_resource: &ConnectionResource,
) -> Result<(String, u32, u32, u32, u32), QuicError> {
use web_transport_proto::ConnectError;
use web_transport_proto::ConnectRequest;
use web_transport_proto::ConnectResponse;
let conn = connection_resource.0.clone();
let (settings_tx_rid, settings_rx_rid) =
exchange_settings(state.clone(), conn.clone()).await?;
let (url, connect_tx_rid, connect_rx_rid) = {
let (mut tx, mut rx) = conn.accept_bi().await?;
let mut buf = Vec::new();
let req = loop {
let chunk = rx.read_chunk(usize::MAX, true).await?;
let chunk = chunk.ok_or(QuicError::WebTransportPeerUnsupported)?;
buf.extend_from_slice(&chunk.bytes);
let mut limit = std::io::Cursor::new(&buf);
let req = match ConnectRequest::decode(&mut limit) {
Ok(res) => res,
Err(ConnectError::UnexpectedEnd) => {
continue;
}
Err(e) => return Err(e.into()),
};
break req;
};
buf.clear();
let resp = ConnectResponse {
status: 200u16.try_into().unwrap(),
};
resp.encode(&mut buf);
tx.write_all(&buf).await?;
let mut state = state.borrow_mut();
let tx_rid = state.resource_table.add(SendStreamResource::new(tx));
let rx_rid = state.resource_table.add(RecvStreamResource::new(rx));
(req.url, tx_rid, rx_rid)
};
Ok((
url.to_string(),
connect_tx_rid,
connect_rx_rid,
settings_tx_rid,
settings_rx_rid,
))
}
#[derive(Debug)]
pub(crate) struct ServerFingerprints {
fingerprints: Vec<Vec<u8>>,
provider: CryptoProvider,
}
impl ServerFingerprints {
pub(crate) fn new(fingerprints: Vec<Vec<u8>>) -> ServerFingerprints {
Self {
fingerprints,
provider: rustls::crypto::ring::default_provider(),
}
}
}
impl ServerCertVerifier for ServerFingerprints {
fn verify_server_cert(
&self,
end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
let cert_hash = Sha256::digest(end_entity);
if self
.fingerprints
.iter()
.any(|fingerprint| fingerprint == cert_hash.as_slice())
{
return Ok(rustls::client::danger::ServerCertVerified::assertion());
}
Err(rustls::Error::InvalidCertificate(
rustls::CertificateError::UnknownIssuer,
))
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
verify_tls12_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &rustls::pki_types::CertificateDer<'_>,
dss: &rustls::DigitallySignedStruct,
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error>
{
verify_tls13_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
self
.provider
.signature_verification_algorithms
.supported_schemes()
}
}
}

View file

@ -908,8 +908,8 @@ const _original = Symbol("[[original]]");
* @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true.
* @returns {ReadableStream<Uint8Array>} * @returns {ReadableStream<Uint8Array>}
*/ */
function readableStreamForRid(rid, autoClose = true, Super, onError) { function readableStreamForRid(rid, autoClose = true, cfn, onError) {
const stream = new (Super ?? ReadableStream)(_brand); const stream = cfn ? cfn(_brand) : new ReadableStream(_brand);
stream[_resourceBacking] = { rid, autoClose }; stream[_resourceBacking] = { rid, autoClose };
const tryClose = () => { const tryClose = () => {
@ -1134,8 +1134,8 @@ async function readableStreamCollectIntoUint8Array(stream) {
* @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true. * @param {boolean=} autoClose If the resource should be auto-closed when the stream closes. Defaults to true.
* @returns {ReadableStream<Uint8Array>} * @returns {ReadableStream<Uint8Array>}
*/ */
function writableStreamForRid(rid, autoClose = true, Super) { function writableStreamForRid(rid, autoClose = true, cfn) {
const stream = new (Super ?? WritableStream)(_brand); const stream = cfn ? cfn(_brand) : new WritableStream(_brand);
stream[_resourceBacking] = { rid, autoClose }; stream[_resourceBacking] = { rid, autoClose };
const tryClose = () => { const tryClose = () => {

View file

@ -1378,3 +1378,221 @@ declare var ImageData: {
settings?: ImageDataSettings, settings?: ImageDataSettings,
): ImageData; ): ImageData;
}; };
/** @category Platform */
interface WebTransportCloseInfo {
closeCode?: number;
reason?: string;
}
/** @category Platform */
interface WebTransportErrorOptions {
source?: WebTransportErrorSource;
streamErrorCode?: number | null;
}
/** @category Platform */
interface WebTransportHash {
algorithm?: string;
value?: BufferSource;
}
/** @category Platform */
interface WebTransportOptions {
allowPooling?: boolean;
congestionControl?: WebTransportCongestionControl;
requireUnreliable?: boolean;
serverCertificateHashes?: WebTransportHash[];
}
/** @category Platform */
interface WebTransportSendStreamOptions {
sendGroup?: WebTransportSendGroup;
sendOrder?: number;
waitUntilAvailable?: boolean;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport)
* @category Platform
*/
interface WebTransport {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/closed) */
readonly closed: Promise<WebTransportCloseInfo>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/datagrams) */
readonly datagrams: WebTransportDatagramDuplexStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/incomingBidirectionalStreams) */
readonly incomingBidirectionalStreams: ReadableStream<
WebTransportBidirectionalStream
>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/incomingUnidirectionalStreams) */
readonly incomingUnidirectionalStreams: ReadableStream<
WebTransportReceiveStream
>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/ready) */
readonly ready: Promise<undefined>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/close) */
close(closeInfo?: WebTransportCloseInfo): void;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createBidirectionalStream) */
createBidirectionalStream(
options?: WebTransportSendStreamOptions,
): Promise<WebTransportBidirectionalStream>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createUnidirectionalStream) */
createUnidirectionalStream(
options?: WebTransportSendStreamOptions,
): Promise<WebTransportSendStream>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransport/createSendGroup) */
createSendGroup(): WebTransportSendGroup;
}
/** @category Platform */
declare var WebTransport: {
prototype: WebTransport;
new (url: string | URL, options?: WebTransportOptions): WebTransport;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream)
* @category Platform
*/
interface WebTransportBidirectionalStream {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream/readable) */
readonly readable: WebTransportReceiveStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportBidirectionalStream/writable) */
readonly writable: WebTransportSendStream;
}
/** @category Platform */
declare var WebTransportBidirectionalStream: {
prototype: WebTransportBidirectionalStream;
new (): WebTransportBidirectionalStream;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream)
* @category Platform
*/
interface WebTransportDatagramDuplexStream {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/incomingHighWaterMark) */
incomingHighWaterMark: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/incomingMaxAge) */
incomingMaxAge: number | null;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/maxDatagramSize) */
readonly maxDatagramSize: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/outgoingHighWaterMark) */
outgoingHighWaterMark: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/outgoingMaxAge) */
outgoingMaxAge: number | null;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/readable) */
readonly readable: WebTransportReceiveStream;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportDatagramDuplexStream/writable) */
readonly writable: WebTransportSendStream;
}
/** @category Platform */
declare var WebTransportDatagramDuplexStream: {
prototype: WebTransportDatagramDuplexStream;
new (): WebTransportDatagramDuplexStream;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream)
* @category Platform
*/
interface WebTransportSendStream extends WritableStream<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/sendOrder) */
sendOrder: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/sendGroup) */
sendGroup?: WebTransportSendGroup;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/getStats) */
getStats(): Promise<WebTransportSendStreamStats>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendStream/getWriter) */
getWriter(): WebTransportWriter;
}
/** @category Platform */
declare var WebTransportSendStream: {
prototype: WebTransportSendStream;
new (): WebTransportSendStream;
};
/** @category Platform */
interface WebTransportSendStreamStats {
bytesWritten: number;
bytesSent: number;
bytesAcknowledged: number;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportWriter)
* @category Platform
*/
interface WebTransportWriter extends WritableStreamDefaultWriter<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportWriter/atomicWrite) */
atomicWrite(chunk: any): Promise<undefined>;
}
/** @category Platform */
declare var WebTransportWriter: {
prototype: WebTransportWriter;
new (): WebTransportWriter;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream)
* @category Platform
*/
interface WebTransportReceiveStream extends ReadableStream<Uint8Array> {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportReceiveStream/getStats) */
getStats(): Promise<WebTransportReceiveStreamStats>;
}
/** @category Platform */
declare var WebTransportReceiveStream: {
prototype: WebTransportReceiveStream;
new (): WebTransportReceiveStream;
};
/** @category Platform */
interface WebTransportReceiveStreamStats {
bytesReceived: number;
bytesRead: number;
}
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup)
* @category Platform
*/
interface WebTransportSendGroup {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportSendGroup/getStats) */
getStats(): Promise<WebTransportSendStreamStats>;
}
/** @category Platform */
declare var WebTransportSendGroup: {
prototype: WebTransportSendGroup;
new (): WebTransportSendGroup;
};
/**
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError)
* @category Platform
*/
interface WebTransportError extends DOMException {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError/source) */
readonly source: WebTransportErrorSource;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebTransportError/streamErrorCode) */
readonly streamErrorCode: number | null;
}
/** @category Platform */
declare var WebTransportError: {
prototype: WebTransportError;
new (message?: string, options?: WebTransportErrorOptions): WebTransportError;
};
/** @category Platform */
type WebTransportCongestionControl = "default" | "low-latency" | "throughput";
/** @category Platform */
type WebTransportErrorSource = "session" | "stream";

View file

@ -113,6 +113,7 @@ deno_core::extension!(deno_web,
"15_performance.js", "15_performance.js",
"16_image_data.js", "16_image_data.js",
], ],
lazy_loaded_esm = [ "webtransport.js" ],
options = { options = {
blob_store: Arc<BlobStore>, blob_store: Arc<BlobStore>,
maybe_location: Option<Url>, maybe_location: Option<Url>,

1125
ext/web/webtransport.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -324,9 +324,15 @@ converters.short = createIntegerConversion(16, { unsigned: false });
converters["unsigned short"] = createIntegerConversion(16, { converters["unsigned short"] = createIntegerConversion(16, {
unsigned: true, unsigned: true,
}); });
converters["unsigned short?"] = createNullableConverter(
converters["unsigned short"],
);
converters.long = createIntegerConversion(32, { unsigned: false }); converters.long = createIntegerConversion(32, { unsigned: false });
converters["unsigned long"] = createIntegerConversion(32, { unsigned: true }); converters["unsigned long"] = createIntegerConversion(32, { unsigned: true });
converters["unsigned long?"] = createNullableConverter(
converters["unsigned long"],
);
converters["long long"] = createLongLongConversion(64, { unsigned: false }); converters["long long"] = createLongLongConversion(64, { unsigned: false });
converters["unsigned long long"] = createLongLongConversion(64, { converters["unsigned long long"] = createLongLongConversion(64, {
@ -398,6 +404,10 @@ converters["unrestricted double"] = (V, _prefix, _context, _opts) => {
return x; return x;
}; };
converters["unrestricted double?"] = createNullableConverter(
converters["unrestricted double"],
);
converters.DOMString = function ( converters.DOMString = function (
V, V,
prefix, prefix,

View file

@ -34,6 +34,7 @@ import * as telemetry from "ext:deno_telemetry/telemetry.ts";
const { ObjectDefineProperties } = primordials; const { ObjectDefineProperties } = primordials;
const loadQuic = core.createLazyLoader("ext:deno_net/03_quic.js"); const loadQuic = core.createLazyLoader("ext:deno_net/03_quic.js");
const loadWebTransport = core.createLazyLoader("ext:deno_web/webtransport.js");
const denoNs = { const denoNs = {
Process: process.Process, Process: process.Process,
@ -198,6 +199,10 @@ ObjectDefineProperties(denoNsUnstableById[unstableIds.net], {
loadQuic, loadQuic,
), ),
QuicIncoming: core.propWritableLazyLoaded((q) => q.QuicIncoming, loadQuic), QuicIncoming: core.propWritableLazyLoaded((q) => q.QuicIncoming, loadQuic),
upgradeWebTransport: core.propWritableLazyLoaded(
(wt) => wt.upgradeWebTransport,
loadWebTransport,
),
}); });
// denoNsUnstableById[unstableIds.unsafeProto] = { __proto__: null } // denoNsUnstableById[unstableIds.unsafeProto] = { __proto__: null }

View file

@ -39,6 +39,7 @@ import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
import { unstableIds } from "ext:runtime/90_deno_ns.js"; import { unstableIds } from "ext:runtime/90_deno_ns.js";
const loadImage = core.createLazyLoader("ext:deno_canvas/01_image.js"); const loadImage = core.createLazyLoader("ext:deno_canvas/01_image.js");
const loadWebTransport = core.createLazyLoader("ext:deno_web/webtransport.js");
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
const windowOrWorkerGlobalScope = { const windowOrWorkerGlobalScope = {
@ -298,6 +299,34 @@ unstableForWindowOrWorkerGlobalScope[unstableIds.broadcastChannel] = {
unstableForWindowOrWorkerGlobalScope[unstableIds.net] = { unstableForWindowOrWorkerGlobalScope[unstableIds.net] = {
WebSocketStream: core.propNonEnumerable(webSocketStream.WebSocketStream), WebSocketStream: core.propNonEnumerable(webSocketStream.WebSocketStream),
WebSocketError: core.propNonEnumerable(webSocketStream.WebSocketError), WebSocketError: core.propNonEnumerable(webSocketStream.WebSocketError),
WebTransport: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransport,
loadWebTransport,
),
WebTransportBidirectionalStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportBidirectionalStream,
loadWebTransport,
),
WebTransportDatagramDuplexStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportDatagramDuplexStream,
loadWebTransport,
),
WebTransportReceiveStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportReceiveStream,
loadWebTransport,
),
WebTransportSendGroup: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportSendGroup,
loadWebTransport,
),
WebTransportSendStream: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportSendStream,
loadWebTransport,
),
WebTransportError: core.propNonEnumerableLazyLoaded(
(wt) => wt.WebTransportError,
loadWebTransport,
),
}; };
unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {}; unstableForWindowOrWorkerGlobalScope[unstableIds.webgpu] = {};

View file

@ -5762,7 +5762,7 @@ fn lsp_jsr_auto_import_completion() {
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
assert!(!list.is_incomplete); assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 268); assert_eq!(list.items.len(), 276);
let item = list.items.iter().find(|i| i.label == "add").unwrap(); let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add"); assert_eq!(&item.label, "add");
assert_eq!( assert_eq!(
@ -5842,7 +5842,7 @@ fn lsp_jsr_auto_import_completion_import_map() {
json!({ "triggerKind": 1 }), json!({ "triggerKind": 1 }),
); );
assert!(!list.is_incomplete); assert!(!list.is_incomplete);
assert_eq!(list.items.len(), 268); assert_eq!(list.items.len(), 276);
let item = list.items.iter().find(|i| i.label == "add").unwrap(); let item = list.items.iter().find(|i| i.label == "add").unwrap();
assert_eq!(&item.label, "add"); assert_eq!(&item.label, "add");
assert_eq!(json!(&item.label_details), json!({ "description": "add" })); assert_eq!(json!(&item.label_details), json!({ "description": "add" }));

View file

@ -0,0 +1,5 @@
{
"tempDir": true,
"args": "install -g --root ./folder --config deno.json main.ts --name my-cli",
"output": "install.out"
}

View file

@ -0,0 +1,3 @@
{
"importMap": "./import_map.json"
}

View file

@ -0,0 +1,2 @@
{
}

View file

@ -0,0 +1,3 @@
Warning "importMap" field in the specified config file we be ignored. Use the --import-map flag instead.
✅ Successfully installed my-cli
[WILDCARD]

View file

@ -0,0 +1 @@
console.log(1);

View file

@ -0,0 +1,5 @@
{
"args": "run --quiet --unstable-net -A main.ts",
"output": "",
"exitCode": 0
}

View file

@ -0,0 +1,4 @@
{
"lock": false,
"importMap": "../../../../import_map.json"
}

View file

@ -0,0 +1,100 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { decodeBase64 } from "@std/encoding/base64";
import { assertEquals } from "@std/assert";
const cert = Deno.readTextFileSync("../../../testdata/tls/localhost.crt");
const certHash = await crypto.subtle.digest(
"SHA-256",
decodeBase64(cert.split("\n").slice(1, -2).join("")),
);
const server = new Deno.QuicEndpoint({
hostname: "localhost",
port: 0,
});
const listener = server.listen({
cert,
key: Deno.readTextFileSync("../../../testdata/tls/localhost.key"),
alpnProtocols: ["h3"],
});
(async () => {
for await (const conn of listener) {
const wt = await Deno.upgradeWebTransport(conn);
assertEquals(wt.url, `https://localhost:${server.addr.port}/path`);
wt.ready.then(() => {
(async () => {
for await (const bidi of wt.incomingBidirectionalStreams) {
bidi.readable.pipeTo(bidi.writable).catch(() => {});
}
})();
(async () => {
for await (const stream of wt.incomingUnidirectionalStreams) {
const out = await wt.createUnidirectionalStream();
stream.pipeTo(out).catch(() => {});
}
})();
wt.datagrams.readable.pipeTo(wt.datagrams.writable);
});
}
})();
const client = new WebTransport(`https://localhost:${server.addr.port}/path`, {
serverCertificateHashes: [{
algorithm: "sha-256",
value: certHash,
}],
});
client.ready.then(async () => {
const bi = await client.createBidirectionalStream();
{
const writer = bi.writable.getWriter();
await writer.write(new Uint8Array([1, 0, 1, 0]));
writer.releaseLock();
}
{
const reader = bi.readable.getReader();
assertEquals(await reader.read(), {
value: new Uint8Array([1, 0, 1, 0]),
done: false,
});
reader.releaseLock();
}
{
const uni = await client.createUnidirectionalStream();
const writer = uni.getWriter();
await writer.write(new Uint8Array([0, 2, 0, 2]));
writer.releaseLock();
}
{
const uni =
(await client.incomingUnidirectionalStreams.getReader().read()).value;
const reader = uni.getReader();
assertEquals(await reader.read(), {
value: new Uint8Array([0, 2, 0, 2]),
done: false,
});
reader.releaseLock();
}
await client.datagrams.writable.getWriter().write(
new Uint8Array([3, 0, 3, 0]),
);
assertEquals(await client.datagrams.readable.getReader().read(), {
value: new Uint8Array([3, 0, 3, 0]),
done: false,
});
client.close();
server.close();
});

View file

@ -233,6 +233,7 @@
"ext:deno_web/14_compression.js": "../ext/web/14_compression.js", "ext:deno_web/14_compression.js": "../ext/web/14_compression.js",
"ext:deno_web/15_performance.js": "../ext/web/15_performance.js", "ext:deno_web/15_performance.js": "../ext/web/15_performance.js",
"ext:deno_web/16_image_data.js": "../ext/web/16_image_data.js", "ext:deno_web/16_image_data.js": "../ext/web/16_image_data.js",
"ext:deno_web/webtransport.js": "../ext/web/webtransport.js",
"ext:deno_webidl/00_webidl.js": "../ext/webidl/00_webidl.js", "ext:deno_webidl/00_webidl.js": "../ext/webidl/00_webidl.js",
"ext:deno_websocket/01_websocket.js": "../ext/websocket/01_websocket.js", "ext:deno_websocket/01_websocket.js": "../ext/websocket/01_websocket.js",
"ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js", "ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js",