nandi/frqpublic Fork 0
627b785fbf6cef0c29d54b82a1ffb5e870ca9f51
Commits
Clone
git clone https://git.rickub.com/nandi/frq.git
git clone ssh://git@rickub.com/nandi/frq.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

frq_host.js · 99 lines · 3.6 KBJavaScript Blame HistoryRaw
A web version, from the same core 23846db nandi 10h ago1// 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})();