Current section

Files

Jump to
midas_browser src midas browser.gleam
Raw

src/midas/browser.gleam

import gleam/fetch
import gleam/int
import gleam/io
import gleam/javascript/promise
import gleam/string
import gleam/uri
import midas/browser/zip
import midas/effect as e
import plinth/browser/crypto
import plinth/browser/crypto/subtle
import plinth/browser/location
import plinth/browser/window
import plinth/browser/window_proxy
import plinth/javascript/global
import snag
/// Run a task in the browser.
pub fn run_task(task) {
run(task, fn(name) { snag.error("Unsupported effect: " <> name) })
}
/// Run effectful code in the browser.
pub fn run(task, unsupported) {
case task {
e.Done(value) -> promise.resolve(value)
e.Fetch(request, resume) -> {
use return <- promise.await(do_fetch(request))
run(resume(return), unsupported)
}
e.Follow(url, resume) -> {
use return <- promise.await(do_follow(url))
run(resume(uri.parse(return)), unsupported)
}
e.Log(message, resume) -> {
io.println(message)
run(resume(Ok(Nil)), unsupported)
}
e.Zip(files, resume) -> {
use zipped <- promise.await(zip.zip(files))
run(resume(Ok(zipped)), unsupported)
}
e.Bundle(..) -> promise.resolve(unsupported("Bundle"))
e.ExportJsonWebKey(..) -> promise.resolve(unsupported("ExportJsonWebKey"))
e.GenerateKeyPair(..) -> promise.resolve(unsupported("GenerateKeyPair"))
e.Hash(algorithm:, bytes:, resume:) -> {
use result <- promise.await(do_hash(algorithm, bytes))
run(resume(result), unsupported)
}
e.List(..) -> promise.resolve(unsupported("List"))
e.Read(..) -> promise.resolve(unsupported("Read"))
e.Serve(..) -> promise.resolve(unsupported("Serve"))
e.Sign(..) -> promise.resolve(unsupported("Sign"))
e.StrongRandom(length:, resume:) -> {
let return = do_random(length)
run(resume(return), unsupported)
}
e.UnixNow(..) -> promise.resolve(unsupported("UnixNow"))
e.Visit(..) -> promise.resolve(unsupported("Visit"))
e.Write(..) -> promise.resolve(unsupported("Write"))
}
}
/// Fetch a request
pub fn do_fetch(request) {
use response <- promise.await(fetch.send_bits(request))
case response {
Ok(response) -> {
use response <- promise.await(fetch.read_bytes_body(response))
let response = case response {
Ok(response) -> Ok(response)
Error(fetch.NetworkError(s)) -> Error(e.NetworkError(s))
Error(fetch.UnableToReadBody) -> Error(e.UnableToReadBody)
Error(fetch.InvalidJsonBody) -> Error(e.UnableToReadBody)
}
promise.resolve(response)
}
Error(fetch.NetworkError(s)) -> promise.resolve(Error(e.NetworkError(s)))
Error(fetch.UnableToReadBody) -> promise.resolve(Error(e.UnableToReadBody))
Error(fetch.InvalidJsonBody) -> promise.resolve(Error(e.UnableToReadBody))
}
}
pub fn do_follow(url) {
let url = uri.to_string(url)
let frame = #(600, 700)
let assert Ok(popup) = open(url, frame)
receive_redirect(popup, 100)
}
pub fn open(url, frame_size) {
let space = #(
window.outer_width(window.self()),
window.outer_height(window.self()),
)
let #(#(offset_x, offset_y), #(inner_x, inner_y)) = center(frame_size, space)
let features =
string.concat([
"popup",
",width=",
int.to_string(inner_x),
",height=",
int.to_string(inner_y),
",left=",
int.to_string(offset_x),
",top=",
int.to_string(offset_y),
])
window.open(url, "_blank", features)
}
pub fn center(inner, outer) {
let #(inner_x, inner_y) = inner
let #(outer_x, outer_y) = outer
let inner_x = int.min(inner_x, outer_x)
let inner_y = int.min(inner_y, outer_y)
let offset_x = { outer_x - inner_x } / 2
let offset_y = { outer_y - inner_y } / 2
#(#(offset_x, offset_y), #(inner_x, inner_y))
}
pub fn receive_redirect(popup, wait) {
use Nil <- promise.await(do_wait(wait))
case location.href(window_proxy.location(popup)) {
Ok("http" <> _ as location) -> {
window_proxy.close(popup)
promise.resolve(location)
}
_ -> receive_redirect(popup, wait)
}
}
pub fn do_hash(algorithm, bytes) {
let algorithm = case algorithm {
e.Sha1 -> subtle.SHA1
e.Sha256 -> subtle.SHA256
e.Sha384 -> subtle.SHA384
e.Sha512 -> subtle.SHA512
}
subtle.digest(algorithm, bytes)
}
pub fn do_random(length) {
case window.crypto(window.self()) {
Ok(crypto) -> crypto.get_random_values(crypto, length)
Error(Nil) -> Error("window.crypo is not present")
}
}
pub fn do_wait(delay) {
promise.new(fn(resolve) {
global.set_timeout(delay, fn() { resolve(Nil) })
Nil
})
}