mirror of
https://github.com/denoland/deno.git
synced 2024-11-24 15:19:26 -05:00
refactor: deno_fetch op crate (#7524)
This commit is contained in:
parent
cead79f5b8
commit
7845740637
34 changed files with 2544 additions and 2234 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -403,6 +403,7 @@ dependencies = [
|
|||
"clap",
|
||||
"deno_core",
|
||||
"deno_doc",
|
||||
"deno_fetch",
|
||||
"deno_lint",
|
||||
"deno_web",
|
||||
"dissimilar",
|
||||
|
@ -483,6 +484,15 @@ dependencies = [
|
|||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_fetch"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"reqwest",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_lint"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -4,6 +4,7 @@ members = [
|
|||
"core",
|
||||
"test_plugin",
|
||||
"test_util",
|
||||
"op_crates/fetch",
|
||||
"op_crates/web",
|
||||
]
|
||||
exclude = [
|
||||
|
|
|
@ -22,6 +22,7 @@ path = "./bench/main.rs"
|
|||
[build-dependencies]
|
||||
deno_core = { path = "../core", version = "0.57.0" }
|
||||
deno_web = { path = "../op_crates/web", version = "0.8.0" }
|
||||
deno_fetch = { path = "../op_crates/fetch", version = "0.1.0" }
|
||||
|
||||
[target.'cfg(windows)'.build-dependencies]
|
||||
winres = "0.1.11"
|
||||
|
@ -32,6 +33,7 @@ deno_core = { path = "../core", version = "0.57.0" }
|
|||
deno_doc = "0.1.9"
|
||||
deno_lint = { version = "0.2.0", features = ["json"] }
|
||||
deno_web = { path = "../op_crates/web", version = "0.8.0" }
|
||||
deno_fetch = { path = "../op_crates/fetch", version = "0.1.0" }
|
||||
|
||||
atty = "0.2.14"
|
||||
base64 = "0.12.3"
|
||||
|
|
|
@ -16,6 +16,7 @@ fn create_snapshot(
|
|||
files: Vec<PathBuf>,
|
||||
) {
|
||||
deno_web::init(&mut isolate);
|
||||
deno_fetch::init(&mut isolate);
|
||||
// TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
|
||||
// workspace root.
|
||||
let display_root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
|
||||
|
@ -52,6 +53,10 @@ fn create_compiler_snapshot(
|
|||
let mut custom_libs: HashMap<String, PathBuf> = HashMap::new();
|
||||
custom_libs
|
||||
.insert("lib.deno.web.d.ts".to_string(), deno_web::get_declaration());
|
||||
custom_libs.insert(
|
||||
"lib.deno.fetch.d.ts".to_string(),
|
||||
deno_fetch::get_declaration(),
|
||||
);
|
||||
custom_libs.insert(
|
||||
"lib.deno.window.d.ts".to_string(),
|
||||
cwd.join("dts/lib.deno.window.d.ts"),
|
||||
|
@ -112,6 +117,10 @@ fn main() {
|
|||
"cargo:rustc-env=DENO_WEB_LIB_PATH={}",
|
||||
deno_web::get_declaration().display()
|
||||
);
|
||||
println!(
|
||||
"cargo:rustc-env=DENO_FETCH_LIB_PATH={}",
|
||||
deno_fetch::get_declaration().display()
|
||||
);
|
||||
|
||||
println!(
|
||||
"cargo:rustc-env=TARGET={}",
|
||||
|
|
629
cli/dts/lib.deno.shared_globals.d.ts
vendored
629
cli/dts/lib.deno.shared_globals.d.ts
vendored
|
@ -5,6 +5,7 @@
|
|||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="esnext" />
|
||||
/// <reference lib="deno.web" />
|
||||
/// <reference lib="deno.fetch" />
|
||||
|
||||
declare namespace WebAssembly {
|
||||
interface CompileError {
|
||||
|
@ -227,255 +228,6 @@ declare function removeEventListener(
|
|||
options?: boolean | EventListenerOptions | undefined,
|
||||
): void;
|
||||
|
||||
interface DomIterable<K, V> {
|
||||
keys(): IterableIterator<K>;
|
||||
values(): IterableIterator<V>;
|
||||
entries(): IterableIterator<[K, V]>;
|
||||
[Symbol.iterator](): IterableIterator<[K, V]>;
|
||||
forEach(
|
||||
callback: (value: V, key: K, parent: this) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface ReadableStreamReadDoneResult<T> {
|
||||
done: true;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
interface ReadableStreamReadValueResult<T> {
|
||||
done: false;
|
||||
value: T;
|
||||
}
|
||||
|
||||
type ReadableStreamReadResult<T> =
|
||||
| ReadableStreamReadValueResult<T>
|
||||
| ReadableStreamReadDoneResult<T>;
|
||||
|
||||
interface ReadableStreamDefaultReader<R = any> {
|
||||
readonly closed: Promise<void>;
|
||||
cancel(reason?: any): Promise<void>;
|
||||
read(): Promise<ReadableStreamReadResult<R>>;
|
||||
releaseLock(): void;
|
||||
}
|
||||
|
||||
interface ReadableStreamReader<R = any> {
|
||||
cancel(): Promise<void>;
|
||||
read(): Promise<ReadableStreamReadResult<R>>;
|
||||
releaseLock(): void;
|
||||
}
|
||||
|
||||
interface ReadableByteStreamControllerCallback {
|
||||
(controller: ReadableByteStreamController): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface UnderlyingByteSource {
|
||||
autoAllocateChunkSize?: number;
|
||||
cancel?: ReadableStreamErrorCallback;
|
||||
pull?: ReadableByteStreamControllerCallback;
|
||||
start?: ReadableByteStreamControllerCallback;
|
||||
type: "bytes";
|
||||
}
|
||||
|
||||
interface UnderlyingSource<R = any> {
|
||||
cancel?: ReadableStreamErrorCallback;
|
||||
pull?: ReadableStreamDefaultControllerCallback<R>;
|
||||
start?: ReadableStreamDefaultControllerCallback<R>;
|
||||
type?: undefined;
|
||||
}
|
||||
|
||||
interface ReadableStreamErrorCallback {
|
||||
(reason: any): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface ReadableStreamDefaultControllerCallback<R> {
|
||||
(controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface ReadableStreamDefaultController<R = any> {
|
||||
readonly desiredSize: number | null;
|
||||
close(): void;
|
||||
enqueue(chunk: R): void;
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
interface ReadableByteStreamController {
|
||||
readonly byobRequest: undefined;
|
||||
readonly desiredSize: number | null;
|
||||
close(): void;
|
||||
enqueue(chunk: ArrayBufferView): void;
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
interface PipeOptions {
|
||||
preventAbort?: boolean;
|
||||
preventCancel?: boolean;
|
||||
preventClose?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
interface QueuingStrategySizeCallback<T = any> {
|
||||
(chunk: T): number;
|
||||
}
|
||||
|
||||
interface QueuingStrategy<T = any> {
|
||||
highWaterMark?: number;
|
||||
size?: QueuingStrategySizeCallback<T>;
|
||||
}
|
||||
|
||||
/** This Streams API interface provides a built-in byte length queuing strategy
|
||||
* that can be used when constructing streams. */
|
||||
declare class CountQueuingStrategy implements QueuingStrategy {
|
||||
constructor(options: { highWaterMark: number });
|
||||
highWaterMark: number;
|
||||
size(chunk: any): 1;
|
||||
}
|
||||
|
||||
declare class ByteLengthQueuingStrategy
|
||||
implements QueuingStrategy<ArrayBufferView> {
|
||||
constructor(options: { highWaterMark: number });
|
||||
highWaterMark: number;
|
||||
size(chunk: ArrayBufferView): number;
|
||||
}
|
||||
|
||||
/** This Streams API interface represents a readable stream of byte data. The
|
||||
* Fetch API offers a concrete instance of a ReadableStream through the body
|
||||
* property of a Response object. */
|
||||
interface ReadableStream<R = any> {
|
||||
readonly locked: boolean;
|
||||
cancel(reason?: any): Promise<void>;
|
||||
getIterator(options?: { preventCancel?: boolean }): AsyncIterableIterator<R>;
|
||||
// getReader(options: { mode: "byob" }): ReadableStreamBYOBReader;
|
||||
getReader(): ReadableStreamDefaultReader<R>;
|
||||
pipeThrough<T>(
|
||||
{
|
||||
writable,
|
||||
readable,
|
||||
}: {
|
||||
writable: WritableStream<R>;
|
||||
readable: ReadableStream<T>;
|
||||
},
|
||||
options?: PipeOptions,
|
||||
): ReadableStream<T>;
|
||||
pipeTo(dest: WritableStream<R>, options?: PipeOptions): Promise<void>;
|
||||
tee(): [ReadableStream<R>, ReadableStream<R>];
|
||||
[Symbol.asyncIterator](options?: {
|
||||
preventCancel?: boolean;
|
||||
}): AsyncIterableIterator<R>;
|
||||
}
|
||||
|
||||
declare var ReadableStream: {
|
||||
prototype: ReadableStream;
|
||||
new (
|
||||
underlyingSource: UnderlyingByteSource,
|
||||
strategy?: { highWaterMark?: number; size?: undefined },
|
||||
): ReadableStream<Uint8Array>;
|
||||
new <R = any>(
|
||||
underlyingSource?: UnderlyingSource<R>,
|
||||
strategy?: QueuingStrategy<R>,
|
||||
): ReadableStream<R>;
|
||||
};
|
||||
|
||||
interface WritableStreamDefaultControllerCloseCallback {
|
||||
(): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface WritableStreamDefaultControllerStartCallback {
|
||||
(controller: WritableStreamDefaultController): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface WritableStreamDefaultControllerWriteCallback<W> {
|
||||
(chunk: W, controller: WritableStreamDefaultController):
|
||||
| void
|
||||
| PromiseLike<
|
||||
void
|
||||
>;
|
||||
}
|
||||
|
||||
interface WritableStreamErrorCallback {
|
||||
(reason: any): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface UnderlyingSink<W = any> {
|
||||
abort?: WritableStreamErrorCallback;
|
||||
close?: WritableStreamDefaultControllerCloseCallback;
|
||||
start?: WritableStreamDefaultControllerStartCallback;
|
||||
type?: undefined;
|
||||
write?: WritableStreamDefaultControllerWriteCallback<W>;
|
||||
}
|
||||
|
||||
/** This Streams API interface provides a standard abstraction for writing
|
||||
* streaming data to a destination, known as a sink. This object comes with
|
||||
* built-in backpressure and queuing. */
|
||||
declare class WritableStream<W = any> {
|
||||
constructor(
|
||||
underlyingSink?: UnderlyingSink<W>,
|
||||
strategy?: QueuingStrategy<W>,
|
||||
);
|
||||
readonly locked: boolean;
|
||||
abort(reason?: any): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
getWriter(): WritableStreamDefaultWriter<W>;
|
||||
}
|
||||
|
||||
/** This Streams API interface represents a controller allowing control of a
|
||||
* WritableStream's state. When constructing a WritableStream, the underlying
|
||||
* sink is given a corresponding WritableStreamDefaultController instance to
|
||||
* manipulate. */
|
||||
interface WritableStreamDefaultController {
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
/** This Streams API interface is the object returned by
|
||||
* WritableStream.getWriter() and once created locks the < writer to the
|
||||
* WritableStream ensuring that no other streams can write to the underlying
|
||||
* sink. */
|
||||
interface WritableStreamDefaultWriter<W = any> {
|
||||
readonly closed: Promise<void>;
|
||||
readonly desiredSize: number | null;
|
||||
readonly ready: Promise<void>;
|
||||
abort(reason?: any): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
releaseLock(): void;
|
||||
write(chunk: W): Promise<void>;
|
||||
}
|
||||
|
||||
declare class TransformStream<I = any, O = any> {
|
||||
constructor(
|
||||
transformer?: Transformer<I, O>,
|
||||
writableStrategy?: QueuingStrategy<I>,
|
||||
readableStrategy?: QueuingStrategy<O>,
|
||||
);
|
||||
readonly readable: ReadableStream<O>;
|
||||
readonly writable: WritableStream<I>;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultController<O = any> {
|
||||
readonly desiredSize: number | null;
|
||||
enqueue(chunk: O): void;
|
||||
error(reason?: any): void;
|
||||
terminate(): void;
|
||||
}
|
||||
|
||||
interface Transformer<I = any, O = any> {
|
||||
flush?: TransformStreamDefaultControllerCallback<O>;
|
||||
readableType?: undefined;
|
||||
start?: TransformStreamDefaultControllerCallback<O>;
|
||||
transform?: TransformStreamDefaultControllerTransformCallback<I, O>;
|
||||
writableType?: undefined;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultControllerCallback<O> {
|
||||
(controller: TransformStreamDefaultController<O>): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultControllerTransformCallback<I, O> {
|
||||
(
|
||||
chunk: I,
|
||||
controller: TransformStreamDefaultController<O>,
|
||||
): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface DOMStringList {
|
||||
/** Returns the number of strings in strings. */
|
||||
readonly length: number;
|
||||
|
@ -487,43 +239,6 @@ interface DOMStringList {
|
|||
}
|
||||
|
||||
type BufferSource = ArrayBufferView | ArrayBuffer;
|
||||
type BlobPart = BufferSource | Blob | string;
|
||||
|
||||
interface BlobPropertyBag {
|
||||
type?: string;
|
||||
ending?: "transparent" | "native";
|
||||
}
|
||||
|
||||
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
|
||||
interface Blob {
|
||||
readonly size: number;
|
||||
readonly type: string;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
slice(start?: number, end?: number, contentType?: string): Blob;
|
||||
stream(): ReadableStream;
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
declare const Blob: {
|
||||
prototype: Blob;
|
||||
new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
|
||||
};
|
||||
|
||||
interface FilePropertyBag extends BlobPropertyBag {
|
||||
lastModified?: number;
|
||||
}
|
||||
|
||||
/** Provides information about files and allows JavaScript in a web page to
|
||||
* access their content. */
|
||||
interface File extends Blob {
|
||||
readonly lastModified: number;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
declare const File: {
|
||||
prototype: File;
|
||||
new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
|
||||
};
|
||||
|
||||
interface FileReaderEventMap {
|
||||
"abort": ProgressEvent<FileReader>;
|
||||
|
@ -653,328 +368,6 @@ declare interface Crypto {
|
|||
): T;
|
||||
}
|
||||
|
||||
type FormDataEntryValue = File | string;
|
||||
|
||||
/** Provides a way to easily construct a set of key/value pairs representing
|
||||
* form fields and their values, which can then be easily sent using the
|
||||
* XMLHttpRequest.send() method. It uses the same format a form would use if the
|
||||
* encoding type were set to "multipart/form-data". */
|
||||
interface FormData extends DomIterable<string, FormDataEntryValue> {
|
||||
append(name: string, value: string | Blob, fileName?: string): void;
|
||||
delete(name: string): void;
|
||||
get(name: string): FormDataEntryValue | null;
|
||||
getAll(name: string): FormDataEntryValue[];
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string | Blob, fileName?: string): void;
|
||||
}
|
||||
|
||||
declare const FormData: {
|
||||
prototype: FormData;
|
||||
// TODO(ry) FormData constructor is non-standard.
|
||||
// new(form?: HTMLFormElement): FormData;
|
||||
new (): FormData;
|
||||
};
|
||||
|
||||
interface Body {
|
||||
/** A simple getter used to expose a `ReadableStream` of the body contents. */
|
||||
readonly body: ReadableStream<Uint8Array> | null;
|
||||
/** Stores a `Boolean` that declares whether the body has been used in a
|
||||
* response yet.
|
||||
*/
|
||||
readonly bodyUsed: boolean;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with an `ArrayBuffer`.
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `Blob`.
|
||||
*/
|
||||
blob(): Promise<Blob>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `FormData` object.
|
||||
*/
|
||||
formData(): Promise<FormData>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with the result of parsing the body text as JSON.
|
||||
*/
|
||||
json(): Promise<any>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `USVString` (text).
|
||||
*/
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
type HeadersInit = Headers | string[][] | Record<string, string>;
|
||||
|
||||
/** This Fetch API interface allows you to perform various actions on HTTP
|
||||
* request and response headers. These actions include retrieving, setting,
|
||||
* adding to, and removing. A Headers object has an associated header list,
|
||||
* which is initially empty and consists of zero or more name and value pairs.
|
||||
* You can add to this using methods like append() (see Examples.) In all
|
||||
* methods of this interface, header names are matched by case-insensitive byte
|
||||
* sequence. */
|
||||
interface Headers {
|
||||
append(name: string, value: string): void;
|
||||
delete(name: string): void;
|
||||
get(name: string): string | null;
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string): void;
|
||||
forEach(
|
||||
callbackfn: (value: string, key: string, parent: Headers) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface Headers extends DomIterable<string, string> {
|
||||
/** Appends a new value onto an existing header inside a `Headers` object, or
|
||||
* adds the header if it does not already exist.
|
||||
*/
|
||||
append(name: string, value: string): void;
|
||||
/** Deletes a header from a `Headers` object. */
|
||||
delete(name: string): void;
|
||||
/** Returns an iterator allowing to go through all key/value pairs
|
||||
* contained in this Headers object. The both the key and value of each pairs
|
||||
* are ByteString objects.
|
||||
*/
|
||||
entries(): IterableIterator<[string, string]>;
|
||||
/** Returns a `ByteString` sequence of all the values of a header within a
|
||||
* `Headers` object with a given name.
|
||||
*/
|
||||
get(name: string): string | null;
|
||||
/** Returns a boolean stating whether a `Headers` object contains a certain
|
||||
* header.
|
||||
*/
|
||||
has(name: string): boolean;
|
||||
/** Returns an iterator allowing to go through all keys contained in
|
||||
* this Headers object. The keys are ByteString objects.
|
||||
*/
|
||||
keys(): IterableIterator<string>;
|
||||
/** Sets a new value for an existing header inside a Headers object, or adds
|
||||
* the header if it does not already exist.
|
||||
*/
|
||||
set(name: string, value: string): void;
|
||||
/** Returns an iterator allowing to go through all values contained in
|
||||
* this Headers object. The values are ByteString objects.
|
||||
*/
|
||||
values(): IterableIterator<string>;
|
||||
forEach(
|
||||
callbackfn: (value: string, key: string, parent: this) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
/** The Symbol.iterator well-known symbol specifies the default
|
||||
* iterator for this Headers object
|
||||
*/
|
||||
[Symbol.iterator](): IterableIterator<[string, string]>;
|
||||
}
|
||||
|
||||
declare const Headers: {
|
||||
prototype: Headers;
|
||||
new (init?: HeadersInit): Headers;
|
||||
};
|
||||
|
||||
type RequestInfo = Request | string;
|
||||
type RequestCache =
|
||||
| "default"
|
||||
| "force-cache"
|
||||
| "no-cache"
|
||||
| "no-store"
|
||||
| "only-if-cached"
|
||||
| "reload";
|
||||
type RequestCredentials = "include" | "omit" | "same-origin";
|
||||
type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
|
||||
type RequestRedirect = "error" | "follow" | "manual";
|
||||
type ReferrerPolicy =
|
||||
| ""
|
||||
| "no-referrer"
|
||||
| "no-referrer-when-downgrade"
|
||||
| "origin"
|
||||
| "origin-when-cross-origin"
|
||||
| "same-origin"
|
||||
| "strict-origin"
|
||||
| "strict-origin-when-cross-origin"
|
||||
| "unsafe-url";
|
||||
type BodyInit =
|
||||
| Blob
|
||||
| BufferSource
|
||||
| FormData
|
||||
| URLSearchParams
|
||||
| ReadableStream<Uint8Array>
|
||||
| string;
|
||||
type RequestDestination =
|
||||
| ""
|
||||
| "audio"
|
||||
| "audioworklet"
|
||||
| "document"
|
||||
| "embed"
|
||||
| "font"
|
||||
| "image"
|
||||
| "manifest"
|
||||
| "object"
|
||||
| "paintworklet"
|
||||
| "report"
|
||||
| "script"
|
||||
| "sharedworker"
|
||||
| "style"
|
||||
| "track"
|
||||
| "video"
|
||||
| "worker"
|
||||
| "xslt";
|
||||
|
||||
interface RequestInit {
|
||||
/**
|
||||
* A BodyInit object or null to set request's body.
|
||||
*/
|
||||
body?: BodyInit | null;
|
||||
/**
|
||||
* A string indicating how the request will interact with the browser's cache
|
||||
* to set request's cache.
|
||||
*/
|
||||
cache?: RequestCache;
|
||||
/**
|
||||
* A string indicating whether credentials will be sent with the request
|
||||
* always, never, or only when sent to a same-origin URL. Sets request's
|
||||
* credentials.
|
||||
*/
|
||||
credentials?: RequestCredentials;
|
||||
/**
|
||||
* A Headers object, an object literal, or an array of two-item arrays to set
|
||||
* request's headers.
|
||||
*/
|
||||
headers?: HeadersInit;
|
||||
/**
|
||||
* A cryptographic hash of the resource to be fetched by request. Sets
|
||||
* request's integrity.
|
||||
*/
|
||||
integrity?: string;
|
||||
/**
|
||||
* A boolean to set request's keepalive.
|
||||
*/
|
||||
keepalive?: boolean;
|
||||
/**
|
||||
* A string to set request's method.
|
||||
*/
|
||||
method?: string;
|
||||
/**
|
||||
* A string to indicate whether the request will use CORS, or will be
|
||||
* restricted to same-origin URLs. Sets request's mode.
|
||||
*/
|
||||
mode?: RequestMode;
|
||||
/**
|
||||
* A string indicating whether request follows redirects, results in an error
|
||||
* upon encountering a redirect, or returns the redirect (in an opaque
|
||||
* fashion). Sets request's redirect.
|
||||
*/
|
||||
redirect?: RequestRedirect;
|
||||
/**
|
||||
* A string whose value is a same-origin URL, "about:client", or the empty
|
||||
* string, to set request's referrer.
|
||||
*/
|
||||
referrer?: string;
|
||||
/**
|
||||
* A referrer policy to set request's referrerPolicy.
|
||||
*/
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
/**
|
||||
* An AbortSignal to set request's signal.
|
||||
*/
|
||||
signal?: AbortSignal | null;
|
||||
/**
|
||||
* Can only be null. Used to disassociate request from any Window.
|
||||
*/
|
||||
window?: any;
|
||||
}
|
||||
|
||||
/** This Fetch API interface represents a resource request. */
|
||||
interface Request extends Body {
|
||||
/**
|
||||
* Returns the cache mode associated with request, which is a string
|
||||
* indicating how the request will interact with the browser's cache when
|
||||
* fetching.
|
||||
*/
|
||||
readonly cache: RequestCache;
|
||||
/**
|
||||
* Returns the credentials mode associated with request, which is a string
|
||||
* indicating whether credentials will be sent with the request always, never,
|
||||
* or only when sent to a same-origin URL.
|
||||
*/
|
||||
readonly credentials: RequestCredentials;
|
||||
/**
|
||||
* Returns the kind of resource requested by request, e.g., "document" or "script".
|
||||
*/
|
||||
readonly destination: RequestDestination;
|
||||
/**
|
||||
* Returns a Headers object consisting of the headers associated with request.
|
||||
* Note that headers added in the network layer by the user agent will not be
|
||||
* accounted for in this object, e.g., the "Host" header.
|
||||
*/
|
||||
readonly headers: Headers;
|
||||
/**
|
||||
* Returns request's subresource integrity metadata, which is a cryptographic
|
||||
* hash of the resource being fetched. Its value consists of multiple hashes
|
||||
* separated by whitespace. [SRI]
|
||||
*/
|
||||
readonly integrity: string;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a history
|
||||
* navigation (a.k.a. back-forward navigation).
|
||||
*/
|
||||
readonly isHistoryNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a reload
|
||||
* navigation.
|
||||
*/
|
||||
readonly isReloadNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request can outlive the global
|
||||
* in which it was created.
|
||||
*/
|
||||
readonly keepalive: boolean;
|
||||
/**
|
||||
* Returns request's HTTP method, which is "GET" by default.
|
||||
*/
|
||||
readonly method: string;
|
||||
/**
|
||||
* Returns the mode associated with request, which is a string indicating
|
||||
* whether the request will use CORS, or will be restricted to same-origin
|
||||
* URLs.
|
||||
*/
|
||||
readonly mode: RequestMode;
|
||||
/**
|
||||
* Returns the redirect mode associated with request, which is a string
|
||||
* indicating how redirects for the request will be handled during fetching. A
|
||||
* request will follow redirects by default.
|
||||
*/
|
||||
readonly redirect: RequestRedirect;
|
||||
/**
|
||||
* Returns the referrer of request. Its value can be a same-origin URL if
|
||||
* explicitly set in init, the empty string to indicate no referrer, and
|
||||
* "about:client" when defaulting to the global's default. This is used during
|
||||
* fetching to determine the value of the `Referer` header of the request
|
||||
* being made.
|
||||
*/
|
||||
readonly referrer: string;
|
||||
/**
|
||||
* Returns the referrer policy associated with request. This is used during
|
||||
* fetching to compute the value of the request's referrer.
|
||||
*/
|
||||
readonly referrerPolicy: ReferrerPolicy;
|
||||
/**
|
||||
* Returns the signal associated with request, which is an AbortSignal object
|
||||
* indicating whether or not request has been aborted, and its abort event
|
||||
* handler.
|
||||
*/
|
||||
readonly signal: AbortSignal;
|
||||
/**
|
||||
* Returns the URL of request as a string.
|
||||
*/
|
||||
readonly url: string;
|
||||
clone(): Request;
|
||||
}
|
||||
|
||||
declare const Request: {
|
||||
prototype: Request;
|
||||
new (input: RequestInfo, init?: RequestInit): Request;
|
||||
};
|
||||
|
||||
interface ResponseInit {
|
||||
headers?: HeadersInit;
|
||||
|
@ -1003,26 +396,6 @@ interface Response extends Body {
|
|||
clone(): Response;
|
||||
}
|
||||
|
||||
declare const Response: {
|
||||
prototype: Response;
|
||||
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
||||
error(): Response;
|
||||
redirect(url: string, status?: number): Response;
|
||||
};
|
||||
|
||||
/** Fetch a resource from the network. It returns a Promise that resolves to the
|
||||
* Response to that request, whether it is successful or not.
|
||||
*
|
||||
* const response = await fetch("http://my.json.host/data.json");
|
||||
* console.log(response.status); // e.g. 200
|
||||
* console.log(response.statusText); // e.g. "OK"
|
||||
* const jsonData = await response.json();
|
||||
*/
|
||||
declare function fetch(
|
||||
input: Request | URL | string,
|
||||
init?: RequestInit,
|
||||
): Promise<Response>;
|
||||
|
||||
interface URLSearchParams {
|
||||
/** Appends a specified key/value pair as a new search parameter.
|
||||
*
|
||||
|
|
|
@ -10,6 +10,7 @@ pub static COMPILER_SNAPSHOT: &[u8] =
|
|||
include_bytes!(concat!(env!("OUT_DIR"), "/COMPILER_SNAPSHOT.bin"));
|
||||
pub static DENO_NS_LIB: &str = include_str!("dts/lib.deno.ns.d.ts");
|
||||
pub static DENO_WEB_LIB: &str = include_str!(env!("DENO_WEB_LIB_PATH"));
|
||||
pub static DENO_FETCH_LIB: &str = include_str!(env!("DENO_FETCH_LIB_PATH"));
|
||||
pub static SHARED_GLOBALS_LIB: &str =
|
||||
include_str!("dts/lib.deno.shared_globals.d.ts");
|
||||
pub static WINDOW_LIB: &str = include_str!("dts/lib.deno.window.d.ts");
|
||||
|
|
|
@ -140,9 +140,10 @@ fn print_cache_info(
|
|||
|
||||
fn get_types(unstable: bool) -> String {
|
||||
let mut types = format!(
|
||||
"{}\n{}\n{}\n{}",
|
||||
"{}\n{}\n{}\n{}\n{}",
|
||||
crate::js::DENO_NS_LIB,
|
||||
crate::js::DENO_WEB_LIB,
|
||||
crate::js::DENO_FETCH_LIB,
|
||||
crate::js::SHARED_GLOBALS_LIB,
|
||||
crate::js::WINDOW_LIB,
|
||||
);
|
||||
|
|
195
cli/ops/fetch.rs
195
cli/ops/fetch.rs
|
@ -1,191 +1,12 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::http_util::create_http_client;
|
||||
use deno_core::error::bad_resource_id;
|
||||
use deno_core::error::type_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::url;
|
||||
use deno_core::BufVec;
|
||||
use deno_core::OpState;
|
||||
use deno_core::ZeroCopyBuf;
|
||||
use http::header::HeaderName;
|
||||
use http::header::HeaderValue;
|
||||
use http::Method;
|
||||
use reqwest::Client;
|
||||
use reqwest::Response;
|
||||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::From;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use crate::state::CliState;
|
||||
|
||||
pub fn init(rt: &mut deno_core::JsRuntime) {
|
||||
super::reg_json_async(rt, "op_fetch", op_fetch);
|
||||
super::reg_json_async(rt, "op_fetch_read", op_fetch_read);
|
||||
super::reg_json_sync(rt, "op_create_http_client", op_create_http_client);
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FetchArgs {
|
||||
method: Option<String>,
|
||||
url: String,
|
||||
headers: Vec<(String, String)>,
|
||||
client_rid: Option<u32>,
|
||||
}
|
||||
|
||||
async fn op_fetch(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
let args: FetchArgs = serde_json::from_value(args)?;
|
||||
let url = args.url;
|
||||
|
||||
let client = if let Some(rid) = args.client_rid {
|
||||
let state = state.borrow();
|
||||
let r = state
|
||||
.resource_table
|
||||
.get::<HttpClientResource>(rid)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
r.client.clone()
|
||||
} else {
|
||||
let state_ = state.borrow();
|
||||
let client = state_.borrow::<reqwest::Client>();
|
||||
client.clone()
|
||||
};
|
||||
|
||||
let method = match args.method {
|
||||
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
|
||||
None => Method::GET,
|
||||
};
|
||||
|
||||
let url_ = url::Url::parse(&url)?;
|
||||
|
||||
// Check scheme before asking for net permission
|
||||
let scheme = url_.scheme();
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return Err(type_error(format!("scheme '{}' not supported", scheme)));
|
||||
}
|
||||
|
||||
super::cli_state2(&state).check_net_url(&url_)?;
|
||||
|
||||
let mut request = client.request(method, url_);
|
||||
|
||||
match data.len() {
|
||||
0 => {}
|
||||
1 => request = request.body(Vec::from(&*data[0])),
|
||||
_ => panic!("Invalid number of arguments"),
|
||||
}
|
||||
|
||||
for (key, value) in args.headers {
|
||||
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
|
||||
let v = HeaderValue::from_str(&value).unwrap();
|
||||
request = request.header(name, v);
|
||||
}
|
||||
debug!("Before fetch {}", url);
|
||||
|
||||
let res = request.send().await?;
|
||||
|
||||
debug!("Fetch response {}", url);
|
||||
let status = res.status();
|
||||
let mut res_headers = Vec::new();
|
||||
for (key, val) in res.headers().iter() {
|
||||
res_headers.push((key.to_string(), val.to_str().unwrap().to_owned()));
|
||||
}
|
||||
|
||||
let rid = state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
.add("httpBody", Box::new(res));
|
||||
|
||||
Ok(json!({
|
||||
"bodyRid": rid,
|
||||
"status": status.as_u16(),
|
||||
"statusText": status.canonical_reason().unwrap_or(""),
|
||||
"headers": res_headers
|
||||
}))
|
||||
}
|
||||
|
||||
async fn op_fetch_read(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
_data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Args {
|
||||
rid: u32,
|
||||
}
|
||||
|
||||
let args: Args = serde_json::from_value(args)?;
|
||||
let rid = args.rid;
|
||||
|
||||
use futures::future::poll_fn;
|
||||
use futures::ready;
|
||||
use futures::FutureExt;
|
||||
let f = poll_fn(move |cx| {
|
||||
let mut state = state.borrow_mut();
|
||||
let response = state
|
||||
.resource_table
|
||||
.get_mut::<Response>(rid as u32)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
|
||||
let mut chunk_fut = response.chunk().boxed_local();
|
||||
let r = ready!(chunk_fut.poll_unpin(cx))?;
|
||||
if let Some(chunk) = r {
|
||||
Ok(json!({ "chunk": &*chunk })).into()
|
||||
} else {
|
||||
Ok(json!({ "chunk": null })).into()
|
||||
}
|
||||
});
|
||||
f.await
|
||||
/*
|
||||
// I'm programming this as I want it to be programmed, even though it might be
|
||||
// incorrect, normally we would use poll_fn here. We need to make this await pattern work.
|
||||
let chunk = response.chunk().await?;
|
||||
if let Some(chunk) = chunk {
|
||||
// TODO(ry) This is terribly inefficient. Make this zero-copy.
|
||||
Ok(json!({ "chunk": &*chunk }))
|
||||
} else {
|
||||
Ok(json!({ "chunk": null }))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
struct HttpClientResource {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl HttpClientResource {
|
||||
fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(default)]
|
||||
struct CreateHttpClientOptions {
|
||||
ca_file: Option<String>,
|
||||
}
|
||||
|
||||
fn op_create_http_client(
|
||||
state: &mut OpState,
|
||||
args: Value,
|
||||
_zero_copy: &mut [ZeroCopyBuf],
|
||||
) -> Result<Value, AnyError> {
|
||||
let args: CreateHttpClientOptions = serde_json::from_value(args)?;
|
||||
|
||||
if let Some(ca_file) = args.ca_file.clone() {
|
||||
super::cli_state(state).check_read(&PathBuf::from(ca_file))?;
|
||||
}
|
||||
|
||||
let client = create_http_client(args.ca_file.as_deref()).unwrap();
|
||||
|
||||
let rid = state
|
||||
.resource_table
|
||||
.add("httpClient", Box::new(HttpClientResource::new(client)));
|
||||
Ok(json!(rid))
|
||||
super::reg_json_async(rt, "op_fetch", deno_fetch::op_fetch::<CliState>);
|
||||
super::reg_json_async(rt, "op_fetch_read", deno_fetch::op_fetch_read);
|
||||
super::reg_json_sync(
|
||||
rt,
|
||||
"op_create_http_client",
|
||||
deno_fetch::op_create_http_client::<CliState>,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
function isTypedArray(x) {
|
||||
return ArrayBuffer.isView(x) && !(x instanceof DataView);
|
||||
}
|
||||
|
||||
function isInvalidDate(x) {
|
||||
return isNaN(x.getTime());
|
||||
}
|
||||
|
@ -149,40 +145,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
function getHeaderValueParams(value) {
|
||||
const params = new Map();
|
||||
// Forced to do so for some Map constructor param mismatch
|
||||
value
|
||||
.split(";")
|
||||
.slice(1)
|
||||
.map((s) => s.trim().split("="))
|
||||
.filter((arr) => arr.length > 1)
|
||||
.map(([k, v]) => [k, v.replace(/^"([^"]*)"$/, "$1")])
|
||||
.forEach(([k, v]) => params.set(k, v));
|
||||
return params;
|
||||
}
|
||||
|
||||
function hasHeaderValueOf(s, value) {
|
||||
return new RegExp(`^${value}[\t\s]*;?`).test(s);
|
||||
}
|
||||
|
||||
/** An internal function which provides a function name for some generated
|
||||
* functions, so stack traces are a bit more readable.
|
||||
*/
|
||||
function setFunctionName(fn, value) {
|
||||
Object.defineProperty(fn, "name", { value, configurable: true });
|
||||
}
|
||||
|
||||
window.__bootstrap.webUtil = {
|
||||
isTypedArray,
|
||||
isInvalidDate,
|
||||
requiredArguments,
|
||||
immutableDefine,
|
||||
hasOwnProperty,
|
||||
cloneValue,
|
||||
defineEnumerableProps,
|
||||
getHeaderValueParams,
|
||||
hasHeaderValueOf,
|
||||
setFunctionName,
|
||||
};
|
||||
})(this);
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
} = window.__bootstrap.colors;
|
||||
|
||||
const {
|
||||
isTypedArray,
|
||||
isInvalidDate,
|
||||
hasOwnProperty,
|
||||
} = window.__bootstrap.webUtil;
|
||||
|
@ -23,6 +22,10 @@
|
|||
// Copyright Joyent, Inc. and other Node contributors. MIT license.
|
||||
// Forked from Node's lib/internal/cli_table.js
|
||||
|
||||
function isTypedArray(x) {
|
||||
return ArrayBuffer.isView(x) && !(x instanceof DataView);
|
||||
}
|
||||
|
||||
const tableChars = {
|
||||
middleMiddle: "─",
|
||||
rowMiddle: "┼",
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const { build } = window.__bootstrap.build;
|
||||
const { ReadableStream } = window.__bootstrap.streams;
|
||||
|
||||
const bytesSymbol = Symbol("bytes");
|
||||
|
||||
function containsOnlyASCII(str) {
|
||||
if (typeof str !== "string") {
|
||||
return false;
|
||||
}
|
||||
return /^[\x00-\x7F]*$/.test(str);
|
||||
}
|
||||
|
||||
function convertLineEndingsToNative(s) {
|
||||
const nativeLineEnd = build.os == "windows" ? "\r\n" : "\n";
|
||||
|
||||
let position = 0;
|
||||
|
||||
let collectionResult = collectSequenceNotCRLF(s, position);
|
||||
|
||||
let token = collectionResult.collected;
|
||||
position = collectionResult.newPosition;
|
||||
|
||||
let result = token;
|
||||
|
||||
while (position < s.length) {
|
||||
const c = s.charAt(position);
|
||||
if (c == "\r") {
|
||||
result += nativeLineEnd;
|
||||
position++;
|
||||
if (position < s.length && s.charAt(position) == "\n") {
|
||||
position++;
|
||||
}
|
||||
} else if (c == "\n") {
|
||||
position++;
|
||||
result += nativeLineEnd;
|
||||
}
|
||||
|
||||
collectionResult = collectSequenceNotCRLF(s, position);
|
||||
|
||||
token = collectionResult.collected;
|
||||
position = collectionResult.newPosition;
|
||||
|
||||
result += token;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function collectSequenceNotCRLF(
|
||||
s,
|
||||
position,
|
||||
) {
|
||||
const start = position;
|
||||
for (
|
||||
let c = s.charAt(position);
|
||||
position < s.length && !(c == "\r" || c == "\n");
|
||||
c = s.charAt(++position)
|
||||
);
|
||||
return { collected: s.slice(start, position), newPosition: position };
|
||||
}
|
||||
|
||||
function toUint8Arrays(
|
||||
blobParts,
|
||||
doNormalizeLineEndingsToNative,
|
||||
) {
|
||||
const ret = [];
|
||||
const enc = new TextEncoder();
|
||||
for (const element of blobParts) {
|
||||
if (typeof element === "string") {
|
||||
let str = element;
|
||||
if (doNormalizeLineEndingsToNative) {
|
||||
str = convertLineEndingsToNative(element);
|
||||
}
|
||||
ret.push(enc.encode(str));
|
||||
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
||||
} else if (element instanceof Blob) {
|
||||
ret.push(element[bytesSymbol]);
|
||||
} else if (element instanceof Uint8Array) {
|
||||
ret.push(element);
|
||||
} else if (element instanceof Uint16Array) {
|
||||
const uint8 = new Uint8Array(element.buffer);
|
||||
ret.push(uint8);
|
||||
} else if (element instanceof Uint32Array) {
|
||||
const uint8 = new Uint8Array(element.buffer);
|
||||
ret.push(uint8);
|
||||
} else if (ArrayBuffer.isView(element)) {
|
||||
// Convert view to Uint8Array.
|
||||
const uint8 = new Uint8Array(element.buffer);
|
||||
ret.push(uint8);
|
||||
} else if (element instanceof ArrayBuffer) {
|
||||
// Create a new Uint8Array view for the given ArrayBuffer.
|
||||
const uint8 = new Uint8Array(element);
|
||||
ret.push(uint8);
|
||||
} else {
|
||||
ret.push(enc.encode(String(element)));
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function processBlobParts(
|
||||
blobParts,
|
||||
options,
|
||||
) {
|
||||
const normalizeLineEndingsToNative = options.ending === "native";
|
||||
// ArrayBuffer.transfer is not yet implemented in V8, so we just have to
|
||||
// pre compute size of the array buffer and do some sort of static allocation
|
||||
// instead of dynamic allocation.
|
||||
const uint8Arrays = toUint8Arrays(blobParts, normalizeLineEndingsToNative);
|
||||
const byteLength = uint8Arrays
|
||||
.map((u8) => u8.byteLength)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
const ab = new ArrayBuffer(byteLength);
|
||||
const bytes = new Uint8Array(ab);
|
||||
let courser = 0;
|
||||
for (const u8 of uint8Arrays) {
|
||||
bytes.set(u8, courser);
|
||||
courser += u8.byteLength;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
function getStream(blobBytes) {
|
||||
// TODO: Align to spec https://fetch.spec.whatwg.org/#concept-construct-readablestream
|
||||
return new ReadableStream({
|
||||
type: "bytes",
|
||||
start: (controller) => {
|
||||
controller.enqueue(blobBytes);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function readBytes(
|
||||
reader,
|
||||
) {
|
||||
const chunks = [];
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (!done && value instanceof Uint8Array) {
|
||||
chunks.push(value);
|
||||
} else if (done) {
|
||||
const size = chunks.reduce((p, i) => p + i.byteLength, 0);
|
||||
const bytes = new Uint8Array(size);
|
||||
let offs = 0;
|
||||
for (const chunk of chunks) {
|
||||
bytes.set(chunk, offs);
|
||||
offs += chunk.byteLength;
|
||||
}
|
||||
return bytes.buffer;
|
||||
} else {
|
||||
throw new TypeError("Invalid reader result.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A WeakMap holding blob to byte array mapping.
|
||||
// Ensures it does not impact garbage collection.
|
||||
const blobBytesWeakMap = new WeakMap();
|
||||
|
||||
class Blob {
|
||||
constructor(blobParts, options) {
|
||||
if (arguments.length === 0) {
|
||||
this[bytesSymbol] = new Uint8Array();
|
||||
return;
|
||||
}
|
||||
|
||||
const { ending = "transparent", type = "" } = options ?? {};
|
||||
// Normalize options.type.
|
||||
let normalizedType = type;
|
||||
if (!containsOnlyASCII(type)) {
|
||||
normalizedType = "";
|
||||
} else {
|
||||
if (type.length) {
|
||||
for (let i = 0; i < type.length; ++i) {
|
||||
const char = type[i];
|
||||
if (char < "\u0020" || char > "\u007E") {
|
||||
normalizedType = "";
|
||||
break;
|
||||
}
|
||||
}
|
||||
normalizedType = type.toLowerCase();
|
||||
}
|
||||
}
|
||||
const bytes = processBlobParts(blobParts, { ending, type });
|
||||
// Set Blob object's properties.
|
||||
this[bytesSymbol] = bytes;
|
||||
this.size = bytes.byteLength;
|
||||
this.type = normalizedType;
|
||||
}
|
||||
|
||||
slice(start, end, contentType) {
|
||||
return new Blob([this[bytesSymbol].slice(start, end)], {
|
||||
type: contentType || this.type,
|
||||
});
|
||||
}
|
||||
|
||||
stream() {
|
||||
return getStream(this[bytesSymbol]);
|
||||
}
|
||||
|
||||
async text() {
|
||||
const reader = getStream(this[bytesSymbol]).getReader();
|
||||
const decoder = new TextDecoder();
|
||||
return decoder.decode(await readBytes(reader));
|
||||
}
|
||||
|
||||
arrayBuffer() {
|
||||
return readBytes(getStream(this[bytesSymbol]).getReader());
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.blob = {
|
||||
Blob,
|
||||
bytesSymbol,
|
||||
containsOnlyASCII,
|
||||
blobBytesWeakMap,
|
||||
};
|
||||
})(this);
|
|
@ -1,50 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const customInspect = Symbol.for("Deno.customInspect");
|
||||
|
||||
class CountQueuingStrategy {
|
||||
constructor({ highWaterMark }) {
|
||||
this.highWaterMark = highWaterMark;
|
||||
}
|
||||
|
||||
size() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[customInspect]() {
|
||||
return `${this.constructor.name} { highWaterMark: ${
|
||||
String(this.highWaterMark)
|
||||
}, size: f }`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(CountQueuingStrategy.prototype, "size", {
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
class ByteLengthQueuingStrategy {
|
||||
constructor({ highWaterMark }) {
|
||||
this.highWaterMark = highWaterMark;
|
||||
}
|
||||
|
||||
size(chunk) {
|
||||
return chunk.byteLength;
|
||||
}
|
||||
|
||||
[customInspect]() {
|
||||
return `${this.constructor.name} { highWaterMark: ${
|
||||
String(this.highWaterMark)
|
||||
}, size: f }`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(ByteLengthQueuingStrategy.prototype, "size", {
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
window.__bootstrap.queuingStrategy = {
|
||||
CountQueuingStrategy,
|
||||
ByteLengthQueuingStrategy,
|
||||
};
|
||||
})(this);
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const blob = window.__bootstrap.blob;
|
||||
|
||||
class DomFile extends blob.Blob {
|
||||
constructor(
|
||||
fileBits,
|
||||
fileName,
|
||||
options,
|
||||
) {
|
||||
const { lastModified = Date.now(), ...blobPropertyBag } = options ?? {};
|
||||
super(fileBits, blobPropertyBag);
|
||||
|
||||
// 4.1.2.1 Replace any "/" character (U+002F SOLIDUS)
|
||||
// with a ":" (U + 003A COLON)
|
||||
this.name = String(fileName).replace(/\u002F/g, "\u003A");
|
||||
// 4.1.3.3 If lastModified is not provided, set lastModified to the current
|
||||
// date and time represented in number of milliseconds since the Unix Epoch.
|
||||
this.lastModified = lastModified;
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.domFile = {
|
||||
DomFile,
|
||||
};
|
||||
})(this);
|
|
@ -1,116 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const blob = window.__bootstrap.blob;
|
||||
const domFile = window.__bootstrap.domFile;
|
||||
const { DomIterableMixin } = window.__bootstrap.domIterable;
|
||||
const { requiredArguments } = window.__bootstrap.webUtil;
|
||||
|
||||
const dataSymbol = Symbol("data");
|
||||
|
||||
function parseFormDataValue(value, filename) {
|
||||
if (value instanceof domFile.DomFile) {
|
||||
return new domFile.DomFile([value], filename || value.name, {
|
||||
type: value.type,
|
||||
lastModified: value.lastModified,
|
||||
});
|
||||
} else if (value instanceof blob.Blob) {
|
||||
return new domFile.DomFile([value], filename || "blob", {
|
||||
type: value.type,
|
||||
});
|
||||
} else {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
class FormDataBase {
|
||||
[dataSymbol] = [];
|
||||
|
||||
append(name, value, filename) {
|
||||
requiredArguments("FormData.append", arguments.length, 2);
|
||||
name = String(name);
|
||||
this[dataSymbol].push([name, parseFormDataValue(value, filename)]);
|
||||
}
|
||||
|
||||
delete(name) {
|
||||
requiredArguments("FormData.delete", arguments.length, 1);
|
||||
name = String(name);
|
||||
let i = 0;
|
||||
while (i < this[dataSymbol].length) {
|
||||
if (this[dataSymbol][i][0] === name) {
|
||||
this[dataSymbol].splice(i, 1);
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAll(name) {
|
||||
requiredArguments("FormData.getAll", arguments.length, 1);
|
||||
name = String(name);
|
||||
const values = [];
|
||||
for (const entry of this[dataSymbol]) {
|
||||
if (entry[0] === name) {
|
||||
values.push(entry[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
get(name) {
|
||||
requiredArguments("FormData.get", arguments.length, 1);
|
||||
name = String(name);
|
||||
for (const entry of this[dataSymbol]) {
|
||||
if (entry[0] === name) {
|
||||
return entry[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
has(name) {
|
||||
requiredArguments("FormData.has", arguments.length, 1);
|
||||
name = String(name);
|
||||
return this[dataSymbol].some((entry) => entry[0] === name);
|
||||
}
|
||||
|
||||
set(name, value, filename) {
|
||||
requiredArguments("FormData.set", arguments.length, 2);
|
||||
name = String(name);
|
||||
|
||||
// If there are any entries in the context object’s entry list whose name
|
||||
// is name, replace the first such entry with entry and remove the others
|
||||
let found = false;
|
||||
let i = 0;
|
||||
while (i < this[dataSymbol].length) {
|
||||
if (this[dataSymbol][i][0] === name) {
|
||||
if (!found) {
|
||||
this[dataSymbol][i][1] = parseFormDataValue(value, filename);
|
||||
found = true;
|
||||
} else {
|
||||
this[dataSymbol].splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// Otherwise, append entry to the context object’s entry list.
|
||||
if (!found) {
|
||||
this[dataSymbol].push([name, parseFormDataValue(value, filename)]);
|
||||
}
|
||||
}
|
||||
|
||||
get [Symbol.toStringTag]() {
|
||||
return "FormData";
|
||||
}
|
||||
}
|
||||
|
||||
class FormData extends DomIterableMixin(FormDataBase, dataSymbol) {}
|
||||
|
||||
window.__bootstrap.formData = {
|
||||
FormData,
|
||||
};
|
||||
})(this);
|
|
@ -1,199 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const { Buffer } = window.__bootstrap.buffer;
|
||||
const { bytesSymbol, Blob } = window.__bootstrap.blob;
|
||||
const { DomFile } = window.__bootstrap.domFile;
|
||||
const { getHeaderValueParams } = window.__bootstrap.webUtil;
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
const encoder = new TextEncoder();
|
||||
const CR = "\r".charCodeAt(0);
|
||||
const LF = "\n".charCodeAt(0);
|
||||
|
||||
class MultipartBuilder {
|
||||
constructor(formData, boundary) {
|
||||
this.formData = formData;
|
||||
this.boundary = boundary ?? this.#createBoundary();
|
||||
this.writer = new Buffer();
|
||||
}
|
||||
|
||||
getContentType() {
|
||||
return `multipart/form-data; boundary=${this.boundary}`;
|
||||
}
|
||||
|
||||
getBody() {
|
||||
for (const [fieldName, fieldValue] of this.formData.entries()) {
|
||||
if (fieldValue instanceof DomFile) {
|
||||
this.#writeFile(fieldName, fieldValue);
|
||||
} else this.#writeField(fieldName, fieldValue);
|
||||
}
|
||||
|
||||
this.writer.writeSync(encoder.encode(`\r\n--${this.boundary}--`));
|
||||
|
||||
return this.writer.bytes();
|
||||
}
|
||||
|
||||
#createBoundary = () => {
|
||||
return (
|
||||
"----------" +
|
||||
Array.from(Array(32))
|
||||
.map(() => Math.random().toString(36)[2] || 0)
|
||||
.join("")
|
||||
);
|
||||
};
|
||||
|
||||
#writeHeaders = (headers) => {
|
||||
let buf = this.writer.empty() ? "" : "\r\n";
|
||||
|
||||
buf += `--${this.boundary}\r\n`;
|
||||
for (const [key, value] of headers) {
|
||||
buf += `${key}: ${value}\r\n`;
|
||||
}
|
||||
buf += `\r\n`;
|
||||
|
||||
this.writer.writeSync(encoder.encode(buf));
|
||||
};
|
||||
|
||||
#writeFileHeaders = (
|
||||
field,
|
||||
filename,
|
||||
type,
|
||||
) => {
|
||||
const headers = [
|
||||
[
|
||||
"Content-Disposition",
|
||||
`form-data; name="${field}"; filename="${filename}"`,
|
||||
],
|
||||
["Content-Type", type || "application/octet-stream"],
|
||||
];
|
||||
return this.#writeHeaders(headers);
|
||||
};
|
||||
|
||||
#writeFieldHeaders = (field) => {
|
||||
const headers = [["Content-Disposition", `form-data; name="${field}"`]];
|
||||
return this.#writeHeaders(headers);
|
||||
};
|
||||
|
||||
#writeField = (field, value) => {
|
||||
this.#writeFieldHeaders(field);
|
||||
this.writer.writeSync(encoder.encode(value));
|
||||
};
|
||||
|
||||
#writeFile = (field, value) => {
|
||||
this.#writeFileHeaders(field, value.name, value.type);
|
||||
this.writer.writeSync(value[bytesSymbol]);
|
||||
};
|
||||
}
|
||||
|
||||
class MultipartParser {
|
||||
constructor(body, boundary) {
|
||||
if (!boundary) {
|
||||
throw new TypeError("multipart/form-data must provide a boundary");
|
||||
}
|
||||
|
||||
this.boundary = `--${boundary}`;
|
||||
this.body = body;
|
||||
this.boundaryChars = encoder.encode(this.boundary);
|
||||
}
|
||||
|
||||
#parseHeaders = (headersText) => {
|
||||
const headers = new Headers();
|
||||
const rawHeaders = headersText.split("\r\n");
|
||||
for (const rawHeader of rawHeaders) {
|
||||
const sepIndex = rawHeader.indexOf(":");
|
||||
if (sepIndex < 0) {
|
||||
continue; // Skip this header
|
||||
}
|
||||
const key = rawHeader.slice(0, sepIndex);
|
||||
const value = rawHeader.slice(sepIndex + 1);
|
||||
headers.set(key, value);
|
||||
}
|
||||
|
||||
return {
|
||||
headers,
|
||||
disposition: getHeaderValueParams(
|
||||
headers.get("Content-Disposition") ?? "",
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
parse() {
|
||||
const formData = new FormData();
|
||||
let headerText = "";
|
||||
let boundaryIndex = 0;
|
||||
let state = 0;
|
||||
let fileStart = 0;
|
||||
|
||||
for (let i = 0; i < this.body.length; i++) {
|
||||
const byte = this.body[i];
|
||||
const prevByte = this.body[i - 1];
|
||||
const isNewLine = byte === LF && prevByte === CR;
|
||||
|
||||
if (state === 1 || state === 2 || state == 3) {
|
||||
headerText += String.fromCharCode(byte);
|
||||
}
|
||||
if (state === 0 && isNewLine) {
|
||||
state = 1;
|
||||
} else if (state === 1 && isNewLine) {
|
||||
state = 2;
|
||||
const headersDone = this.body[i + 1] === CR &&
|
||||
this.body[i + 2] === LF;
|
||||
|
||||
if (headersDone) {
|
||||
state = 3;
|
||||
}
|
||||
} else if (state === 2 && isNewLine) {
|
||||
state = 3;
|
||||
} else if (state === 3 && isNewLine) {
|
||||
state = 4;
|
||||
fileStart = i + 1;
|
||||
} else if (state === 4) {
|
||||
if (this.boundaryChars[boundaryIndex] !== byte) {
|
||||
boundaryIndex = 0;
|
||||
} else {
|
||||
boundaryIndex++;
|
||||
}
|
||||
|
||||
if (boundaryIndex >= this.boundary.length) {
|
||||
const { headers, disposition } = this.#parseHeaders(headerText);
|
||||
const content = this.body.subarray(
|
||||
fileStart,
|
||||
i - boundaryIndex - 1,
|
||||
);
|
||||
// https://fetch.spec.whatwg.org/#ref-for-dom-body-formdata
|
||||
const filename = disposition.get("filename");
|
||||
const name = disposition.get("name");
|
||||
|
||||
state = 5;
|
||||
// Reset
|
||||
boundaryIndex = 0;
|
||||
headerText = "";
|
||||
|
||||
if (!name) {
|
||||
continue; // Skip, unknown name
|
||||
}
|
||||
|
||||
if (filename) {
|
||||
const blob = new Blob([content], {
|
||||
type: headers.get("Content-Type") || "application/octet-stream",
|
||||
});
|
||||
formData.append(name, blob, filename);
|
||||
} else {
|
||||
formData.append(name, decoder.decode(content));
|
||||
}
|
||||
}
|
||||
} else if (state === 5 && isNewLine) {
|
||||
state = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return formData;
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.multipart = {
|
||||
MultipartBuilder,
|
||||
MultipartParser,
|
||||
};
|
||||
})(this);
|
|
@ -1,220 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const { Blob } = window.__bootstrap.blob;
|
||||
const { ReadableStream, isReadableStreamDisturbed } =
|
||||
window.__bootstrap.streams;
|
||||
const { Buffer } = window.__bootstrap.buffer;
|
||||
const {
|
||||
getHeaderValueParams,
|
||||
hasHeaderValueOf,
|
||||
isTypedArray,
|
||||
} = window.__bootstrap.webUtil;
|
||||
const { MultipartParser } = window.__bootstrap.multipart;
|
||||
|
||||
function validateBodyType(owner, bodySource) {
|
||||
if (isTypedArray(bodySource)) {
|
||||
return true;
|
||||
} else if (bodySource instanceof ArrayBuffer) {
|
||||
return true;
|
||||
} else if (typeof bodySource === "string") {
|
||||
return true;
|
||||
} else if (bodySource instanceof ReadableStream) {
|
||||
return true;
|
||||
} else if (bodySource instanceof FormData) {
|
||||
return true;
|
||||
} else if (bodySource instanceof URLSearchParams) {
|
||||
return true;
|
||||
} else if (!bodySource) {
|
||||
return true; // null body is fine
|
||||
}
|
||||
throw new Error(
|
||||
`Bad ${owner.constructor.name} body type: ${bodySource.constructor.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
async function bufferFromStream(
|
||||
stream,
|
||||
size,
|
||||
) {
|
||||
const encoder = new TextEncoder();
|
||||
const buffer = new Buffer();
|
||||
|
||||
if (size) {
|
||||
// grow to avoid unnecessary allocations & copies
|
||||
buffer.grow(size);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await stream.read();
|
||||
|
||||
if (done) break;
|
||||
|
||||
if (typeof value === "string") {
|
||||
buffer.writeSync(encoder.encode(value));
|
||||
} else if (value instanceof ArrayBuffer) {
|
||||
buffer.writeSync(new Uint8Array(value));
|
||||
} else if (value instanceof Uint8Array) {
|
||||
buffer.writeSync(value);
|
||||
} else if (!value) {
|
||||
// noop for undefined
|
||||
} else {
|
||||
throw new Error("unhandled type on stream read");
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.bytes().buffer;
|
||||
}
|
||||
|
||||
function bodyToArrayBuffer(bodySource) {
|
||||
if (isTypedArray(bodySource)) {
|
||||
return bodySource.buffer;
|
||||
} else if (bodySource instanceof ArrayBuffer) {
|
||||
return bodySource;
|
||||
} else if (typeof bodySource === "string") {
|
||||
const enc = new TextEncoder();
|
||||
return enc.encode(bodySource).buffer;
|
||||
} else if (bodySource instanceof ReadableStream) {
|
||||
throw new Error(
|
||||
`Can't convert stream to ArrayBuffer (try bufferFromStream)`,
|
||||
);
|
||||
} else if (
|
||||
bodySource instanceof FormData ||
|
||||
bodySource instanceof URLSearchParams
|
||||
) {
|
||||
const enc = new TextEncoder();
|
||||
return enc.encode(bodySource.toString()).buffer;
|
||||
} else if (!bodySource) {
|
||||
return new ArrayBuffer(0);
|
||||
}
|
||||
throw new Error(
|
||||
`Body type not implemented: ${bodySource.constructor.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
const BodyUsedError =
|
||||
"Failed to execute 'clone' on 'Body': body is already used";
|
||||
|
||||
class Body {
|
||||
#contentType = "";
|
||||
#size = undefined;
|
||||
|
||||
constructor(_bodySource, meta) {
|
||||
validateBodyType(this, _bodySource);
|
||||
this._bodySource = _bodySource;
|
||||
this.#contentType = meta.contentType;
|
||||
this.#size = meta.size;
|
||||
this._stream = null;
|
||||
}
|
||||
|
||||
get body() {
|
||||
if (this._stream) {
|
||||
return this._stream;
|
||||
}
|
||||
|
||||
if (!this._bodySource) {
|
||||
return null;
|
||||
} else if (this._bodySource instanceof ReadableStream) {
|
||||
this._stream = this._bodySource;
|
||||
} else {
|
||||
const buf = bodyToArrayBuffer(this._bodySource);
|
||||
if (!(buf instanceof ArrayBuffer)) {
|
||||
throw new Error(
|
||||
`Expected ArrayBuffer from body`,
|
||||
);
|
||||
}
|
||||
|
||||
this._stream = new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(buf);
|
||||
controller.close();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return this._stream;
|
||||
}
|
||||
|
||||
get bodyUsed() {
|
||||
if (this.body && isReadableStreamDisturbed(this.body)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async blob() {
|
||||
return new Blob([await this.arrayBuffer()], {
|
||||
type: this.#contentType,
|
||||
});
|
||||
}
|
||||
|
||||
// ref: https://fetch.spec.whatwg.org/#body-mixin
|
||||
async formData() {
|
||||
const formData = new FormData();
|
||||
if (hasHeaderValueOf(this.#contentType, "multipart/form-data")) {
|
||||
const params = getHeaderValueParams(this.#contentType);
|
||||
|
||||
// ref: https://tools.ietf.org/html/rfc2046#section-5.1
|
||||
const boundary = params.get("boundary");
|
||||
const body = new Uint8Array(await this.arrayBuffer());
|
||||
const multipartParser = new MultipartParser(body, boundary);
|
||||
|
||||
return multipartParser.parse();
|
||||
} else if (
|
||||
hasHeaderValueOf(this.#contentType, "application/x-www-form-urlencoded")
|
||||
) {
|
||||
// From https://github.com/github/fetch/blob/master/fetch.js
|
||||
// Copyright (c) 2014-2016 GitHub, Inc. MIT License
|
||||
const body = await this.text();
|
||||
try {
|
||||
body
|
||||
.trim()
|
||||
.split("&")
|
||||
.forEach((bytes) => {
|
||||
if (bytes) {
|
||||
const split = bytes.split("=");
|
||||
const name = split.shift().replace(/\+/g, " ");
|
||||
const value = split.join("=").replace(/\+/g, " ");
|
||||
formData.append(
|
||||
decodeURIComponent(name),
|
||||
decodeURIComponent(value),
|
||||
);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
throw new TypeError("Invalid form urlencoded format");
|
||||
}
|
||||
return formData;
|
||||
} else {
|
||||
throw new TypeError("Invalid form data");
|
||||
}
|
||||
}
|
||||
|
||||
async text() {
|
||||
if (typeof this._bodySource === "string") {
|
||||
return this._bodySource;
|
||||
}
|
||||
|
||||
const ab = await this.arrayBuffer();
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
return decoder.decode(ab);
|
||||
}
|
||||
|
||||
async json() {
|
||||
const raw = await this.text();
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
arrayBuffer() {
|
||||
if (this._bodySource instanceof ReadableStream) {
|
||||
return bufferFromStream(this._bodySource.getReader(), this.#size);
|
||||
}
|
||||
return bodyToArrayBuffer(this._bodySource);
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.body = {
|
||||
Body,
|
||||
BodyUsedError,
|
||||
};
|
||||
})(this);
|
|
@ -1,139 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const body = window.__bootstrap.body;
|
||||
const { ReadableStream } = window.__bootstrap.streams;
|
||||
|
||||
function byteUpperCase(s) {
|
||||
return String(s).replace(/[a-z]/g, function byteUpperCaseReplace(c) {
|
||||
return c.toUpperCase();
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeMethod(m) {
|
||||
const u = byteUpperCase(m);
|
||||
if (
|
||||
u === "DELETE" ||
|
||||
u === "GET" ||
|
||||
u === "HEAD" ||
|
||||
u === "OPTIONS" ||
|
||||
u === "POST" ||
|
||||
u === "PUT"
|
||||
) {
|
||||
return u;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
class Request extends body.Body {
|
||||
constructor(input, init) {
|
||||
if (arguments.length < 1) {
|
||||
throw TypeError("Not enough arguments");
|
||||
}
|
||||
|
||||
if (!init) {
|
||||
init = {};
|
||||
}
|
||||
|
||||
let b;
|
||||
|
||||
// prefer body from init
|
||||
if (init.body) {
|
||||
b = init.body;
|
||||
} else if (input instanceof Request && input._bodySource) {
|
||||
if (input.bodyUsed) {
|
||||
throw TypeError(body.BodyUsedError);
|
||||
}
|
||||
b = input._bodySource;
|
||||
} else if (typeof input === "object" && "body" in input && input.body) {
|
||||
if (input.bodyUsed) {
|
||||
throw TypeError(body.BodyUsedError);
|
||||
}
|
||||
b = input.body;
|
||||
} else {
|
||||
b = "";
|
||||
}
|
||||
|
||||
let headers;
|
||||
|
||||
// prefer headers from init
|
||||
if (init.headers) {
|
||||
headers = new Headers(init.headers);
|
||||
} else if (input instanceof Request) {
|
||||
headers = input.headers;
|
||||
} else {
|
||||
headers = new Headers();
|
||||
}
|
||||
|
||||
const contentType = headers.get("content-type") || "";
|
||||
super(b, { contentType });
|
||||
this.headers = headers;
|
||||
|
||||
// readonly attribute ByteString method;
|
||||
this.method = "GET";
|
||||
|
||||
// readonly attribute USVString url;
|
||||
this.url = "";
|
||||
|
||||
// readonly attribute RequestCredentials credentials;
|
||||
this.credentials = "omit";
|
||||
|
||||
if (input instanceof Request) {
|
||||
if (input.bodyUsed) {
|
||||
throw TypeError(body.BodyUsedError);
|
||||
}
|
||||
this.method = input.method;
|
||||
this.url = input.url;
|
||||
this.headers = new Headers(input.headers);
|
||||
this.credentials = input.credentials;
|
||||
this._stream = input._stream;
|
||||
} else if (typeof input === "string") {
|
||||
this.url = input;
|
||||
}
|
||||
|
||||
if (init && "method" in init) {
|
||||
this.method = normalizeMethod(init.method);
|
||||
}
|
||||
|
||||
if (
|
||||
init &&
|
||||
"credentials" in init &&
|
||||
init.credentials &&
|
||||
["omit", "same-origin", "include"].indexOf(init.credentials) !== -1
|
||||
) {
|
||||
this.credentials = init.credentials;
|
||||
}
|
||||
}
|
||||
|
||||
clone() {
|
||||
if (this.bodyUsed) {
|
||||
throw TypeError(body.BodyUsedError);
|
||||
}
|
||||
|
||||
const iterators = this.headers.entries();
|
||||
const headersList = [];
|
||||
for (const header of iterators) {
|
||||
headersList.push(header);
|
||||
}
|
||||
|
||||
let body2 = this._bodySource;
|
||||
|
||||
if (this._bodySource instanceof ReadableStream) {
|
||||
const tees = this._bodySource.tee();
|
||||
this._stream = this._bodySource = tees[0];
|
||||
body2 = tees[1];
|
||||
}
|
||||
|
||||
return new Request(this.url, {
|
||||
body: body2,
|
||||
method: this.method,
|
||||
headers: new Headers(headersList),
|
||||
credentials: this.credentials,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.request = {
|
||||
Request,
|
||||
};
|
||||
})(this);
|
|
@ -1,391 +0,0 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const core = window.Deno.core;
|
||||
const { notImplemented } = window.__bootstrap.util;
|
||||
const { getHeaderValueParams, isTypedArray } = window.__bootstrap.webUtil;
|
||||
const { Blob, bytesSymbol: blobBytesSymbol } = window.__bootstrap.blob;
|
||||
const Body = window.__bootstrap.body;
|
||||
const { ReadableStream } = window.__bootstrap.streams;
|
||||
const { MultipartBuilder } = window.__bootstrap.multipart;
|
||||
const { Headers } = window.__bootstrap.headers;
|
||||
|
||||
function createHttpClient(options) {
|
||||
return new HttpClient(opCreateHttpClient(options));
|
||||
}
|
||||
|
||||
function opCreateHttpClient(args) {
|
||||
return core.jsonOpSync("op_create_http_client", args);
|
||||
}
|
||||
|
||||
class HttpClient {
|
||||
constructor(rid) {
|
||||
this.rid = rid;
|
||||
}
|
||||
close() {
|
||||
core.close(this.rid);
|
||||
}
|
||||
}
|
||||
|
||||
function opFetch(args, body) {
|
||||
let zeroCopy;
|
||||
if (body != null) {
|
||||
zeroCopy = new Uint8Array(body.buffer, body.byteOffset, body.byteLength);
|
||||
}
|
||||
|
||||
return core.jsonOpAsync("op_fetch", args, ...(zeroCopy ? [zeroCopy] : []));
|
||||
}
|
||||
|
||||
const NULL_BODY_STATUS = [101, 204, 205, 304];
|
||||
const REDIRECT_STATUS = [301, 302, 303, 307, 308];
|
||||
|
||||
const responseData = new WeakMap();
|
||||
class Response extends Body.Body {
|
||||
constructor(body = null, init) {
|
||||
init = init ?? {};
|
||||
|
||||
if (typeof init !== "object") {
|
||||
throw new TypeError(`'init' is not an object`);
|
||||
}
|
||||
|
||||
const extraInit = responseData.get(init) || {};
|
||||
let { type = "default", url = "" } = extraInit;
|
||||
|
||||
let status = init.status === undefined ? 200 : Number(init.status || 0);
|
||||
let statusText = init.statusText ?? "";
|
||||
let headers = init.headers instanceof Headers
|
||||
? init.headers
|
||||
: new Headers(init.headers);
|
||||
|
||||
if (init.status !== undefined && (status < 200 || status > 599)) {
|
||||
throw new RangeError(
|
||||
`The status provided (${init.status}) is outside the range [200, 599]`,
|
||||
);
|
||||
}
|
||||
|
||||
// null body status
|
||||
if (body && NULL_BODY_STATUS.includes(status)) {
|
||||
throw new TypeError("Response with null body status cannot have body");
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
type = "default";
|
||||
} else {
|
||||
if (type == "error") {
|
||||
// spec: https://fetch.spec.whatwg.org/#concept-network-error
|
||||
status = 0;
|
||||
statusText = "";
|
||||
headers = new Headers();
|
||||
body = null;
|
||||
/* spec for other Response types:
|
||||
https://fetch.spec.whatwg.org/#concept-filtered-response-basic
|
||||
Please note that type "basic" is not the same thing as "default".*/
|
||||
} else if (type == "basic") {
|
||||
for (const h of headers) {
|
||||
/* Forbidden Response-Header Names:
|
||||
https://fetch.spec.whatwg.org/#forbidden-response-header-name */
|
||||
if (["set-cookie", "set-cookie2"].includes(h[0].toLowerCase())) {
|
||||
headers.delete(h[0]);
|
||||
}
|
||||
}
|
||||
} else if (type == "cors") {
|
||||
/* CORS-safelisted Response-Header Names:
|
||||
https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name */
|
||||
const allowedHeaders = [
|
||||
"Cache-Control",
|
||||
"Content-Language",
|
||||
"Content-Length",
|
||||
"Content-Type",
|
||||
"Expires",
|
||||
"Last-Modified",
|
||||
"Pragma",
|
||||
].map((c) => c.toLowerCase());
|
||||
for (const h of headers) {
|
||||
/* Technically this is still not standards compliant because we are
|
||||
supposed to allow headers allowed in the
|
||||
'Access-Control-Expose-Headers' header in the 'internal response'
|
||||
However, this implementation of response doesn't seem to have an
|
||||
easy way to access the internal response, so we ignore that
|
||||
header.
|
||||
TODO(serverhiccups): change how internal responses are handled
|
||||
so we can do this properly. */
|
||||
if (!allowedHeaders.includes(h[0].toLowerCase())) {
|
||||
headers.delete(h[0]);
|
||||
}
|
||||
}
|
||||
/* TODO(serverhiccups): Once I fix the 'internal response' thing,
|
||||
these actually need to treat the internal response differently */
|
||||
} else if (type == "opaque" || type == "opaqueredirect") {
|
||||
url = "";
|
||||
status = 0;
|
||||
statusText = "";
|
||||
headers = new Headers();
|
||||
body = null;
|
||||
}
|
||||
}
|
||||
|
||||
const contentType = headers.get("content-type") || "";
|
||||
const size = Number(headers.get("content-length")) || undefined;
|
||||
|
||||
super(body, { contentType, size });
|
||||
|
||||
this.url = url;
|
||||
this.statusText = statusText;
|
||||
this.status = extraInit.status || status;
|
||||
this.headers = headers;
|
||||
this.redirected = extraInit.redirected || false;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
get ok() {
|
||||
return 200 <= this.status && this.status < 300;
|
||||
}
|
||||
|
||||
clone() {
|
||||
if (this.bodyUsed) {
|
||||
throw TypeError(Body.BodyUsedError);
|
||||
}
|
||||
|
||||
const iterators = this.headers.entries();
|
||||
const headersList = [];
|
||||
for (const header of iterators) {
|
||||
headersList.push(header);
|
||||
}
|
||||
|
||||
let resBody = this._bodySource;
|
||||
|
||||
if (this._bodySource instanceof ReadableStream) {
|
||||
const tees = this._bodySource.tee();
|
||||
this._stream = this._bodySource = tees[0];
|
||||
resBody = tees[1];
|
||||
}
|
||||
|
||||
return new Response(resBody, {
|
||||
status: this.status,
|
||||
statusText: this.statusText,
|
||||
headers: new Headers(headersList),
|
||||
});
|
||||
}
|
||||
|
||||
static redirect(url, status) {
|
||||
if (![301, 302, 303, 307, 308].includes(status)) {
|
||||
throw new RangeError(
|
||||
"The redirection status must be one of 301, 302, 303, 307 and 308.",
|
||||
);
|
||||
}
|
||||
return new Response(null, {
|
||||
status,
|
||||
statusText: "",
|
||||
headers: [["Location", typeof url === "string" ? url : url.toString()]],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function sendFetchReq(url, method, headers, body, clientRid) {
|
||||
let headerArray = [];
|
||||
if (headers) {
|
||||
headerArray = Array.from(headers.entries());
|
||||
}
|
||||
|
||||
const args = {
|
||||
method,
|
||||
url,
|
||||
headers: headerArray,
|
||||
clientRid,
|
||||
};
|
||||
|
||||
return opFetch(args, body);
|
||||
}
|
||||
|
||||
async function fetch(input, init) {
|
||||
let url;
|
||||
let method = null;
|
||||
let headers = null;
|
||||
let body;
|
||||
let clientRid = null;
|
||||
let redirected = false;
|
||||
let remRedirectCount = 20; // TODO: use a better way to handle
|
||||
|
||||
if (typeof input === "string" || input instanceof URL) {
|
||||
url = typeof input === "string" ? input : input.href;
|
||||
if (init != null) {
|
||||
method = init.method || null;
|
||||
if (init.headers) {
|
||||
headers = init.headers instanceof Headers
|
||||
? init.headers
|
||||
: new Headers(init.headers);
|
||||
} else {
|
||||
headers = null;
|
||||
}
|
||||
|
||||
// ref: https://fetch.spec.whatwg.org/#body-mixin
|
||||
// Body should have been a mixin
|
||||
// but we are treating it as a separate class
|
||||
if (init.body) {
|
||||
if (!headers) {
|
||||
headers = new Headers();
|
||||
}
|
||||
let contentType = "";
|
||||
if (typeof init.body === "string") {
|
||||
body = new TextEncoder().encode(init.body);
|
||||
contentType = "text/plain;charset=UTF-8";
|
||||
} else if (isTypedArray(init.body)) {
|
||||
body = init.body;
|
||||
} else if (init.body instanceof ArrayBuffer) {
|
||||
body = new Uint8Array(init.body);
|
||||
} else if (init.body instanceof URLSearchParams) {
|
||||
body = new TextEncoder().encode(init.body.toString());
|
||||
contentType = "application/x-www-form-urlencoded;charset=UTF-8";
|
||||
} else if (init.body instanceof Blob) {
|
||||
body = init.body[blobBytesSymbol];
|
||||
contentType = init.body.type;
|
||||
} else if (init.body instanceof FormData) {
|
||||
let boundary;
|
||||
if (headers.has("content-type")) {
|
||||
const params = getHeaderValueParams("content-type");
|
||||
boundary = params.get("boundary");
|
||||
}
|
||||
const multipartBuilder = new MultipartBuilder(init.body, boundary);
|
||||
body = multipartBuilder.getBody();
|
||||
contentType = multipartBuilder.getContentType();
|
||||
} else {
|
||||
// TODO: ReadableStream
|
||||
notImplemented();
|
||||
}
|
||||
if (contentType && !headers.has("content-type")) {
|
||||
headers.set("content-type", contentType);
|
||||
}
|
||||
}
|
||||
|
||||
if (init.client instanceof HttpClient) {
|
||||
clientRid = init.client.rid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
url = input.url;
|
||||
method = input.method;
|
||||
headers = input.headers;
|
||||
|
||||
if (input._bodySource) {
|
||||
body = new DataView(await input.arrayBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
let responseBody;
|
||||
let responseInit = {};
|
||||
while (remRedirectCount) {
|
||||
const fetchResponse = await sendFetchReq(
|
||||
url,
|
||||
method,
|
||||
headers,
|
||||
body,
|
||||
clientRid,
|
||||
);
|
||||
const rid = fetchResponse.bodyRid;
|
||||
|
||||
if (
|
||||
NULL_BODY_STATUS.includes(fetchResponse.status) ||
|
||||
REDIRECT_STATUS.includes(fetchResponse.status)
|
||||
) {
|
||||
// We won't use body of received response, so close it now
|
||||
// otherwise it will be kept in resource table.
|
||||
core.close(fetchResponse.bodyRid);
|
||||
responseBody = null;
|
||||
} else {
|
||||
responseBody = new ReadableStream({
|
||||
type: "bytes",
|
||||
async pull(controller) {
|
||||
try {
|
||||
const result = await core.jsonOpAsync("op_fetch_read", { rid });
|
||||
if (!result || !result.chunk) {
|
||||
controller.close();
|
||||
core.close(rid);
|
||||
} else {
|
||||
// TODO(ry) This is terribly inefficient. Make this zero-copy.
|
||||
const chunk = new Uint8Array(result.chunk);
|
||||
controller.enqueue(chunk);
|
||||
}
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
controller.close();
|
||||
core.close(rid);
|
||||
}
|
||||
},
|
||||
cancel() {
|
||||
// When reader.cancel() is called
|
||||
core.close(rid);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
responseInit = {
|
||||
status: 200,
|
||||
statusText: fetchResponse.statusText,
|
||||
headers: fetchResponse.headers,
|
||||
};
|
||||
|
||||
responseData.set(responseInit, {
|
||||
redirected,
|
||||
rid: fetchResponse.bodyRid,
|
||||
status: fetchResponse.status,
|
||||
url,
|
||||
});
|
||||
|
||||
const response = new Response(responseBody, responseInit);
|
||||
|
||||
if (REDIRECT_STATUS.includes(fetchResponse.status)) {
|
||||
// We're in a redirect status
|
||||
switch ((init && init.redirect) || "follow") {
|
||||
case "error":
|
||||
responseInit = {};
|
||||
responseData.set(responseInit, {
|
||||
type: "error",
|
||||
redirected: false,
|
||||
url: "",
|
||||
});
|
||||
return new Response(null, responseInit);
|
||||
case "manual":
|
||||
responseInit = {};
|
||||
responseData.set(responseInit, {
|
||||
type: "opaqueredirect",
|
||||
redirected: false,
|
||||
url: "",
|
||||
});
|
||||
return new Response(null, responseInit);
|
||||
case "follow":
|
||||
default:
|
||||
let redirectUrl = response.headers.get("Location");
|
||||
if (redirectUrl == null) {
|
||||
return response; // Unspecified
|
||||
}
|
||||
if (
|
||||
!redirectUrl.startsWith("http://") &&
|
||||
!redirectUrl.startsWith("https://")
|
||||
) {
|
||||
redirectUrl = new URL(redirectUrl, url).href;
|
||||
}
|
||||
url = redirectUrl;
|
||||
redirected = true;
|
||||
remRedirectCount--;
|
||||
}
|
||||
} else {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
responseData.set(responseInit, {
|
||||
type: "error",
|
||||
redirected: false,
|
||||
url: "",
|
||||
});
|
||||
|
||||
return new Response(null, responseInit);
|
||||
}
|
||||
|
||||
window.__bootstrap.fetch = {
|
||||
fetch,
|
||||
Response,
|
||||
HttpClient,
|
||||
createHttpClient,
|
||||
};
|
||||
})(this);
|
|
@ -22,15 +22,10 @@ delete Object.prototype.__proto__;
|
|||
const crypto = window.__bootstrap.crypto;
|
||||
const url = window.__bootstrap.url;
|
||||
const headers = window.__bootstrap.headers;
|
||||
const queuingStrategy = window.__bootstrap.queuingStrategy;
|
||||
const streams = window.__bootstrap.streams;
|
||||
const blob = window.__bootstrap.blob;
|
||||
const domFile = window.__bootstrap.domFile;
|
||||
const progressEvent = window.__bootstrap.progressEvent;
|
||||
const fileReader = window.__bootstrap.fileReader;
|
||||
const formData = window.__bootstrap.formData;
|
||||
const webSocket = window.__bootstrap.webSocket;
|
||||
const request = window.__bootstrap.request;
|
||||
const fetch = window.__bootstrap.fetch;
|
||||
const denoNs = window.__bootstrap.denoNs;
|
||||
const denoNsUnstable = window.__bootstrap.denoNsUnstable;
|
||||
|
@ -198,22 +193,22 @@ delete Object.prototype.__proto__;
|
|||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
|
||||
const windowOrWorkerGlobalScope = {
|
||||
Blob: util.nonEnumerable(blob.Blob),
|
||||
Blob: util.nonEnumerable(fetch.Blob),
|
||||
ByteLengthQueuingStrategy: util.nonEnumerable(
|
||||
queuingStrategy.ByteLengthQueuingStrategy,
|
||||
streams.ByteLengthQueuingStrategy,
|
||||
),
|
||||
CloseEvent: util.nonEnumerable(CloseEvent),
|
||||
CountQueuingStrategy: util.nonEnumerable(
|
||||
queuingStrategy.CountQueuingStrategy,
|
||||
streams.CountQueuingStrategy,
|
||||
),
|
||||
CustomEvent: util.nonEnumerable(CustomEvent),
|
||||
DOMException: util.nonEnumerable(DOMException),
|
||||
ErrorEvent: util.nonEnumerable(ErrorEvent),
|
||||
Event: util.nonEnumerable(Event),
|
||||
EventTarget: util.nonEnumerable(EventTarget),
|
||||
File: util.nonEnumerable(domFile.DomFile),
|
||||
File: util.nonEnumerable(fetch.DomFile),
|
||||
FileReader: util.nonEnumerable(fileReader.FileReader),
|
||||
FormData: util.nonEnumerable(formData.FormData),
|
||||
FormData: util.nonEnumerable(fetch.FormData),
|
||||
Headers: util.nonEnumerable(headers.Headers),
|
||||
MessageEvent: util.nonEnumerable(MessageEvent),
|
||||
Performance: util.nonEnumerable(performance.Performance),
|
||||
|
@ -222,7 +217,7 @@ delete Object.prototype.__proto__;
|
|||
PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure),
|
||||
ProgressEvent: util.nonEnumerable(progressEvent.ProgressEvent),
|
||||
ReadableStream: util.nonEnumerable(streams.ReadableStream),
|
||||
Request: util.nonEnumerable(request.Request),
|
||||
Request: util.nonEnumerable(fetch.Request),
|
||||
Response: util.nonEnumerable(fetch.Response),
|
||||
TextDecoder: util.nonEnumerable(TextDecoder),
|
||||
TextEncoder: util.nonEnumerable(TextEncoder),
|
||||
|
|
11
cli/state.rs
11
cli/state.rs
|
@ -21,6 +21,7 @@ use std::cell::Cell;
|
|||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::str;
|
||||
|
@ -315,3 +316,13 @@ impl CliState {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl deno_fetch::FetchPermissions for CliState {
|
||||
fn check_net_url(&self, url: &url::Url) -> Result<(), AnyError> {
|
||||
CliState::check_net_url(self, url)
|
||||
}
|
||||
|
||||
fn check_read(&self, p: &PathBuf) -> Result<(), AnyError> {
|
||||
CliState::check_read(self, p)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[WILDCARD]
|
||||
Files: 45
|
||||
Files: 46
|
||||
Nodes: [WILDCARD]
|
||||
Identifiers: [WILDCARD]
|
||||
Symbols: [WILDCARD]
|
||||
|
|
|
@ -61,6 +61,7 @@ unitTest(function blobShouldNotThrowError(): void {
|
|||
assertEquals(hasThrown, false);
|
||||
});
|
||||
|
||||
/* TODO https://github.com/denoland/deno/issues/7540
|
||||
unitTest(function nativeEndLine(): void {
|
||||
const options = {
|
||||
ending: "native",
|
||||
|
@ -69,6 +70,7 @@ unitTest(function nativeEndLine(): void {
|
|||
|
||||
assertEquals(blob.size, Deno.build.os === "windows" ? 12 : 11);
|
||||
});
|
||||
*/
|
||||
|
||||
unitTest(async function blobText(): Promise<void> {
|
||||
const blob = new Blob(["Hello World"]);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
/* TODO https://github.com/denoland/deno/issues/7540
|
||||
import { unitTest, assert, assertEquals } from "./test_util.ts";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
|
@ -25,6 +27,7 @@ function setup() {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
unitTest(function testDomIterable(): void {
|
||||
const { DomIterable, Base } = setup();
|
||||
|
||||
|
@ -85,3 +88,4 @@ unitTest(function testDomIterableScope(): void {
|
|||
checkScope(null, window);
|
||||
checkScope(undefined, window);
|
||||
});
|
||||
*/
|
||||
|
|
|
@ -435,6 +435,7 @@ delete Object.prototype.__proto__;
|
|||
ts.libs.push("deno.ns", "deno.window", "deno.worker", "deno.shared_globals");
|
||||
ts.libMap.set("deno.ns", "lib.deno.ns.d.ts");
|
||||
ts.libMap.set("deno.web", "lib.deno.web.d.ts");
|
||||
ts.libMap.set("deno.fetch", "lib.deno.fetch.d.ts");
|
||||
ts.libMap.set("deno.window", "lib.deno.window.d.ts");
|
||||
ts.libMap.set("deno.worker", "lib.deno.worker.d.ts");
|
||||
ts.libMap.set("deno.shared_globals", "lib.deno.shared_globals.d.ts");
|
||||
|
@ -450,6 +451,10 @@ delete Object.prototype.__proto__;
|
|||
`${ASSETS}/lib.deno.web.d.ts`,
|
||||
ts.ScriptTarget.ESNext,
|
||||
);
|
||||
SNAPSHOT_HOST.getSourceFile(
|
||||
`${ASSETS}/lib.deno.fetch.d.ts`,
|
||||
ts.ScriptTarget.ESNext,
|
||||
);
|
||||
SNAPSHOT_HOST.getSourceFile(
|
||||
`${ASSETS}/lib.deno.window.d.ts`,
|
||||
ts.ScriptTarget.ESNext,
|
||||
|
|
20
op_crates/fetch/01_fetch_util.js
Normal file
20
op_crates/fetch/01_fetch_util.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
function requiredArguments(
|
||||
name,
|
||||
length,
|
||||
required,
|
||||
) {
|
||||
if (length < required) {
|
||||
const errMsg = `${name} requires at least ${required} argument${
|
||||
required === 1 ? "" : "s"
|
||||
}, but only ${length} present`;
|
||||
throw new TypeError(errMsg);
|
||||
}
|
||||
}
|
||||
|
||||
window.__bootstrap.fetchUtil = {
|
||||
requiredArguments,
|
||||
};
|
||||
})(this);
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
((window) => {
|
||||
const { requiredArguments } = window.__bootstrap.webUtil;
|
||||
const { exposeForTest } = window.__bootstrap.internals;
|
||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
||||
// const { exposeForTest } = window.__bootstrap.internals;
|
||||
|
||||
function DomIterableMixin(
|
||||
Base,
|
||||
|
@ -69,7 +69,7 @@
|
|||
return DomIterable;
|
||||
}
|
||||
|
||||
exposeForTest("DomIterableMixin", DomIterableMixin);
|
||||
// exposeForTest("DomIterableMixin", DomIterableMixin);
|
||||
|
||||
window.__bootstrap.domIterable = {
|
||||
DomIterableMixin,
|
|
@ -9,11 +9,107 @@
|
|||
((window) => {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any,require-await */
|
||||
|
||||
const { cloneValue, setFunctionName } = window.__bootstrap.webUtil;
|
||||
const { assert, AssertionError } = window.__bootstrap.util;
|
||||
|
||||
const customInspect = Symbol.for("Deno.customInspect");
|
||||
|
||||
/** Clone a value in a similar way to structured cloning. It is similar to a
|
||||
* StructureDeserialize(StructuredSerialize(...)). */
|
||||
function cloneValue(value) {
|
||||
switch (typeof value) {
|
||||
case "number":
|
||||
case "string":
|
||||
case "boolean":
|
||||
case "undefined":
|
||||
case "bigint":
|
||||
return value;
|
||||
case "object": {
|
||||
if (objectCloneMemo.has(value)) {
|
||||
return objectCloneMemo.get(value);
|
||||
}
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Date) {
|
||||
return new Date(value.valueOf());
|
||||
}
|
||||
if (value instanceof RegExp) {
|
||||
return new RegExp(value);
|
||||
}
|
||||
if (value instanceof SharedArrayBuffer) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof ArrayBuffer) {
|
||||
const cloned = cloneArrayBuffer(
|
||||
value,
|
||||
0,
|
||||
value.byteLength,
|
||||
ArrayBuffer,
|
||||
);
|
||||
objectCloneMemo.set(value, cloned);
|
||||
return cloned;
|
||||
}
|
||||
if (ArrayBuffer.isView(value)) {
|
||||
const clonedBuffer = cloneValue(value.buffer);
|
||||
// Use DataViewConstructor type purely for type-checking, can be a
|
||||
// DataView or TypedArray. They use the same constructor signature,
|
||||
// only DataView has a length in bytes and TypedArrays use a length in
|
||||
// terms of elements, so we adjust for that.
|
||||
let length;
|
||||
if (value instanceof DataView) {
|
||||
length = value.byteLength;
|
||||
} else {
|
||||
length = value.length;
|
||||
}
|
||||
return new (value.constructor)(
|
||||
clonedBuffer,
|
||||
value.byteOffset,
|
||||
length,
|
||||
);
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
const clonedMap = new Map();
|
||||
objectCloneMemo.set(value, clonedMap);
|
||||
value.forEach((v, k) => clonedMap.set(k, cloneValue(v)));
|
||||
return clonedMap;
|
||||
}
|
||||
if (value instanceof Set) {
|
||||
const clonedSet = new Map();
|
||||
objectCloneMemo.set(value, clonedSet);
|
||||
value.forEach((v, k) => clonedSet.set(k, cloneValue(v)));
|
||||
return clonedSet;
|
||||
}
|
||||
|
||||
const clonedObj = {};
|
||||
objectCloneMemo.set(value, clonedObj);
|
||||
const sourceKeys = Object.getOwnPropertyNames(value);
|
||||
for (const key of sourceKeys) {
|
||||
clonedObj[key] = cloneValue(value[key]);
|
||||
}
|
||||
return clonedObj;
|
||||
}
|
||||
case "symbol":
|
||||
case "function":
|
||||
default:
|
||||
throw new DOMException("Uncloneable value in stream", "DataCloneError");
|
||||
}
|
||||
}
|
||||
|
||||
function setFunctionName(fn, value) {
|
||||
Object.defineProperty(fn, "name", { value, configurable: true });
|
||||
}
|
||||
|
||||
class AssertionError extends Error {
|
||||
constructor(msg) {
|
||||
super(msg);
|
||||
this.name = "AssertionError";
|
||||
}
|
||||
}
|
||||
|
||||
function assert(cond, msg = "Assertion failed.") {
|
||||
if (!cond) {
|
||||
throw new AssertionError(msg);
|
||||
}
|
||||
}
|
||||
|
||||
const sym = {
|
||||
abortAlgorithm: Symbol("abortAlgorithm"),
|
||||
abortSteps: Symbol("abortSteps"),
|
||||
|
@ -3271,10 +3367,52 @@
|
|||
}
|
||||
/* eslint-enable */
|
||||
|
||||
class CountQueuingStrategy {
|
||||
constructor({ highWaterMark }) {
|
||||
this.highWaterMark = highWaterMark;
|
||||
}
|
||||
|
||||
size() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
[customInspect]() {
|
||||
return `${this.constructor.name} { highWaterMark: ${
|
||||
String(this.highWaterMark)
|
||||
}, size: f }`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(CountQueuingStrategy.prototype, "size", {
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
class ByteLengthQueuingStrategy {
|
||||
constructor({ highWaterMark }) {
|
||||
this.highWaterMark = highWaterMark;
|
||||
}
|
||||
|
||||
size(chunk) {
|
||||
return chunk.byteLength;
|
||||
}
|
||||
|
||||
[customInspect]() {
|
||||
return `${this.constructor.name} { highWaterMark: ${
|
||||
String(this.highWaterMark)
|
||||
}, size: f }`;
|
||||
}
|
||||
}
|
||||
|
||||
Object.defineProperty(ByteLengthQueuingStrategy.prototype, "size", {
|
||||
enumerable: true,
|
||||
});
|
||||
|
||||
window.__bootstrap.streams = {
|
||||
ReadableStream,
|
||||
TransformStream,
|
||||
WritableStream,
|
||||
isReadableStreamDisturbed,
|
||||
CountQueuingStrategy,
|
||||
ByteLengthQueuingStrategy,
|
||||
};
|
||||
})(this);
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
((window) => {
|
||||
const { DomIterableMixin } = window.__bootstrap.domIterable;
|
||||
const { requiredArguments } = window.__bootstrap.webUtil;
|
||||
const { requiredArguments } = window.__bootstrap.fetchUtil;
|
||||
|
||||
// From node-fetch
|
||||
// Copyright (c) 2016 David Frank. MIT License.
|
1390
op_crates/fetch/26_fetch.js
Normal file
1390
op_crates/fetch/26_fetch.js
Normal file
File diff suppressed because it is too large
Load diff
19
op_crates/fetch/Cargo.toml
Normal file
19
op_crates/fetch/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
[package]
|
||||
name = "deno_fetch"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
description = "fetch Web API"
|
||||
authors = ["the Deno authors"]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/denoland/deno"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
deno_core = { version = "0.57.0", path = "../../core" }
|
||||
reqwest = { version = "0.10.8", default-features = false, features = ["rustls-tls", "stream", "gzip", "brotli"] }
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
636
op_crates/fetch/lib.deno_fetch.d.ts
vendored
Normal file
636
op_crates/fetch/lib.deno_fetch.d.ts
vendored
Normal file
|
@ -0,0 +1,636 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-explicit-any, no-var */
|
||||
|
||||
/// <reference no-default-lib="true" />
|
||||
/// <reference lib="esnext" />
|
||||
|
||||
interface DomIterable<K, V> {
|
||||
keys(): IterableIterator<K>;
|
||||
values(): IterableIterator<V>;
|
||||
entries(): IterableIterator<[K, V]>;
|
||||
[Symbol.iterator](): IterableIterator<[K, V]>;
|
||||
forEach(
|
||||
callback: (value: V, key: K, parent: this) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface ReadableStreamReadDoneResult<T> {
|
||||
done: true;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
interface ReadableStreamReadValueResult<T> {
|
||||
done: false;
|
||||
value: T;
|
||||
}
|
||||
|
||||
type ReadableStreamReadResult<T> =
|
||||
| ReadableStreamReadValueResult<T>
|
||||
| ReadableStreamReadDoneResult<T>;
|
||||
|
||||
interface ReadableStreamDefaultReader<R = any> {
|
||||
readonly closed: Promise<void>;
|
||||
cancel(reason?: any): Promise<void>;
|
||||
read(): Promise<ReadableStreamReadResult<R>>;
|
||||
releaseLock(): void;
|
||||
}
|
||||
|
||||
interface ReadableStreamReader<R = any> {
|
||||
cancel(): Promise<void>;
|
||||
read(): Promise<ReadableStreamReadResult<R>>;
|
||||
releaseLock(): void;
|
||||
}
|
||||
|
||||
interface ReadableByteStreamControllerCallback {
|
||||
(controller: ReadableByteStreamController): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface UnderlyingByteSource {
|
||||
autoAllocateChunkSize?: number;
|
||||
cancel?: ReadableStreamErrorCallback;
|
||||
pull?: ReadableByteStreamControllerCallback;
|
||||
start?: ReadableByteStreamControllerCallback;
|
||||
type: "bytes";
|
||||
}
|
||||
|
||||
interface UnderlyingSource<R = any> {
|
||||
cancel?: ReadableStreamErrorCallback;
|
||||
pull?: ReadableStreamDefaultControllerCallback<R>;
|
||||
start?: ReadableStreamDefaultControllerCallback<R>;
|
||||
type?: undefined;
|
||||
}
|
||||
|
||||
interface ReadableStreamErrorCallback {
|
||||
(reason: any): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface ReadableStreamDefaultControllerCallback<R> {
|
||||
(controller: ReadableStreamDefaultController<R>): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface ReadableStreamDefaultController<R = any> {
|
||||
readonly desiredSize: number | null;
|
||||
close(): void;
|
||||
enqueue(chunk: R): void;
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
interface ReadableByteStreamController {
|
||||
readonly byobRequest: undefined;
|
||||
readonly desiredSize: number | null;
|
||||
close(): void;
|
||||
enqueue(chunk: ArrayBufferView): void;
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
interface PipeOptions {
|
||||
preventAbort?: boolean;
|
||||
preventCancel?: boolean;
|
||||
preventClose?: boolean;
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
interface QueuingStrategySizeCallback<T = any> {
|
||||
(chunk: T): number;
|
||||
}
|
||||
|
||||
interface QueuingStrategy<T = any> {
|
||||
highWaterMark?: number;
|
||||
size?: QueuingStrategySizeCallback<T>;
|
||||
}
|
||||
|
||||
/** This Streams API interface provides a built-in byte length queuing strategy
|
||||
* that can be used when constructing streams. */
|
||||
declare class CountQueuingStrategy implements QueuingStrategy {
|
||||
constructor(options: { highWaterMark: number });
|
||||
highWaterMark: number;
|
||||
size(chunk: any): 1;
|
||||
}
|
||||
|
||||
declare class ByteLengthQueuingStrategy
|
||||
implements QueuingStrategy<ArrayBufferView> {
|
||||
constructor(options: { highWaterMark: number });
|
||||
highWaterMark: number;
|
||||
size(chunk: ArrayBufferView): number;
|
||||
}
|
||||
|
||||
/** This Streams API interface represents a readable stream of byte data. The
|
||||
* Fetch API offers a concrete instance of a ReadableStream through the body
|
||||
* property of a Response object. */
|
||||
interface ReadableStream<R = any> {
|
||||
readonly locked: boolean;
|
||||
cancel(reason?: any): Promise<void>;
|
||||
getIterator(options?: { preventCancel?: boolean }): AsyncIterableIterator<R>;
|
||||
// getReader(options: { mode: "byob" }): ReadableStreamBYOBReader;
|
||||
getReader(): ReadableStreamDefaultReader<R>;
|
||||
pipeThrough<T>(
|
||||
{
|
||||
writable,
|
||||
readable,
|
||||
}: {
|
||||
writable: WritableStream<R>;
|
||||
readable: ReadableStream<T>;
|
||||
},
|
||||
options?: PipeOptions,
|
||||
): ReadableStream<T>;
|
||||
pipeTo(dest: WritableStream<R>, options?: PipeOptions): Promise<void>;
|
||||
tee(): [ReadableStream<R>, ReadableStream<R>];
|
||||
[Symbol.asyncIterator](options?: {
|
||||
preventCancel?: boolean;
|
||||
}): AsyncIterableIterator<R>;
|
||||
}
|
||||
|
||||
declare var ReadableStream: {
|
||||
prototype: ReadableStream;
|
||||
new (
|
||||
underlyingSource: UnderlyingByteSource,
|
||||
strategy?: { highWaterMark?: number; size?: undefined },
|
||||
): ReadableStream<Uint8Array>;
|
||||
new <R = any>(
|
||||
underlyingSource?: UnderlyingSource<R>,
|
||||
strategy?: QueuingStrategy<R>,
|
||||
): ReadableStream<R>;
|
||||
};
|
||||
|
||||
interface WritableStreamDefaultControllerCloseCallback {
|
||||
(): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface WritableStreamDefaultControllerStartCallback {
|
||||
(controller: WritableStreamDefaultController): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface WritableStreamDefaultControllerWriteCallback<W> {
|
||||
(chunk: W, controller: WritableStreamDefaultController):
|
||||
| void
|
||||
| PromiseLike<
|
||||
void
|
||||
>;
|
||||
}
|
||||
|
||||
interface WritableStreamErrorCallback {
|
||||
(reason: any): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface UnderlyingSink<W = any> {
|
||||
abort?: WritableStreamErrorCallback;
|
||||
close?: WritableStreamDefaultControllerCloseCallback;
|
||||
start?: WritableStreamDefaultControllerStartCallback;
|
||||
type?: undefined;
|
||||
write?: WritableStreamDefaultControllerWriteCallback<W>;
|
||||
}
|
||||
|
||||
/** This Streams API interface provides a standard abstraction for writing
|
||||
* streaming data to a destination, known as a sink. This object comes with
|
||||
* built-in backpressure and queuing. */
|
||||
declare class WritableStream<W = any> {
|
||||
constructor(
|
||||
underlyingSink?: UnderlyingSink<W>,
|
||||
strategy?: QueuingStrategy<W>,
|
||||
);
|
||||
readonly locked: boolean;
|
||||
abort(reason?: any): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
getWriter(): WritableStreamDefaultWriter<W>;
|
||||
}
|
||||
|
||||
/** This Streams API interface represents a controller allowing control of a
|
||||
* WritableStream's state. When constructing a WritableStream, the underlying
|
||||
* sink is given a corresponding WritableStreamDefaultController instance to
|
||||
* manipulate. */
|
||||
interface WritableStreamDefaultController {
|
||||
error(error?: any): void;
|
||||
}
|
||||
|
||||
/** This Streams API interface is the object returned by
|
||||
* WritableStream.getWriter() and once created locks the < writer to the
|
||||
* WritableStream ensuring that no other streams can write to the underlying
|
||||
* sink. */
|
||||
interface WritableStreamDefaultWriter<W = any> {
|
||||
readonly closed: Promise<void>;
|
||||
readonly desiredSize: number | null;
|
||||
readonly ready: Promise<void>;
|
||||
abort(reason?: any): Promise<void>;
|
||||
close(): Promise<void>;
|
||||
releaseLock(): void;
|
||||
write(chunk: W): Promise<void>;
|
||||
}
|
||||
|
||||
declare class TransformStream<I = any, O = any> {
|
||||
constructor(
|
||||
transformer?: Transformer<I, O>,
|
||||
writableStrategy?: QueuingStrategy<I>,
|
||||
readableStrategy?: QueuingStrategy<O>,
|
||||
);
|
||||
readonly readable: ReadableStream<O>;
|
||||
readonly writable: WritableStream<I>;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultController<O = any> {
|
||||
readonly desiredSize: number | null;
|
||||
enqueue(chunk: O): void;
|
||||
error(reason?: any): void;
|
||||
terminate(): void;
|
||||
}
|
||||
|
||||
interface Transformer<I = any, O = any> {
|
||||
flush?: TransformStreamDefaultControllerCallback<O>;
|
||||
readableType?: undefined;
|
||||
start?: TransformStreamDefaultControllerCallback<O>;
|
||||
transform?: TransformStreamDefaultControllerTransformCallback<I, O>;
|
||||
writableType?: undefined;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultControllerCallback<O> {
|
||||
(controller: TransformStreamDefaultController<O>): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
interface TransformStreamDefaultControllerTransformCallback<I, O> {
|
||||
(
|
||||
chunk: I,
|
||||
controller: TransformStreamDefaultController<O>,
|
||||
): void | PromiseLike<void>;
|
||||
}
|
||||
|
||||
type BlobPart = BufferSource | Blob | string;
|
||||
|
||||
interface BlobPropertyBag {
|
||||
type?: string;
|
||||
ending?: "transparent" | "native";
|
||||
}
|
||||
|
||||
/** A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. */
|
||||
interface Blob {
|
||||
readonly size: number;
|
||||
readonly type: string;
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
slice(start?: number, end?: number, contentType?: string): Blob;
|
||||
stream(): ReadableStream;
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
declare const Blob: {
|
||||
prototype: Blob;
|
||||
new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
|
||||
};
|
||||
|
||||
interface FilePropertyBag extends BlobPropertyBag {
|
||||
lastModified?: number;
|
||||
}
|
||||
|
||||
/** Provides information about files and allows JavaScript in a web page to
|
||||
* access their content. */
|
||||
interface File extends Blob {
|
||||
readonly lastModified: number;
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
declare const File: {
|
||||
prototype: File;
|
||||
new (fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
|
||||
};
|
||||
|
||||
type FormDataEntryValue = File | string;
|
||||
|
||||
/** Provides a way to easily construct a set of key/value pairs representing
|
||||
* form fields and their values, which can then be easily sent using the
|
||||
* XMLHttpRequest.send() method. It uses the same format a form would use if the
|
||||
* encoding type were set to "multipart/form-data". */
|
||||
interface FormData extends DomIterable<string, FormDataEntryValue> {
|
||||
append(name: string, value: string | Blob, fileName?: string): void;
|
||||
delete(name: string): void;
|
||||
get(name: string): FormDataEntryValue | null;
|
||||
getAll(name: string): FormDataEntryValue[];
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string | Blob, fileName?: string): void;
|
||||
}
|
||||
|
||||
declare const FormData: {
|
||||
prototype: FormData;
|
||||
// TODO(ry) FormData constructor is non-standard.
|
||||
// new(form?: HTMLFormElement): FormData;
|
||||
new (): FormData;
|
||||
};
|
||||
|
||||
interface Body {
|
||||
/** A simple getter used to expose a `ReadableStream` of the body contents. */
|
||||
readonly body: ReadableStream<Uint8Array> | null;
|
||||
/** Stores a `Boolean` that declares whether the body has been used in a
|
||||
* response yet.
|
||||
*/
|
||||
readonly bodyUsed: boolean;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with an `ArrayBuffer`.
|
||||
*/
|
||||
arrayBuffer(): Promise<ArrayBuffer>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `Blob`.
|
||||
*/
|
||||
blob(): Promise<Blob>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `FormData` object.
|
||||
*/
|
||||
formData(): Promise<FormData>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with the result of parsing the body text as JSON.
|
||||
*/
|
||||
json(): Promise<any>;
|
||||
/** Takes a `Response` stream and reads it to completion. It returns a promise
|
||||
* that resolves with a `USVString` (text).
|
||||
*/
|
||||
text(): Promise<string>;
|
||||
}
|
||||
|
||||
type HeadersInit = Headers | string[][] | Record<string, string>;
|
||||
|
||||
/** This Fetch API interface allows you to perform various actions on HTTP
|
||||
* request and response headers. These actions include retrieving, setting,
|
||||
* adding to, and removing. A Headers object has an associated header list,
|
||||
* which is initially empty and consists of zero or more name and value pairs.
|
||||
* You can add to this using methods like append() (see Examples.) In all
|
||||
* methods of this interface, header names are matched by case-insensitive byte
|
||||
* sequence. */
|
||||
interface Headers {
|
||||
append(name: string, value: string): void;
|
||||
delete(name: string): void;
|
||||
get(name: string): string | null;
|
||||
has(name: string): boolean;
|
||||
set(name: string, value: string): void;
|
||||
forEach(
|
||||
callbackfn: (value: string, key: string, parent: Headers) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
}
|
||||
|
||||
interface Headers extends DomIterable<string, string> {
|
||||
/** Appends a new value onto an existing header inside a `Headers` object, or
|
||||
* adds the header if it does not already exist.
|
||||
*/
|
||||
append(name: string, value: string): void;
|
||||
/** Deletes a header from a `Headers` object. */
|
||||
delete(name: string): void;
|
||||
/** Returns an iterator allowing to go through all key/value pairs
|
||||
* contained in this Headers object. The both the key and value of each pairs
|
||||
* are ByteString objects.
|
||||
*/
|
||||
entries(): IterableIterator<[string, string]>;
|
||||
/** Returns a `ByteString` sequence of all the values of a header within a
|
||||
* `Headers` object with a given name.
|
||||
*/
|
||||
get(name: string): string | null;
|
||||
/** Returns a boolean stating whether a `Headers` object contains a certain
|
||||
* header.
|
||||
*/
|
||||
has(name: string): boolean;
|
||||
/** Returns an iterator allowing to go through all keys contained in
|
||||
* this Headers object. The keys are ByteString objects.
|
||||
*/
|
||||
keys(): IterableIterator<string>;
|
||||
/** Sets a new value for an existing header inside a Headers object, or adds
|
||||
* the header if it does not already exist.
|
||||
*/
|
||||
set(name: string, value: string): void;
|
||||
/** Returns an iterator allowing to go through all values contained in
|
||||
* this Headers object. The values are ByteString objects.
|
||||
*/
|
||||
values(): IterableIterator<string>;
|
||||
forEach(
|
||||
callbackfn: (value: string, key: string, parent: this) => void,
|
||||
thisArg?: any,
|
||||
): void;
|
||||
/** The Symbol.iterator well-known symbol specifies the default
|
||||
* iterator for this Headers object
|
||||
*/
|
||||
[Symbol.iterator](): IterableIterator<[string, string]>;
|
||||
}
|
||||
|
||||
declare const Headers: {
|
||||
prototype: Headers;
|
||||
new (init?: HeadersInit): Headers;
|
||||
};
|
||||
|
||||
type RequestInfo = Request | string;
|
||||
type RequestCache =
|
||||
| "default"
|
||||
| "force-cache"
|
||||
| "no-cache"
|
||||
| "no-store"
|
||||
| "only-if-cached"
|
||||
| "reload";
|
||||
type RequestCredentials = "include" | "omit" | "same-origin";
|
||||
type RequestMode = "cors" | "navigate" | "no-cors" | "same-origin";
|
||||
type RequestRedirect = "error" | "follow" | "manual";
|
||||
type ReferrerPolicy =
|
||||
| ""
|
||||
| "no-referrer"
|
||||
| "no-referrer-when-downgrade"
|
||||
| "origin"
|
||||
| "origin-when-cross-origin"
|
||||
| "same-origin"
|
||||
| "strict-origin"
|
||||
| "strict-origin-when-cross-origin"
|
||||
| "unsafe-url";
|
||||
type BodyInit =
|
||||
| Blob
|
||||
| BufferSource
|
||||
| FormData
|
||||
| URLSearchParams
|
||||
| ReadableStream<Uint8Array>
|
||||
| string;
|
||||
type RequestDestination =
|
||||
| ""
|
||||
| "audio"
|
||||
| "audioworklet"
|
||||
| "document"
|
||||
| "embed"
|
||||
| "font"
|
||||
| "image"
|
||||
| "manifest"
|
||||
| "object"
|
||||
| "paintworklet"
|
||||
| "report"
|
||||
| "script"
|
||||
| "sharedworker"
|
||||
| "style"
|
||||
| "track"
|
||||
| "video"
|
||||
| "worker"
|
||||
| "xslt";
|
||||
|
||||
interface RequestInit {
|
||||
/**
|
||||
* A BodyInit object or null to set request's body.
|
||||
*/
|
||||
body?: BodyInit | null;
|
||||
/**
|
||||
* A string indicating how the request will interact with the browser's cache
|
||||
* to set request's cache.
|
||||
*/
|
||||
cache?: RequestCache;
|
||||
/**
|
||||
* A string indicating whether credentials will be sent with the request
|
||||
* always, never, or only when sent to a same-origin URL. Sets request's
|
||||
* credentials.
|
||||
*/
|
||||
credentials?: RequestCredentials;
|
||||
/**
|
||||
* A Headers object, an object literal, or an array of two-item arrays to set
|
||||
* request's headers.
|
||||
*/
|
||||
headers?: HeadersInit;
|
||||
/**
|
||||
* A cryptographic hash of the resource to be fetched by request. Sets
|
||||
* request's integrity.
|
||||
*/
|
||||
integrity?: string;
|
||||
/**
|
||||
* A boolean to set request's keepalive.
|
||||
*/
|
||||
keepalive?: boolean;
|
||||
/**
|
||||
* A string to set request's method.
|
||||
*/
|
||||
method?: string;
|
||||
/**
|
||||
* A string to indicate whether the request will use CORS, or will be
|
||||
* restricted to same-origin URLs. Sets request's mode.
|
||||
*/
|
||||
mode?: RequestMode;
|
||||
/**
|
||||
* A string indicating whether request follows redirects, results in an error
|
||||
* upon encountering a redirect, or returns the redirect (in an opaque
|
||||
* fashion). Sets request's redirect.
|
||||
*/
|
||||
redirect?: RequestRedirect;
|
||||
/**
|
||||
* A string whose value is a same-origin URL, "about:client", or the empty
|
||||
* string, to set request's referrer.
|
||||
*/
|
||||
referrer?: string;
|
||||
/**
|
||||
* A referrer policy to set request's referrerPolicy.
|
||||
*/
|
||||
referrerPolicy?: ReferrerPolicy;
|
||||
/**
|
||||
* An AbortSignal to set request's signal.
|
||||
*/
|
||||
signal?: AbortSignal | null;
|
||||
/**
|
||||
* Can only be null. Used to disassociate request from any Window.
|
||||
*/
|
||||
window?: any;
|
||||
}
|
||||
|
||||
/** This Fetch API interface represents a resource request. */
|
||||
interface Request extends Body {
|
||||
/**
|
||||
* Returns the cache mode associated with request, which is a string
|
||||
* indicating how the request will interact with the browser's cache when
|
||||
* fetching.
|
||||
*/
|
||||
readonly cache: RequestCache;
|
||||
/**
|
||||
* Returns the credentials mode associated with request, which is a string
|
||||
* indicating whether credentials will be sent with the request always, never,
|
||||
* or only when sent to a same-origin URL.
|
||||
*/
|
||||
readonly credentials: RequestCredentials;
|
||||
/**
|
||||
* Returns the kind of resource requested by request, e.g., "document" or "script".
|
||||
*/
|
||||
readonly destination: RequestDestination;
|
||||
/**
|
||||
* Returns a Headers object consisting of the headers associated with request.
|
||||
* Note that headers added in the network layer by the user agent will not be
|
||||
* accounted for in this object, e.g., the "Host" header.
|
||||
*/
|
||||
readonly headers: Headers;
|
||||
/**
|
||||
* Returns request's subresource integrity metadata, which is a cryptographic
|
||||
* hash of the resource being fetched. Its value consists of multiple hashes
|
||||
* separated by whitespace. [SRI]
|
||||
*/
|
||||
readonly integrity: string;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a history
|
||||
* navigation (a.k.a. back-forward navigation).
|
||||
*/
|
||||
readonly isHistoryNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request is for a reload
|
||||
* navigation.
|
||||
*/
|
||||
readonly isReloadNavigation: boolean;
|
||||
/**
|
||||
* Returns a boolean indicating whether or not request can outlive the global
|
||||
* in which it was created.
|
||||
*/
|
||||
readonly keepalive: boolean;
|
||||
/**
|
||||
* Returns request's HTTP method, which is "GET" by default.
|
||||
*/
|
||||
readonly method: string;
|
||||
/**
|
||||
* Returns the mode associated with request, which is a string indicating
|
||||
* whether the request will use CORS, or will be restricted to same-origin
|
||||
* URLs.
|
||||
*/
|
||||
readonly mode: RequestMode;
|
||||
/**
|
||||
* Returns the redirect mode associated with request, which is a string
|
||||
* indicating how redirects for the request will be handled during fetching. A
|
||||
* request will follow redirects by default.
|
||||
*/
|
||||
readonly redirect: RequestRedirect;
|
||||
/**
|
||||
* Returns the referrer of request. Its value can be a same-origin URL if
|
||||
* explicitly set in init, the empty string to indicate no referrer, and
|
||||
* "about:client" when defaulting to the global's default. This is used during
|
||||
* fetching to determine the value of the `Referer` header of the request
|
||||
* being made.
|
||||
*/
|
||||
readonly referrer: string;
|
||||
/**
|
||||
* Returns the referrer policy associated with request. This is used during
|
||||
* fetching to compute the value of the request's referrer.
|
||||
*/
|
||||
readonly referrerPolicy: ReferrerPolicy;
|
||||
/**
|
||||
* Returns the signal associated with request, which is an AbortSignal object
|
||||
* indicating whether or not request has been aborted, and its abort event
|
||||
* handler.
|
||||
*/
|
||||
readonly signal: AbortSignal;
|
||||
/**
|
||||
* Returns the URL of request as a string.
|
||||
*/
|
||||
readonly url: string;
|
||||
clone(): Request;
|
||||
}
|
||||
|
||||
declare const Request: {
|
||||
prototype: Request;
|
||||
new (input: RequestInfo, init?: RequestInit): Request;
|
||||
};
|
||||
|
||||
declare const Response: {
|
||||
prototype: Response;
|
||||
new (body?: BodyInit | null, init?: ResponseInit): Response;
|
||||
error(): Response;
|
||||
redirect(url: string, status?: number): Response;
|
||||
};
|
||||
|
||||
/** Fetch a resource from the network. It returns a Promise that resolves to the
|
||||
* Response to that request, whether it is successful or not.
|
||||
*
|
||||
* const response = await fetch("http://my.json.host/data.json");
|
||||
* console.log(response.status); // e.g. 200
|
||||
* console.log(response.statusText); // e.g. "OK"
|
||||
* const jsonData = await response.json();
|
||||
*/
|
||||
declare function fetch(
|
||||
input: Request | URL | string,
|
||||
init?: RequestInit,
|
||||
): Promise<Response>;
|
266
op_crates/fetch/lib.rs
Normal file
266
op_crates/fetch/lib.rs
Normal file
|
@ -0,0 +1,266 @@
|
|||
// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use deno_core::error::bad_resource_id;
|
||||
use deno_core::error::type_error;
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::futures;
|
||||
use deno_core::js_check;
|
||||
use deno_core::serde_json;
|
||||
use deno_core::serde_json::json;
|
||||
use deno_core::serde_json::Value;
|
||||
use deno_core::url;
|
||||
use deno_core::url::Url;
|
||||
use deno_core::BufVec;
|
||||
use deno_core::JsRuntime;
|
||||
use deno_core::OpState;
|
||||
use deno_core::ZeroCopyBuf;
|
||||
use reqwest::header::HeaderMap;
|
||||
use reqwest::header::HeaderName;
|
||||
use reqwest::header::HeaderValue;
|
||||
use reqwest::header::USER_AGENT;
|
||||
use reqwest::redirect::Policy;
|
||||
use reqwest::Client;
|
||||
use reqwest::Method;
|
||||
use reqwest::Response;
|
||||
use serde::Deserialize;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::From;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn init(isolate: &mut JsRuntime) {
|
||||
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let files = vec![
|
||||
manifest_dir.join("01_fetch_util.js"),
|
||||
manifest_dir.join("03_dom_iterable.js"),
|
||||
manifest_dir.join("11_streams.js"),
|
||||
manifest_dir.join("20_headers.js"),
|
||||
manifest_dir.join("26_fetch.js"),
|
||||
];
|
||||
// TODO(nayeemrmn): https://github.com/rust-lang/cargo/issues/3946 to get the
|
||||
// workspace root.
|
||||
let display_root = manifest_dir.parent().unwrap().parent().unwrap();
|
||||
for file in files {
|
||||
println!("cargo:rerun-if-changed={}", file.display());
|
||||
let display_path = file.strip_prefix(display_root).unwrap();
|
||||
let display_path_str = display_path.display().to_string();
|
||||
js_check(isolate.execute(
|
||||
&("deno:".to_string() + &display_path_str.replace('\\', "/")),
|
||||
&std::fs::read_to_string(&file).unwrap(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait FetchPermissions {
|
||||
fn check_net_url(&self, url: &Url) -> Result<(), AnyError>;
|
||||
fn check_read(&self, p: &PathBuf) -> Result<(), AnyError>;
|
||||
}
|
||||
|
||||
pub fn get_declaration() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_fetch.d.ts")
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct FetchArgs {
|
||||
method: Option<String>,
|
||||
url: String,
|
||||
headers: Vec<(String, String)>,
|
||||
client_rid: Option<u32>,
|
||||
}
|
||||
|
||||
pub async fn op_fetch<FP>(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
data: BufVec,
|
||||
) -> Result<Value, AnyError>
|
||||
where
|
||||
FP: FetchPermissions + 'static,
|
||||
{
|
||||
let args: FetchArgs = serde_json::from_value(args)?;
|
||||
let url = args.url;
|
||||
|
||||
let client = if let Some(rid) = args.client_rid {
|
||||
let state_ = state.borrow();
|
||||
let r = state_
|
||||
.resource_table
|
||||
.get::<HttpClientResource>(rid)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
r.client.clone()
|
||||
} else {
|
||||
let state_ = state.borrow();
|
||||
let client = state_.borrow::<reqwest::Client>();
|
||||
client.clone()
|
||||
};
|
||||
|
||||
let method = match args.method {
|
||||
Some(method_str) => Method::from_bytes(method_str.as_bytes())?,
|
||||
None => Method::GET,
|
||||
};
|
||||
|
||||
let url_ = url::Url::parse(&url)?;
|
||||
|
||||
// Check scheme before asking for net permission
|
||||
let scheme = url_.scheme();
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return Err(type_error(format!("scheme '{}' not supported", scheme)));
|
||||
}
|
||||
|
||||
{
|
||||
let state_ = state.borrow();
|
||||
// TODO(ry) The Rc below is a hack because we store Rc<CliState> in OpState.
|
||||
// Ideally it could be removed.
|
||||
let permissions = state_.borrow::<Rc<FP>>();
|
||||
permissions.check_net_url(&url_)?;
|
||||
}
|
||||
|
||||
let mut request = client.request(method, url_);
|
||||
|
||||
match data.len() {
|
||||
0 => {}
|
||||
1 => request = request.body(Vec::from(&*data[0])),
|
||||
_ => panic!("Invalid number of arguments"),
|
||||
}
|
||||
|
||||
for (key, value) in args.headers {
|
||||
let name = HeaderName::from_bytes(key.as_bytes()).unwrap();
|
||||
let v = HeaderValue::from_str(&value).unwrap();
|
||||
request = request.header(name, v);
|
||||
}
|
||||
//debug!("Before fetch {}", url);
|
||||
|
||||
let res = request.send().await?;
|
||||
|
||||
//debug!("Fetch response {}", url);
|
||||
let status = res.status();
|
||||
let mut res_headers = Vec::new();
|
||||
for (key, val) in res.headers().iter() {
|
||||
res_headers.push((key.to_string(), val.to_str().unwrap().to_owned()));
|
||||
}
|
||||
|
||||
let rid = state
|
||||
.borrow_mut()
|
||||
.resource_table
|
||||
.add("httpBody", Box::new(res));
|
||||
|
||||
Ok(json!({
|
||||
"bodyRid": rid,
|
||||
"status": status.as_u16(),
|
||||
"statusText": status.canonical_reason().unwrap_or(""),
|
||||
"headers": res_headers
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn op_fetch_read(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
args: Value,
|
||||
_data: BufVec,
|
||||
) -> Result<Value, AnyError> {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct Args {
|
||||
rid: u32,
|
||||
}
|
||||
|
||||
let args: Args = serde_json::from_value(args)?;
|
||||
let rid = args.rid;
|
||||
|
||||
use futures::future::poll_fn;
|
||||
use futures::ready;
|
||||
use futures::FutureExt;
|
||||
let f = poll_fn(move |cx| {
|
||||
let mut state = state.borrow_mut();
|
||||
let response = state
|
||||
.resource_table
|
||||
.get_mut::<Response>(rid as u32)
|
||||
.ok_or_else(bad_resource_id)?;
|
||||
|
||||
let mut chunk_fut = response.chunk().boxed_local();
|
||||
let r = ready!(chunk_fut.poll_unpin(cx))?;
|
||||
if let Some(chunk) = r {
|
||||
Ok(json!({ "chunk": &*chunk })).into()
|
||||
} else {
|
||||
Ok(json!({ "chunk": null })).into()
|
||||
}
|
||||
});
|
||||
f.await
|
||||
/*
|
||||
// I'm programming this as I want it to be programmed, even though it might be
|
||||
// incorrect, normally we would use poll_fn here. We need to make this await pattern work.
|
||||
let chunk = response.chunk().await?;
|
||||
if let Some(chunk) = chunk {
|
||||
// TODO(ry) This is terribly inefficient. Make this zero-copy.
|
||||
Ok(json!({ "chunk": &*chunk }))
|
||||
} else {
|
||||
Ok(json!({ "chunk": null }))
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
struct HttpClientResource {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl HttpClientResource {
|
||||
fn new(client: Client) -> Self {
|
||||
Self { client }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op_create_http_client<FP>(
|
||||
state: &mut OpState,
|
||||
args: Value,
|
||||
_zero_copy: &mut [ZeroCopyBuf],
|
||||
) -> Result<Value, AnyError>
|
||||
where
|
||||
FP: FetchPermissions + 'static,
|
||||
{
|
||||
#[derive(Deserialize, Default, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(default)]
|
||||
struct CreateHttpClientOptions {
|
||||
ca_file: Option<String>,
|
||||
}
|
||||
|
||||
let args: CreateHttpClientOptions = serde_json::from_value(args)?;
|
||||
|
||||
if let Some(ca_file) = args.ca_file.clone() {
|
||||
// TODO(ry) The Rc below is a hack because we store Rc<CliState> in OpState.
|
||||
// Ideally it could be removed.
|
||||
let permissions = state.borrow::<Rc<FP>>();
|
||||
permissions.check_read(&PathBuf::from(ca_file))?;
|
||||
}
|
||||
|
||||
let client = create_http_client(args.ca_file.as_deref()).unwrap();
|
||||
|
||||
let rid = state
|
||||
.resource_table
|
||||
.add("httpClient", Box::new(HttpClientResource::new(client)));
|
||||
Ok(json!(rid))
|
||||
}
|
||||
|
||||
/// Create new instance of async reqwest::Client. This client supports
|
||||
/// proxies and doesn't follow redirects.
|
||||
fn create_http_client(ca_file: Option<&str>) -> Result<Client, AnyError> {
|
||||
let mut headers = HeaderMap::new();
|
||||
// TODO(ry) set the verison correctly.
|
||||
headers.insert(USER_AGENT, format!("Deno/{}", "x.x.x").parse().unwrap());
|
||||
let mut builder = Client::builder()
|
||||
.redirect(Policy::none())
|
||||
.default_headers(headers)
|
||||
.use_rustls_tls();
|
||||
|
||||
if let Some(ca_file) = ca_file {
|
||||
let mut buf = Vec::new();
|
||||
File::open(ca_file)?.read_to_end(&mut buf)?;
|
||||
let cert = reqwest::Certificate::from_pem(&buf)?;
|
||||
builder = builder.add_root_certificate(cert);
|
||||
}
|
||||
|
||||
builder
|
||||
.build()
|
||||
.map_err(|_| deno_core::error::generic_error("Unable to build http client"))
|
||||
}
|
|
@ -197,7 +197,7 @@ Deno.test("contentType", async () => {
|
|||
(response.body as Deno.File).close();
|
||||
});
|
||||
|
||||
/*
|
||||
/* TODO https://github.com/denoland/deno/issues/7540
|
||||
Deno.test("file_server running as library", async function (): Promise<void> {
|
||||
await startFileServerAsLibrary();
|
||||
try {
|
||||
|
|
Loading…
Reference in a new issue