import { DatagramInitObject } from "../../Independents/Datagram/datagram-types.js"; import { Jsonable } from "../types.js"; type TypedArray = Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Uint8ClampedArray | Float32Array | Float64Array; export let encoder = new TextEncoder(); export let decoder = new TextDecoder(); //export type EncodableMessage = { body?: BodyTypes, [key: string]: any } /** * Creates a binary message blob for an object that may contain a body property in binary format. * The object must otherwise be jsonable. * @param json The otherwise jsonable object * @returns A byte array */ export function encodeMessage(dg: DatagramInitObject): ArrayBuffer { let msg = dg as any; let { body, ...withoutBody } = msg; let bbody: Uint8Array | undefined; let x = encodeBody(msg.body); (withoutBody as any).type = x.type; bbody = x.body; let bjson = encoder.encode(JSON.stringify(withoutBody)); let head = new Uint8Array(1 + 4 + 4); let d = new DataView(head.buffer); d.setUint8(0, 123); // version d.setUint32(1, bjson.length, false); d.setUint32(5, bbody.length, false); //console.log("ENCODING MESSAGE", bjson, "bodylength", bbody.length); return concatenate(Uint8Array, head, bjson, bbody).buffer } /** * Converts the byte array generated by encodeMessage() back to its * original form. * @param message The raw data * @returns */ export function decodeMessage(message: ArrayBuffer): DatagramInitObject { //let arr = new Uint8Array(message); let d = new DataView(message); if (message.byteLength < 1 + 4 + 4) { throw new Error("Buffer does not contain header") } let version = d.getUint8(0); if (version != 123) { throw new Error(`Illegal Burpa Transport version ${version}`) } let jsonLength = d.getUint32(1); let bodyLength = d.getUint32(5); if (1 + 4 + 4 + jsonLength + bodyLength > message.byteLength) { throw new Error(`Datagram is ${message.byteLength} and cannot contain header length ${1 + 4 + 4} and property length ${jsonLength} and body length ${bodyLength}`); } //console.log("version",version,"json",jsonLength,"body",bodyLength); let bjson = message.slice(1 + 4 + 4, 1 + 4 + 4 + jsonLength); let json = decoder.decode(bjson); //console.log("MSG PROPS",json); let obj = JSON.parse(json) as DatagramInitObject & { type: string }; if (bodyLength > 0) { let body = message.slice(1 + 4 + 4 + jsonLength, 1 + 4 + 4 + jsonLength + bodyLength); obj.body = decodeBody(body, obj.type); } //console.log("DECODING MESSAGE", obj, "bodylength", bodyLength); return obj; } export function encodeBody(body: object | string | undefined): { body: Uint8Array, type?: string } { switch (typeof (body)) { case "object": let str = JSON.stringify(body); return { body: encoder.encode(str), type: "application/json" }; case "string": return { body: encoder.encode(body), type: "text/plain" }; case "undefined": return { body: new Uint8Array(0) } default: throw `Cannot encode body of type ${typeof (body)}` } } export function decodeBody(body: ArrayBuffer, type: string | null): any { if (!(body instanceof ArrayBuffer)) { console.log("BODY TO DECODE", body); throw "body must be binary" } // console.log("WILL DECODE BODY", type, decoder.decode(body)); switch (type) { case "application/json": let str = decoder.decode(body); let j: Jsonable | undefined = undefined; try { j = JSON.parse(str); } catch (e) { console.warn(`Not valid json "${str}"`, e); } return j; case "text/plain": return decoder.decode(body); case "void": return undefined; case "text/javascript": return decoder.decode(body); // return evalESM(decoder.decode(body)) case null: return decoder.decode(body) default: console.error(`Mime type "${type}" not suported`) return decoder.decode(body) // throw `Unknown mime type ${type}` } } export function concatenate(resultConstructor: new (length: number) => TypedArray, ...arrays: T[]) { let totalLength = 0; for (const arr of arrays) { totalLength += arr.length; } const result: T = new resultConstructor(totalLength) as T; let offset = 0; for (const arr of arrays) { result.set(arr, offset); offset += arr.length; } return result; } export async function evalESM(js: string) { await fetch("/resources/herpderp", { method: "PUT", headers: { "port": "burpasys", "content-type": "text/javascript" }, body: js }); const t = (() => "/resources/herpderp")(); return await import(t); } export async function evalFunction(js: string, type: string) { let fn: (...args: any[]) => any; switch (type) { case "module": let ns = await evalESM(js); fn = ns.default; if (typeof fn !== "function") { console.warn(`Module exports a ${typeof fn} and not a function"`, fn); fn = await eval(js); if (typeof fn == "function") { console.warn(`The javascript in evalFunction is not of type module as stated. Evaluating as plain javascript instead.`); return fn; } } break; case "application/javascript": case "": fn = await eval(js); break; default: console.warn(`Scripts of type="${type}" are not supported.`); fn = await eval(js); } if (typeof fn !== "function") { let err = `The string ${js} does not evaluate to a function. Instead it evaluates to`; console.warn(err, fn); throw `${err} ${fn}`; } return fn; }