diff --git a/BUILD.gn b/BUILD.gn index 95640c7c88..38b194b9a3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -197,6 +197,8 @@ run_node("bundle") { sources = [ "js/assets.ts", "js/console.ts", + "js/fetch.ts", + "js/fetch_types.d.ts", "js/globals.ts", "js/lib.globals.d.ts", "js/main.ts", diff --git a/js/assets.ts b/js/assets.ts index 6261f4ad3b..425ef3fc27 100644 --- a/js/assets.ts +++ b/js/assets.ts @@ -11,6 +11,7 @@ import consoleDts from "gen/js/console.d.ts!string"; import denoDts from "gen/js/deno.d.ts!string"; import globalsDts from "gen/js/globals.d.ts!string"; import osDts from "gen/js/os.d.ts!string"; +import fetchDts from "gen/js/fetch.d.ts!string"; import timersDts from "gen/js/timers.d.ts!string"; import utilDts from "gen/js/util.d.ts!string"; @@ -48,6 +49,7 @@ import libGlobalsDts from "/js/lib.globals.d.ts!string"; // Static definitions import typescriptDts from "/third_party/node_modules/typescript/lib/typescript.d.ts!string"; import typesDts from "/js/types.d.ts!string"; +import fetchTypesDts from "/js/fetch_types.d.ts!string"; // tslint:enable:max-line-length // prettier-ignore @@ -57,6 +59,8 @@ export const assetSourceCode: { [key: string]: string } = { "deno.d.ts": denoDts, "globals.d.ts": globalsDts, "os.d.ts": osDts, + "fetch.d.ts": fetchDts, + "fetch_types.d.ts": fetchTypesDts, "timers.d.ts": timersDts, "util.d.ts": utilDts, @@ -94,4 +98,7 @@ export const assetSourceCode: { [key: string]: string } = { // Static definitions "typescript.d.ts": typescriptDts, "types.d.ts": typesDts, + + // TODO Remove. + "msg_generated.d.ts": "", }; diff --git a/js/console.ts b/js/console.ts index 48ecc6e7e1..2eb5e27c0b 100644 --- a/js/console.ts +++ b/js/console.ts @@ -112,7 +112,8 @@ export class Console { // tslint:disable-next-line:no-any warn(...args: any[]): void { - this.printFunc(`ERROR: ${stringifyArgs(args)}`); + // TODO Log to stderr. + this.printFunc(stringifyArgs(args)); } error = this.warn; diff --git a/js/fetch.ts b/js/fetch.ts index 48696778e8..599d02baf5 100644 --- a/js/fetch.ts +++ b/js/fetch.ts @@ -7,42 +7,77 @@ import { typedArrayToArrayBuffer, notImplemented, } from "./util"; -import { pubInternal, sub } from "./dispatch"; -import { deno as pb } from "./msg.pb"; +import { flatbuffers } from "flatbuffers"; +import { libdeno } from "./globals"; +import { deno as fbs } from "gen/msg_generated"; +import { + Headers, + Request, + Response, + Blob, + RequestInit, + FormData +} from "./fetch_types"; +import { TextDecoder } from "./text_encoding"; -export function initFetch() { - sub("fetch", (payload: Uint8Array) => { - const msg = pb.Msg.decode(payload); - assert(msg.command === pb.Msg.Command.FETCH_RES); - const id = msg.fetchResId; - const f = fetchRequests.get(id); - assert(f != null, `Couldn't find FetchRequest id ${id}`); - - f.onMsg(msg); - }); +/** @internal */ +export function onFetchRes(base: fbs.Base, msg: fbs.FetchRes) { + const id = msg.id(); + const req = fetchRequests.get(id); + assert(req != null, `Couldn't find FetchRequest id ${id}`); + req!.onMsg(base, msg); } const fetchRequests = new Map(); +class DenoHeaders implements Headers { + append(name: string, value: string): void { + assert(false, "Implement me"); + } + delete(name: string): void { + assert(false, "Implement me"); + } + get(name: string): string | null { + assert(false, "Implement me"); + return null; + } + has(name: string): boolean { + assert(false, "Implement me"); + return false; + } + set(name: string, value: string): void { + assert(false, "Implement me"); + } + forEach( + callbackfn: (value: string, key: string, parent: Headers) => void, + // tslint:disable-next-line:no-any + thisArg?: any + ): void { + assert(false, "Implement me"); + } +} + class FetchResponse implements Response { readonly url: string; body: null; bodyUsed = false; // TODO - status: number; + status = 0; statusText = "FIXME"; // TODO readonly type = "basic"; // TODO redirected = false; // TODO - headers: null; // TODO + headers = new DenoHeaders(); + readonly trailer: Promise; //private bodyChunks: Uint8Array[] = []; private first = true; + private bodyWaiter: Resolvable; constructor(readonly req: FetchRequest) { this.url = req.url; + this.bodyWaiter = createResolvable(); + this.trailer = createResolvable(); } - bodyWaiter: Resolvable; arrayBuffer(): Promise { - this.bodyWaiter = createResolvable(); return this.bodyWaiter; } @@ -73,23 +108,27 @@ class FetchResponse implements Response { notImplemented(); } - onHeader: (res: Response) => void; - onError: (error: Error) => void; + onHeader?: (res: FetchResponse) => void; + onError?: (error: Error) => void; - onMsg(msg: pb.Msg) { - if (msg.error !== null && msg.error !== "") { - //throw new Error(msg.error) - this.onError(new Error(msg.error)); + onMsg(base: fbs.Base, msg: fbs.FetchRes) { + const error = base.error(); + if (error != null) { + assert(this.onError != null); + this.onError!(new Error(error)); return; } if (this.first) { this.first = false; - this.status = msg.fetchResStatus; - this.onHeader(this); + this.status = msg.status(); + assert(this.onHeader != null); + this.onHeader!(this); } else { // Body message. Assuming it all comes in one message now. - const ab = typedArrayToArrayBuffer(msg.fetchResBody); + const bodyArray = msg.bodyArray(); + assert(bodyArray != null); + const ab = typedArrayToArrayBuffer(bodyArray!); this.bodyWaiter.resolve(ab); } } @@ -106,8 +145,8 @@ class FetchRequest { this.response = new FetchResponse(this); } - onMsg(msg: pb.Msg) { - this.response.onMsg(msg); + onMsg(base: fbs.Base, msg: fbs.FetchRes) { + this.response.onMsg(base, msg); } destroy() { @@ -116,12 +155,22 @@ class FetchRequest { start() { log("dispatch FETCH_REQ", this.id, this.url); - const res = pubInternal("fetch", { - command: pb.Msg.Command.FETCH_REQ, - fetchReqId: this.id, - fetchReqUrl: this.url - }); - assert(res == null); + + // Send FetchReq message + const builder = new flatbuffers.Builder(); + const url = builder.createString(this.url); + fbs.FetchReq.startFetchReq(builder); + fbs.FetchReq.addId(builder, this.id); + fbs.FetchReq.addUrl(builder, url); + const msg = fbs.FetchReq.endFetchReq(builder); + fbs.Base.startBase(builder); + fbs.Base.addMsg(builder, msg); + fbs.Base.addMsgType(builder, fbs.Any.FetchReq); + builder.finish(fbs.Base.endBase(builder)); + const resBuf = libdeno.send(builder.asUint8Array()); + assert(resBuf == null); + + //console.log("FetchReq sent", builder); } } @@ -132,8 +181,7 @@ export function fetch( const fetchReq = new FetchRequest(input as string); const response = fetchReq.response; return new Promise((resolve, reject) => { - // tslint:disable-next-line:no-any - response.onHeader = (response: any) => { + response.onHeader = (response: FetchResponse) => { log("onHeader"); resolve(response); }; diff --git a/js/fetch_types.d.ts b/js/fetch_types.d.ts new file mode 100644 index 0000000000..644cb76ee8 --- /dev/null +++ b/js/fetch_types.d.ts @@ -0,0 +1,441 @@ +/*! **************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +*******************************************************************************/ + +type HeadersInit = Headers | string[][] | Record; +type BodyInit = + | Blob + | BufferSource + | FormData + | URLSearchParams + | ReadableStream + | string; +type RequestInfo = Request | string; +type ReferrerPolicy = + | "" + | "no-referrer" + | "no-referrer-when-downgrade" + | "origin-only" + | "origin-when-cross-origin" + | "unsafe-url"; +type BlobPart = BufferSource | Blob | string; +declare type EventListenerOrEventListenerObject = + | EventListener + | EventListenerObject; + +interface Element { + // TODO +} + +interface HTMLFormElement { + // TODO +} + +interface FormDataEntryValue { + // TODO +} + +interface BlobPropertyBag { + type?: string; +} + +interface AbortSignalEventMap { + abort: ProgressEvent; +} + +interface EventTarget { + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject | null, + options?: boolean | AddEventListenerOptions + ): void; + dispatchEvent(evt: Event): boolean; + removeEventListener( + type: string, + listener?: EventListenerOrEventListenerObject | null, + options?: EventListenerOptions | boolean + ): void; +} + +interface ProgressEventInit extends EventInit { + lengthComputable?: boolean; + loaded?: number; + total?: number; +} + +interface URLSearchParams { + /** + * Appends a specified key/value pair as a new search parameter. + */ + append(name: string, value: string): void; + /** + * Deletes the given search parameter, and its associated value, from the list of all search parameters. + */ + delete(name: string): void; + /** + * Returns the first value associated to the given search parameter. + */ + get(name: string): string | null; + /** + * Returns all the values association with a given search parameter. + */ + getAll(name: string): string[]; + /** + * Returns a Boolean indicating if such a search parameter exists. + */ + has(name: string): boolean; + /** + * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. + */ + set(name: string, value: string): void; + sort(): void; + forEach( + callbackfn: (value: string, key: string, parent: URLSearchParams) => void, + thisArg?: any + ): void; +} + +interface EventListener { + (evt: Event): void; +} + +interface EventInit { + bubbles?: boolean; + cancelable?: boolean; + composed?: boolean; +} + +interface Event { + readonly bubbles: boolean; + cancelBubble: boolean; + readonly cancelable: boolean; + readonly composed: boolean; + readonly currentTarget: EventTarget | null; + readonly defaultPrevented: boolean; + readonly eventPhase: number; + readonly isTrusted: boolean; + returnValue: boolean; + readonly srcElement: Element | null; + readonly target: EventTarget | null; + readonly timeStamp: number; + readonly type: string; + deepPath(): EventTarget[]; + initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void; + preventDefault(): void; + stopImmediatePropagation(): void; + stopPropagation(): void; + readonly AT_TARGET: number; + readonly BUBBLING_PHASE: number; + readonly CAPTURING_PHASE: number; + readonly NONE: number; +} + +interface ProgressEvent extends Event { + readonly lengthComputable: boolean; + readonly loaded: number; + readonly total: number; +} + +declare var ProgressEvent: { + prototype: ProgressEvent; + new (type: string, eventInitDict?: ProgressEventInit): ProgressEvent; +}; + +interface EventListenerOptions { + capture?: boolean; +} + +interface AddEventListenerOptions extends EventListenerOptions { + once?: boolean; + passive?: boolean; +} + +interface AbortSignal extends EventTarget { + readonly aborted: boolean; + onabort: ((this: AbortSignal, ev: ProgressEvent) => any) | null; + addEventListener( + type: K, + listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, + options?: boolean | AddEventListenerOptions + ): void; + addEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | AddEventListenerOptions + ): void; + removeEventListener( + type: K, + listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => any, + options?: boolean | EventListenerOptions + ): void; + removeEventListener( + type: string, + listener: EventListenerOrEventListenerObject, + options?: boolean | EventListenerOptions + ): void; +} + +declare var AbortSignal: { + prototype: AbortSignal; + new (): AbortSignal; +}; + +interface ReadableStream { + readonly locked: boolean; + cancel(): Promise; + getReader(): ReadableStreamReader; +} + +declare var ReadableStream: { + prototype: ReadableStream; + new (): ReadableStream; +}; + +interface EventListenerObject { + handleEvent(evt: Event): void; +} + +interface ReadableStreamReader { + cancel(): Promise; + read(): Promise; + releaseLock(): void; +} + +declare var ReadableStreamReader: { + prototype: ReadableStreamReader; + new (): ReadableStreamReader; +}; + +interface FormData { + 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; + forEach( + callbackfn: ( + value: FormDataEntryValue, + key: string, + parent: FormData + ) => void, + thisArg?: any + ): void; +} + +declare var FormData: { + prototype: FormData; + new (form?: HTMLFormElement): FormData; +}; + +export interface Blob { + readonly size: number; + readonly type: string; + slice(start?: number, end?: number, contentType?: string): Blob; +} + +declare var Blob: { + prototype: Blob; + new (blobParts?: BlobPart[], options?: BlobPropertyBag): Blob; +}; + +interface Body { + readonly body: ReadableStream | null; + readonly bodyUsed: boolean; + arrayBuffer(): Promise; + blob(): Promise; + formData(): Promise; + json(): Promise; + text(): Promise; +} + +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; +} + +declare var Headers: { + prototype: Headers; + new (init?: HeadersInit): Headers; +}; + +type RequestCache = + | "default" + | "no-store" + | "reload" + | "no-cache" + | "force-cache" + | "only-if-cached"; +type RequestCredentials = "omit" | "same-origin" | "include"; +type RequestDestination = + | "" + | "audio" + | "audioworklet" + | "document" + | "embed" + | "font" + | "image" + | "manifest" + | "object" + | "paintworklet" + | "report" + | "script" + | "sharedworker" + | "style" + | "track" + | "video" + | "worker" + | "xslt"; +type RequestMode = "navigate" | "same-origin" | "no-cors" | "cors"; +type RequestRedirect = "follow" | "error" | "manual"; +type ResponseType = + | "basic" + | "cors" + | "default" + | "error" + | "opaque" + | "opaqueredirect"; + +export interface RequestInit { + body?: BodyInit | null; + cache?: RequestCache; + credentials?: RequestCredentials; + headers?: HeadersInit; + integrity?: string; + keepalive?: boolean; + method?: string; + mode?: RequestMode; + redirect?: RequestRedirect; + referrer?: string; + referrerPolicy?: ReferrerPolicy; + signal?: AbortSignal | null; + window?: any; +} + +export interface ResponseInit { + headers?: HeadersInit; + status?: number; + statusText?: string; +} + +export interface Request extends Body { + /** + * Returns the cache mode associated with request, which is a string indicating + * how the 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-foward 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 var Request: { + prototype: Request; + new (input: RequestInfo, init?: RequestInit): Request; +}; + +export interface Response extends Body { + readonly headers: Headers; + readonly ok: boolean; + readonly redirected: boolean; + readonly status: number; + readonly statusText: string; + readonly trailer: Promise; + readonly type: ResponseType; + readonly url: string; + clone(): Response; +} + +declare var Response: { + prototype: Response; + new (body?: BodyInit | null, init?: ResponseInit): Response; + error(): Response; + redirect(url: string, status?: number): Response; +}; diff --git a/js/globals.ts b/js/globals.ts index a6f1b0927a..ebfd3d265f 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -4,6 +4,7 @@ import { Console } from "./console"; import { RawSourceMap } from "./types"; import * as timers from "./timers"; import { TextEncoder, TextDecoder } from "./text_encoding"; +import * as fetch_ from "./fetch"; declare global { interface Window { @@ -18,6 +19,8 @@ declare global { const console: Console; const window: Window; + const fetch: typeof fetch_.fetch; + // tslint:disable:variable-name let TextEncoder: TextEncoder; let TextDecoder: TextDecoder; @@ -58,5 +61,4 @@ window.console = new Console(libdeno.print); window.TextEncoder = TextEncoder; window.TextDecoder = TextDecoder; -// import { fetch } from "./fetch"; -// window["fetch"] = fetch; +window.fetch = fetch_.fetch; diff --git a/js/main.ts b/js/main.ts index c712f7e08c..d035f9ba6d 100644 --- a/js/main.ts +++ b/js/main.ts @@ -7,6 +7,7 @@ import * as os from "./os"; import * as runtime from "./runtime"; import { libdeno } from "./globals"; import * as timers from "./timers"; +import { onFetchRes } from "./fetch"; function startMsg(cmdId: number): Uint8Array { const builder = new flatbuffers.Builder(); @@ -24,6 +25,12 @@ function onMessage(ui8: Uint8Array) { const bb = new flatbuffers.ByteBuffer(ui8); const base = fbs.Base.getRootAsBase(bb); switch (base.msgType()) { + case fbs.Any.FetchRes: { + const msg = new fbs.FetchRes(); + assert(base.msg(msg) != null); + onFetchRes(base, msg); + break; + } case fbs.Any.TimerReady: { const msg = new fbs.TimerReady(); assert(base.msg(msg) != null); diff --git a/js/unit_tests.ts b/js/unit_tests.ts index 7caae52830..96341b4a24 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -92,13 +92,13 @@ test(async function tests_readFileSync() { assertEqual(pkg.name, "deno"); }); -/* test(async function tests_fetch() { const response = await fetch("http://localhost:4545/package.json"); const json = await response.json(); assertEqual(json.name, "deno"); }); +/* test(async function tests_writeFileSync() { const enc = new TextEncoder(); const data = enc.encode("Hello"); diff --git a/js/util.ts b/js/util.ts index 3eaa98a6b6..1754dc6639 100644 --- a/js/util.ts +++ b/js/util.ts @@ -56,7 +56,7 @@ export interface ResolvableMethods { reject: (reason?: any) => void; } -type Resolvable = Promise & ResolvableMethods; +export type Resolvable = Promise & ResolvableMethods; export function createResolvable(): Resolvable { let methods: ResolvableMethods; diff --git a/src/handlers.rs b/src/handlers.rs index 21e5b2baaf..b40bbc67bb 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -6,9 +6,13 @@ use from_c; use fs; use futures; use futures::sync::oneshot; +use hyper; +use hyper::rt::{Future, Stream}; +use hyper::Client; use msg_generated::deno as msg; use std; use std::path::Path; +use tokio::prelude::future; pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { let bytes = unsafe { std::slice::from_raw_parts(buf.data_ptr, buf.data_len) }; @@ -33,6 +37,12 @@ pub extern "C" fn msg_from_js(d: *const DenoC, buf: deno_buf) { let output_code = msg.output_code().unwrap(); handle_code_cache(d, filename, source_code, output_code); } + msg::Any::FetchReq => { + // TODO base.msg_as_FetchReq(); + let msg = msg::FetchReq::init_from_table(base.msg().unwrap()); + let url = msg.url().unwrap(); + handle_fetch_req(d, msg.id(), url); + } msg::Any::TimerStart => { // TODO base.msg_as_TimerStart(); let msg = msg::TimerStart::init_from_table(base.msg().unwrap()); @@ -210,6 +220,92 @@ fn handle_code_cache( // null response indicates success. } +fn handle_fetch_req(d: *const DenoC, id: u32, url: &str) { + let deno = from_c(d); + let url = url.parse::().unwrap(); + let client = Client::new(); + + deno.rt.spawn( + client + .get(url) + .map(move |res| { + let status = res.status().as_u16() as i32; + + // Send the first message without a body. This is just to indicate + // what status code. + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let msg = msg::FetchRes::create( + &mut builder, + &msg::FetchResArgs { + id, + status, + ..Default::default() + }, + ); + send_base( + d, + &mut builder, + &msg::BaseArgs { + msg: Some(flatbuffers::Offset::new(msg.value())), + msg_type: msg::Any::FetchRes, + ..Default::default() + }, + ); + res + }) + .and_then(move |res| { + // Send the body as a FetchRes message. + res.into_body().concat2().map(move |body_buffer| { + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let data_off = builder.create_byte_vector(body_buffer.as_ref()); + let msg = msg::FetchRes::create( + &mut builder, + &msg::FetchResArgs { + id, + body: Some(data_off), + ..Default::default() + }, + ); + send_base( + d, + &mut builder, + &msg::BaseArgs { + msg: Some(flatbuffers::Offset::new(msg.value())), + msg_type: msg::Any::FetchRes, + ..Default::default() + }, + ); + }) + }) + .map_err(move |err| { + let errmsg = format!("{}", err); + + // TODO This is obviously a lot of duplicated code from the success case. + // Leaving it here now jsut to get a first pass implementation, but this + // needs to be cleaned up. + let mut builder = flatbuffers::FlatBufferBuilder::new(); + let err_off = builder.create_string(errmsg.as_str()); + let msg = msg::FetchRes::create( + &mut builder, + &msg::FetchResArgs { + id, + ..Default::default() + }, + ); + send_base( + d, + &mut builder, + &msg::BaseArgs { + msg: Some(flatbuffers::Offset::new(msg.value())), + msg_type: msg::Any::FetchRes, + error: Some(err_off), + ..Default::default() + }, + ); + }), + ); +} + fn set_timeout( cb: F, delay: u32, diff --git a/src/main.rs b/src/main.rs index b5eab9e7c2..2d83b8259c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ extern crate flatbuffers; extern crate futures; +extern crate hyper; extern crate libc; extern crate msg_rs as msg_generated; extern crate sha1; diff --git a/tests/fetch_deps.ts b/tests/fetch_deps.ts new file mode 100644 index 0000000000..d2690e01c0 --- /dev/null +++ b/tests/fetch_deps.ts @@ -0,0 +1,14 @@ +// Run ./tools/http_server.py too in order for this test to run. +import { assert } from "../js/testing/util.ts"; + +// TODO Top level await https://github.com/denoland/deno/issues/471 +async function main() { + const response = await fetch("http://localhost:4545/package.json"); + const json = await response.json(); + const deps = Object.keys(json.devDependencies); + console.log("Deno JS Deps"); + console.log(deps.map(d => `* ${d}`).join("\n")); + assert(deps.includes("typescript")); +} + +main();