diff options
Diffstat (limited to 'compat/lustre_websocket/src')
-rw-r--r-- | compat/lustre_websocket/src/ffi.mjs | 39 | ||||
-rw-r--r-- | compat/lustre_websocket/src/lustre_websocket.gleam | 85 |
2 files changed, 124 insertions, 0 deletions
diff --git a/compat/lustre_websocket/src/ffi.mjs b/compat/lustre_websocket/src/ffi.mjs new file mode 100644 index 0000000..56f11fd --- /dev/null +++ b/compat/lustre_websocket/src/ffi.mjs @@ -0,0 +1,39 @@ +let ws_handler_registry = {} + +export const init_websocket = path => { + let ws + if (typeof WebSocket === "function") { + // we're in the browser + let url = new URL(document.URL) + let protocol = url.protocol === "http:" ? "ws" : "wss" + let ws_url = protocol + "://" + url.host + url.pathname + path + ws = new WebSocket(ws_url) + } else { + // we're NOT in the browser, prolly running tests + ws = {} + } + ws_handler_registry[ws.url] = { ws: ws } + + ws.onopen = evt => { + ws_handler_registry[ws.url]?.on_open?.() + } + ws.onclose = evt => { + ws_handler_registry[ws.url]?.on_close?.(evt.code) + delete ws_handler_registry[ws.url] + } + ws.onmessage = event => ws_handler_registry[ws.url]?.on_message?.(event.data) + ws.onerror = error => console.log("ws", ws.url, "error", error, "no handler, since I have no clue what errors we might be talking about") + return ws +} + +export const register_websocket_handler = (ws, on_open, on_message, on_close) => { + const reg_entry = ws_handler_registry[ws.url] + reg_entry.on_open = on_open + reg_entry.on_message = on_message + reg_entry.on_close = on_close + console.log("ws reg", ws_handler_registry) +} + +export const send_over_websocket = (ws, msg) => { + ws.send(msg) +} diff --git a/compat/lustre_websocket/src/lustre_websocket.gleam b/compat/lustre_websocket/src/lustre_websocket.gleam new file mode 100644 index 0000000..ce46a4a --- /dev/null +++ b/compat/lustre_websocket/src/lustre_websocket.gleam @@ -0,0 +1,85 @@ +import lustre/effect.{Effect} + +pub type WebSocket + +pub type WebSocketCloseReason { + // 1000 + Normal + // 1001 + GoingAway + // 1002 + ProtocolError + // 1003 + UnexpectedTypeOfData + // 1004 Reserved + // 1005 + NoCodeFromServer + // 1006, no close frame + AbnormalClose + // 1007 + IncomprehensibleFrame + // 1008 + PolicyViolated + // 1009 + MessageTooBig + // 1010 + FailedExtensionNegotation + // 1011 + UnexpectedFailure + // 1015 + FailedTLSHandshake +} + +pub type WebSocketEvent { + OnOpen(WebSocket) + OnMessage(String) + OnClose(WebSocketCloseReason) +} + +/// Initialize a websocket. These constructs are fully asynchronous, so you must provide a wrapper +/// that takes a `WebSocketEvent` and turns it into a lustre message of your application. +pub fn init(path: String, wrapper: fn(WebSocketEvent) -> a) -> Effect(a) { + let ws = do_init(path) + use dispatch <- effect.from + let on_open = fn() { dispatch(wrapper(OnOpen(ws))) } + let on_message = fn(in_msg) { dispatch(wrapper(OnMessage(in_msg))) } + let on_close = fn(code) { + case code { + 1000 -> dispatch(wrapper(OnClose(Normal))) + 1001 -> dispatch(wrapper(OnClose(GoingAway))) + 1002 -> dispatch(wrapper(OnClose(ProtocolError))) + 1003 -> dispatch(wrapper(OnClose(UnexpectedTypeOfData))) + 1005 -> dispatch(wrapper(OnClose(NoCodeFromServer))) + 1006 -> dispatch(wrapper(OnClose(AbnormalClose))) + 1007 -> dispatch(wrapper(OnClose(IncomprehensibleFrame))) + 1008 -> dispatch(wrapper(OnClose(PolicyViolated))) + 1009 -> dispatch(wrapper(OnClose(MessageTooBig))) + 1010 -> dispatch(wrapper(OnClose(FailedExtensionNegotation))) + 1011 -> dispatch(wrapper(OnClose(UnexpectedFailure))) + 1015 -> dispatch(wrapper(OnClose(FailedTLSHandshake))) + } + } + + do_register(ws, on_open, on_message, on_close) +} + +external fn do_init(path) -> WebSocket = + "./ffi.mjs" "init_websocket" + +external fn do_register( + ws: WebSocket, + on_open: fn() -> Nil, + on_message: fn(String) -> Nil, + on_close: fn(Int) -> Nil, +) -> Nil = + "./ffi.mjs" "register_websocket_handler" + +/// Send a text message over the web socket. This is asynchronous. There is no +/// expectation of a reply. See `init`. Only works on an Non-Closed socket. +/// Returns a `Effect(a)` that you must pass as second entry in the lustre `update` return. +pub fn send(ws: WebSocket, msg: String) -> Effect(a) { + effect.from(fn(_) { do_send(ws, msg) }) +} + +external fn do_send(ws: WebSocket, msg: String) -> Nil = + "./ffi.mjs" "send_over_websocket" |