| A web version, from the same core 23846db nandi 7h ago | 1 | // The half of the web build that owns the I/O. |
| 2 | // |
| 3 | // `frq_core.js` is the same Nim the desktop runs, compiled by `nim js`. It |
| 4 | // holds the state and builds the screens, and it opens nothing: a browser |
| 5 | // cannot dial a TCP socket, and `frq/conn` on this target is a pair of queues |
| 6 | // rather than two threads. This file is what fills them. |
| 7 | // |
| 8 | // Three jobs, and nothing else: |
| 9 | // |
| 10 | // * the socket. The core says where it wants to be connected; this opens a |
| 11 | // WebSocket to freeq's own bridge, feeds every line in, and sends |
| 12 | // everything the core has queued. |
| 13 | // * the sign-in. The broker answers by redirecting the page back with a |
| 14 | // payload in the fragment, so a sign-in finishes on the *next* load — |
| 15 | // this reads it, hands it over, and takes it off the URL. |
| 16 | // * the profiles, which the core asks for through `fetch` (in the Nim, not |
| 17 | // here), so there is nothing to do for them. |
| 18 | // |
| 19 | // Flutter draws. It reaches the core through `dart:js_interop`, and never |
| 20 | // touches any of this. |
| 21 | |
| 22 | (function () { |
| 23 | "use strict"; |
| 24 | |
| 25 | var ws = null; |
| 26 | var wantedNow = ""; |
| 27 | |
| 28 | // freeq publishes this for exactly this case — the same server, the same |
| 29 | // SASL, over a transport a page is allowed to open. The host and the |
| 30 | // scheme come from what the core asked for; the path is the bridge's. |
| 31 | function urlFor(cfg) { |
| 32 | return "wss://" + cfg.host + "/irc"; |
| 33 | } |
| 34 | |
| 35 | function connect(cfg) { |
| 36 | var url = urlFor(cfg); |
| 37 | try { |
| 38 | ws = new WebSocket(url); |
| 39 | } catch (e) { |
| 40 | frq.socketEvent("error: " + e); |
| 41 | return; |
| 42 | } |
| 43 | ws.onopen = function () { frq.socketEvent("open"); }; |
| 44 | ws.onmessage = function (ev) { |
| 45 | // A frame can carry more than one line, and carries the CRLF the wire |
| 46 | // format puts between them. The core wants lines. |
| 47 | String(ev.data).split(/\r?\n/).forEach(function (line) { |
| 48 | if (line.length > 0) frq.feed(line); |
| 49 | }); |
| 50 | }; |
| 51 | ws.onclose = function (ev) { |
| 52 | ws = null; |
| 53 | wantedNow = ""; |
| 54 | frq.socketEvent("close: " + (ev.reason || "the connection ended")); |
| 55 | }; |
| 56 | ws.onerror = function () { |
| 57 | // `onerror` carries nothing worth reporting — the browser withholds the |
| 58 | // reason on purpose — and `onclose` always follows, so the message the |
| 59 | // reader sees comes from there. |
| 60 | frq.socketEvent("error: the connection failed"); |
| 61 | }; |
| 62 | } |
| 63 | |
| 64 | // The pump. Both directions, on a timer, because nothing here is allowed to |
| 65 | // call into Dart and Dart is not going to ask on the core's behalf. |
| 66 | // |
| 67 | // Twenty times a second: fast enough that a keystroke's PRIVMSG does not |
| 68 | // sit in a queue where a reader would notice, and slow enough to be free. |
| 69 | setInterval(function () { |
| 70 | var want = frq.wanted(); |
| 71 | if (want && want !== wantedNow) { |
| 72 | wantedNow = want; |
| 73 | if (ws) { try { ws.close(); } catch (e) {} ws = null; } |
| 74 | connect(JSON.parse(want)); |
| 75 | } |
| 76 | if (ws && ws.readyState === 1) { |
| 77 | var out = frq.takeOutbound(); |
| 78 | if (out) { |
| 79 | out.split("\n").forEach(function (line) { |
| 80 | if (line.length > 0) ws.send(line); |
| 81 | }); |
| 82 | } |
| 83 | } |
| 84 | }, 50); |
| 85 | |
| 86 | // The broker's answer, which arrives as a fragment on a fresh load. |
| 87 | // |
| 88 | // Taken off the URL once read: a payload carries a single-use token, and |
| 89 | // leaving it in the address bar means it is in the history, in whatever |
| 90 | // the reader pastes, and replayed by a refresh. |
| 91 | var payload = ""; |
| 92 | if (window.location.hash) { |
| 93 | var h = window.location.hash.replace(/^#/, ""); |
| 94 | payload = new URLSearchParams(h).get("oauth") || h.replace(/^oauth=/, ""); |
| 95 | history.replaceState(null, "", window.location.pathname + window.location.search); |
| 96 | } |
| 97 | |
| 98 | frq.init(payload); |
| 99 | })(); |