Skip to content

Commit

Permalink
fetch API
Browse files Browse the repository at this point in the history
  • Loading branch information
herudi committed Jul 22, 2024
1 parent 6f4fa93 commit f9de3ab
Show file tree
Hide file tree
Showing 16 changed files with 535 additions and 39 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,10 @@ fn main() {
- [x] [File](https://developer.mozilla.org/en-US/docs/Web/API/File)
- [x] [Performance](https://developer.mozilla.org/en-US/docs/Web/API/Performance)
- [x] [Navigator](https://developer.mozilla.org/en-US/docs/Web/API/Navigator)
- [ ] Fetch API
- [ ] Headers
- [ ] Request
- [ ] Response
- [x] Fetch API
- [x] Headers
- [x] Request
- [x] Response
- <i>More...</i>

### It's Fun Project. PRs Wellcome :)
91 changes: 91 additions & 0 deletions web/fetch.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
module web

import vjs { Context, Value }
import net.http { Method, Response, parse_multipart_form }

fn fetch(this Value, args []Value) Value {
mut error := this.ctx.js_undefined()
promise := this.ctx.js_promise()
url := args[0].str()
opts := args[1]
header := opts.get('headers')
method := opts.get('method').str().to_lower()
raw_body := opts.get('body')
boundary := opts.get('boundary')
mut hd := http.new_header()
props := header.property_names() or { panic(err) }
for data in props {
key := data.atom.str()
hd.set_custom(key, header.get(key).str()) or {
error = this.ctx.js_error(message: err.msg())
unsafe {
goto reject
}
break
}
}
mut body := raw_body.str()
mut resp := Response{}
if boundary.is_undefined() {
resp = http.fetch(
url: url
method: Method.from(method) or { Method.get }
header: hd
data: body
) or {
error = this.ctx.js_error(message: err.msg())
unsafe {
goto reject
}
Response{}
}
} else {
form, files := parse_multipart_form(body, '----formdata-' + boundary.str())
resp = http.post_multipart_form(url, form: form, header: hd, files: files) or {
error = this.ctx.js_error(message: err.msg())
unsafe {
goto reject
}
Response{}
}
}
mut resp_header := resp.header
obj_header := this.ctx.js_object()
for key in resp_header.keys() {
val := resp_header.custom_values(key)
obj_header.set(key, val.join('; '))
}
obj := this.ctx.js_object()
obj.set('body', resp.body)
obj.set('status', resp.status_code)
obj.set('status_message', resp.status_msg)
obj.set('header', obj_header)
resp_header.free()
return promise.resolve(obj)
reject:
return promise.reject(error)
}

fn fetch_boot(ctx &Context, boot Value) {
boot.set('core_fetch', ctx.js_function_this(fetch))
}

// Add Fetch API to globals.
// Example:
// ```v
// import herudi.vjs
// import herudi.vjs.web
//
// fn main() {
// rt := vjs.new_runtime()
// ctx := rt.new_context()
//
// web.fetch_api(ctx)
// }
// ```
pub fn fetch_api(ctx &Context) {
glob, boot := get_bootstrap(ctx)
fetch_boot(ctx, boot)
ctx.eval_file('${@VMODROOT}/web/js/fetch.js', vjs.type_module) or { panic(err) }
glob.free()
}
9 changes: 6 additions & 3 deletions web/js/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function isCyclic(obj) {
}
seenObjects.push(obj);
for (const key in obj) {
if (obj.hasOwnProperty(key) && detect(obj[key])) {
if (Object.hasOwn(obj, key) && detect(obj[key])) {
return true;
}
}
Expand Down Expand Up @@ -138,7 +138,8 @@ function countObject(obj) {
return count;
}
function countArray(arr) {
let count = 0, len = arr.length;
let count = 0;
const len = arr.length;
for (let i = 0; i < len; i++) {
const v = arr[i];
if (v != null) {
Expand Down Expand Up @@ -186,7 +187,9 @@ function formatArray(arr, ctx) {
}
function formatClass(cls, ctx) {
if (cls[vjs_inspect] !== void 0) {
return cls[vjs_inspect](formatValue);
return cls[vjs_inspect]((data) => {
return formatValue(data, void 0, ctx);
});
}
return `${c_name(cls)} ${formatObject(cls, ctx)}`;
}
Expand Down
3 changes: 1 addition & 2 deletions web/js/encoding.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/* Credit: All VJS Author */

const { text_encode, text_decode, text_encode_into, util } =
globalThis.__bootstrap;
const { text_encode, text_decode, text_encode_into } = globalThis.__bootstrap;

class TextEncoder {
get encoding() {
Expand Down
33 changes: 33 additions & 0 deletions web/js/fetch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Request } from "./fetch/request.js";
import { Response } from "./fetch/response.js";
import { Headers } from "./fetch/headers.js";

const { core_fetch } = globalThis.__bootstrap;

async function fetch(input, opts = {}) {
const req = new Request(input, opts);
const url = req.url;
const boundary = opts.body instanceof FormData
? Math.random().toString()
: void 0;
const body = typeof opts.body !== "string"
? await req.text(boundary)
: opts.body;
const res = await core_fetch(url, {
method: opts.method ?? "GET",
headers: req.headers.toJSON(),
body: body,
boundary: boundary,
});
const resInit = {};
resInit.status = res.status;
resInit.statusText = res.status_message;
resInit.headers = res.header;
resInit.url = url;
return new Response(res.body, resInit);
}

globalThis.Headers = Headers;
globalThis.Request = Request;
globalThis.Response = Response;
globalThis.fetch = fetch;
122 changes: 122 additions & 0 deletions web/js/fetch/body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
const { isTypedArray, isArrayBuffer } = globalThis.__bootstrap.util;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
function streamToStr(stream) {
const reader = stream.getReader();
let result = "";
async function read() {
const { done, value } = await reader.read();
if (done) {
return result;
}
result += decoder.decode(value, { stream: true });
return read();
}
return read();
}
const toStream = (data) =>
new ReadableStream({
start(ctrl) {
ctrl.enqueue(data);
ctrl.close();
},
});
export class Body {
#body = {};
#bodyUsed = false;
constructor(body) {
if (body != null) {
if (typeof body === "string") {
this.#body.text = body;
} else if (body instanceof URLSearchParams) {
this.#body.text = body.toString();
} else if (body instanceof FormData) {
this.#body.form = body;
this.#body.blob = body["_blob"];
} else if (body instanceof ReadableStream) {
this.#body.stream = body;
} else if (body instanceof Blob) {
this.#body.blob = body;
} else if (isArrayBuffer(body) || isTypedArray(body)) {
body = isArrayBuffer(body) ? body : body.buffer;
this.#body.arrayBuffer = body;
this.#body.text = decoder.decode(body);
}
}
}
#checkUpdateBody() {
if (this.#bodyUsed) {
throw new TypeError("Body already consumed.");
}
this.#bodyUsed = true;
}
#realBlob(code) {
return typeof this.#body.blob === "function"
? this.#body.blob(code)
: this.#body.blob;
}
get bodyUsed() {
return this.#bodyUsed;
}
get body() {
if (this.#body.stream !== void 0) {
return this.#body.stream;
}
if (this.#body.text !== void 0) {
return toStream(encoder.encode(this.#body.text));
}
if (this.#body.blob !== void 0) {
return this.#realBlob().stream();
}
if (this.#body.arrayBuffer !== void 0) {
return toStream(new Uint8Array(this.#body.arrayBuffer));
}
return null;
}

text(code) {
this.#checkUpdateBody();
if (this.#body.text !== void 0) {
return Promise.resolve(this.#body.text);
}
if (this.#body.arrayBuffer !== void 0) {
return Promise.resolve(decoder.decode(this.#body.arrayBuffer));
}
if (this.#body.blob !== void 0) {
const blob = this.#realBlob(code);
return blob.text();
}
if (this.#body.stream !== void 0) {
return streamToStr(this.#body.stream);
}
return Promise.resolve("");
}

json() {
return this.text().then(JSON.parse);
}

formData() {
this.#checkUpdateBody();
if (this.#body.form !== void 0) {
return Promise.resolve(this.#body.form);
}
throw new TypeError("Can't read form-data from body");
}

blob() {
this.#checkUpdateBody();
if (this.#body.blob !== void 0) {
return Promise.resolve(this.#realBlob());
}
throw new TypeError("Can't read blob from body");
}

arrayBuffer() {
if (this.#body.arrayBuffer !== void 0) {
this.#checkUpdateBody();
return Promise.resolve(this.#body.arrayBuffer);
}
return this.text().then(encoder.encode);
}
}
Loading

0 comments on commit f9de3ab

Please sign in to comment.