// Copyright 2018 Ryan Dahl // All rights reserved. MIT License. import { assert, log, createResolvable, Resolvable } from "./util"; import * as util from "./util"; import * as dispatch from "./dispatch"; import { main as pb } from "./msg.pb"; import { TextDecoder } from "text-encoding"; export function initFetch() { dispatch.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); }); } const fetchRequests = new Map(); class FetchResponse implements Response { readonly url: string; body: null; bodyUsed = false; // TODO status: number; statusText = "FIXME"; // TODO readonly type = "basic"; // TODO redirected = false; // TODO headers: null; // TODO //private bodyChunks: Uint8Array[] = []; private first = true; constructor(readonly req: FetchRequest) { this.url = req.url; } bodyWaiter: Resolvable; arrayBuffer(): Promise { this.bodyWaiter = createResolvable(); return this.bodyWaiter; } blob(): Promise { throw Error("not implemented"); } formData(): Promise { throw Error("not implemented"); } async json(): Promise { const text = await this.text(); return JSON.parse(text); } async text(): Promise { const ab = await this.arrayBuffer(); const decoder = new TextDecoder("utf-8"); return decoder.decode(ab); } get ok(): boolean { return 200 <= this.status && this.status < 300; } clone(): Response { throw Error("not implemented"); } onHeader: (res: Response) => 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)); return; } if (this.first) { this.first = false; this.status = msg.fetchResStatus; this.onHeader(this); } else { // Body message. Assuming it all comes in one message now. const ab = util.typedArrayToArrayBuffer(msg.fetchResBody); this.bodyWaiter.resolve(ab); } } } let nextFetchId = 0; //TODO implements Request class FetchRequest { private readonly id: number; response: FetchResponse; constructor(readonly url: string) { this.id = nextFetchId++; fetchRequests.set(this.id, this); this.response = new FetchResponse(this); } onMsg(msg: pb.Msg) { this.response.onMsg(msg); } destroy() { fetchRequests.delete(this.id); } start() { log("dispatch FETCH_REQ", this.id, this.url); const res = dispatch.sendMsg("fetch", { command: pb.Msg.Command.FETCH_REQ, fetchReqId: this.id, fetchReqUrl: this.url }); assert(res == null); } } export function fetch( input?: Request | string, init?: RequestInit ): Promise { 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) => { log("onHeader"); resolve(response); }; response.onError = (error: Error) => { log("onError", error); reject(error); }; fetchReq.start(); }); }