///| JS transport registry bridge for injected HTTP transports.
///|
fn js_transport_bytes_to_array(bytes : Bytes) -> Array[Byte] {
let out : Array[Byte] = Array::new(capacity=bytes.length())
for i in 0.. (Array[String], Array[String]) {
let names : Array[String] = []
let values : Array[String] = []
for entry in headers.to_array() {
let (name, value) = entry
names.push(name)
values.push(value)
}
(names, values)
}
///|
fn js_transport_b64_decode_char(c : Char) -> Int {
if c >= 'A' && c <= 'Z' {
c.to_int() - 'A'.to_int()
} else if c >= 'a' && c <= 'z' {
c.to_int() - 'a'.to_int() + 26
} else if c >= '0' && c <= '9' {
c.to_int() - '0'.to_int() + 52
} else if c == '+' {
62
} else if c == '/' {
63
} else {
-1
}
}
///|
fn js_transport_base64_decode(b64 : String) -> Bytes {
let out : Array[Byte] = []
let chars : Array[Int] = []
for c in b64 {
if c == '=' {
break
}
let v = js_transport_b64_decode_char(c)
if v >= 0 {
chars.push(v)
}
}
let mut i = 0
while i + 1 < chars.length() {
if i + 3 < chars.length() {
let n = (chars[i] << 18) |
(chars[i + 1] << 12) |
(chars[i + 2] << 6) |
chars[i + 3]
out.push(((n >> 16) & 0xff).to_byte())
out.push(((n >> 8) & 0xff).to_byte())
out.push((n & 0xff).to_byte())
i += 4
} else if i + 2 < chars.length() {
let n = (chars[i] << 18) | (chars[i + 1] << 12) | (chars[i + 2] << 6)
out.push(((n >> 16) & 0xff).to_byte())
out.push(((n >> 8) & 0xff).to_byte())
i += 3
} else {
let n = (chars[i] << 18) | (chars[i + 1] << 12)
out.push(((n >> 16) & 0xff).to_byte())
i += 2
}
}
Bytes::from_array(FixedArray::makei(out.length(), fn(idx) { out[idx] }))
}
///|
fn js_transport_parse_response(
raw : String,
) -> (@bit.HttpResponse, Bytes) raise @bit.GitError {
guard raw.find("\n") is Some(split) else {
raise @bit.GitError::ProtocolError("Malformed JS transport response")
}
let status_raw = String::unsafe_substring(raw, start=0, end=split)
let body_b64 = String::unsafe_substring(
raw,
start=split + 1,
end=raw.length(),
)
let status = @string.parse_int(status_raw) catch {
_ => raise @bit.GitError::ProtocolError("Invalid JS transport status")
}
(@bit.HttpResponse::new(status), js_transport_base64_decode(body_b64))
}
///|
extern "js" fn lib_js_transport_get(
transport_id : Int,
url : String,
header_names : Array[String],
header_values : Array[String],
) -> @js_async.Promise[String] =
#| async (transportId, url, headerNames, headerValues) => {
#| const state = globalThis.__bitGitJsTransportState ??= { nextTransportId: 1, transports: new Map() };
#| const transport = state.transports.get(transportId);
#| if (!transport || typeof transport.get !== 'function') {
#| throw new Error(`bit-git js transport ${transportId} not found`);
#| }
#| const headers = {};
#| for (let i = 0; i < Math.min(headerNames?.length ?? 0, headerValues?.length ?? 0); i++) {
#| headers[String(headerNames[i])] = String(headerValues[i]);
#| }
#| const response = await transport.get(url, headers);
#| const status = Math.trunc(response?.status ?? response?.code ?? 200);
#| const rawBody = response?.body ?? new Uint8Array();
#| const bytes = (() => {
#| if (rawBody == null) return new Uint8Array();
#| if (rawBody instanceof Uint8Array) return rawBody;
#| if (typeof ArrayBuffer !== 'undefined' && rawBody instanceof ArrayBuffer) {
#| return new Uint8Array(rawBody);
#| }
#| if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(rawBody)) {
#| return new Uint8Array(rawBody.buffer, rawBody.byteOffset, rawBody.byteLength);
#| }
#| if (typeof rawBody === 'string') {
#| return new TextEncoder().encode(rawBody);
#| }
#| return Uint8Array.from(rawBody);
#| })();
#| const bodyB64 = typeof Buffer !== 'undefined'
#| ? Buffer.from(bytes).toString('base64')
#| : btoa(Array.from(bytes, b => String.fromCharCode(b)).join(''));
#| return `${status}\n${bodyB64}`;
#| }
///|
extern "js" fn lib_js_transport_post(
transport_id : Int,
url : String,
body : Array[Byte],
header_names : Array[String],
header_values : Array[String],
) -> @js_async.Promise[String] =
#| async (transportId, url, body, headerNames, headerValues) => {
#| const state = globalThis.__bitGitJsTransportState ??= { nextTransportId: 1, transports: new Map() };
#| const transport = state.transports.get(transportId);
#| if (!transport || typeof transport.post !== 'function') {
#| throw new Error(`bit-git js transport ${transportId} not found`);
#| }
#| const headers = {};
#| for (let i = 0; i < Math.min(headerNames?.length ?? 0, headerValues?.length ?? 0); i++) {
#| headers[String(headerNames[i])] = String(headerValues[i]);
#| }
#| const response = await transport.post(url, Uint8Array.from(body ?? []), headers);
#| const status = Math.trunc(response?.status ?? response?.code ?? 200);
#| const rawBody = response?.body ?? new Uint8Array();
#| const bytes = (() => {
#| if (rawBody == null) return new Uint8Array();
#| if (rawBody instanceof Uint8Array) return rawBody;
#| if (typeof ArrayBuffer !== 'undefined' && rawBody instanceof ArrayBuffer) {
#| return new Uint8Array(rawBody);
#| }
#| if (typeof ArrayBuffer !== 'undefined' && ArrayBuffer.isView(rawBody)) {
#| return new Uint8Array(rawBody.buffer, rawBody.byteOffset, rawBody.byteLength);
#| }
#| if (typeof rawBody === 'string') {
#| return new TextEncoder().encode(rawBody);
#| }
#| return Uint8Array.from(rawBody);
#| })();
#| const bodyB64 = typeof Buffer !== 'undefined'
#| ? Buffer.from(bytes).toString('base64')
#| : btoa(Array.from(bytes, b => String.fromCharCode(b)).join(''));
#| return `${status}\n${bodyB64}`;
#| }
///|
async fn js_transport_get(
transport_id : Int,
url : String,
headers : Map[String, String],
) -> (@bit.HttpResponse, Bytes) raise @bit.GitError {
let (header_names, header_values) = js_transport_headers_to_arrays(headers)
let raw = lib_js_transport_get(transport_id, url, header_names, header_values).wait() catch {
error => raise @bit.GitError::IoError("JS transport GET failed: \{error}")
}
js_transport_parse_response(raw)
}
///|
async fn js_transport_post(
transport_id : Int,
url : String,
body : Bytes,
headers : Map[String, String],
) -> (@bit.HttpResponse, Bytes) raise @bit.GitError {
let (header_names, header_values) = js_transport_headers_to_arrays(headers)
let raw = lib_js_transport_post(
transport_id,
url,
js_transport_bytes_to_array(body),
header_names,
header_values,
).wait() catch {
error => raise @bit.GitError::IoError("JS transport POST failed: \{error}")
}
js_transport_parse_response(raw)
}