deno/js/fetch.ts

195 lines
4.7 KiB
TypeScript
Raw Normal View History

// Copyright 2018 the Deno authors. All rights reserved. MIT license.
import {
assert,
log,
createResolvable,
Resolvable,
typedArrayToArrayBuffer,
notImplemented,
} from "./util";
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";
/** @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<number, FetchRequest>();
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 = 0;
statusText = "FIXME"; // TODO
readonly type = "basic"; // TODO
redirected = false; // TODO
headers = new DenoHeaders();
readonly trailer: Promise<Headers>;
//private bodyChunks: Uint8Array[] = [];
private first = true;
private bodyWaiter: Resolvable<ArrayBuffer>;
constructor(readonly req: FetchRequest) {
this.url = req.url;
this.bodyWaiter = createResolvable();
this.trailer = createResolvable();
}
arrayBuffer(): Promise<ArrayBuffer> {
return this.bodyWaiter;
}
blob(): Promise<Blob> {
notImplemented();
}
formData(): Promise<FormData> {
notImplemented();
}
async json(): Promise<object> {
const text = await this.text();
return JSON.parse(text);
}
async text(): Promise<string> {
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 {
notImplemented();
}
onHeader?: (res: FetchResponse) => void;
onError?: (error: Error) => void;
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.status();
assert(this.onHeader != null);
this.onHeader!(this);
} else {
// Body message. Assuming it all comes in one message now.
const bodyArray = msg.bodyArray();
assert(bodyArray != null);
const ab = typedArrayToArrayBuffer(bodyArray!);
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(base: fbs.Base, msg: fbs.FetchRes) {
this.response.onMsg(base, msg);
}
destroy() {
fetchRequests.delete(this.id);
}
start() {
log("dispatch FETCH_REQ", this.id, this.url);
// 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);
}
}
export function fetch(
input?: Request | string,
init?: RequestInit
): Promise<Response> {
const fetchReq = new FetchRequest(input as string);
const response = fetchReq.response;
return new Promise((resolve, reject) => {
response.onHeader = (response: FetchResponse) => {
log("onHeader");
resolve(response);
};
response.onError = (error: Error) => {
log("onError", error);
reject(error);
};
fetchReq.start();
});
}