realtimeclipboard 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * WebSocket channel — the default way to reach the relay.
3
+ *
4
+ * A *channel* moves frames and nothing else. It knows how to open a pipe, push
5
+ * a frame down it, hand incoming frames up, and say when it is finished. It
6
+ * knows nothing about hello, heartbeats, backoff, reconnection or which
7
+ * transport should be used next — all of that lives in relay.js, once, for both
8
+ * channels. See transport/sse.js for the other implementation of this contract.
9
+ *
10
+ * create({ url, roomHash, onOpen, onFrame, onDown }) -> { send, close, isOpen }
11
+ *
12
+ * onOpen() the channel is usable; frames may be sent
13
+ * onFrame(msg) one parsed inbound frame
14
+ * onDown({code, reason})
15
+ * the channel is finished and will not recover. Fired at
16
+ * most once, and never after close() — a deliberate close
17
+ * is not an event anyone needs to react to.
18
+ */
19
+
20
+ import * as proto from "./protocol.js";
21
+
22
+ export const LABEL = "WebSocket";
23
+
24
+ export const available = () => typeof WebSocket !== "undefined";
25
+
26
+ export function create({ url, roomHash, auth = null, onOpen, onFrame, onDown }) {
27
+ let done = false;
28
+
29
+ const finish = (code, reason) => {
30
+ if (done) return;
31
+ done = true;
32
+ onDown({ code, reason });
33
+ };
34
+
35
+ let sock;
36
+ try {
37
+ // `?a=` is a locked session's admission token. Not a secret in the sense
38
+ // the PIN is — it is HKDF output the relay compares against the room's
39
+ // first arrival — but it is still session material, so it rides in the
40
+ // query rather than the path for the same reason `sid` does on the SSE
41
+ // path: paths are what proxies and access logs like to keep.
42
+ sock = new WebSocket(
43
+ `${url.replace(/\/+$/, "")}/ws/${roomHash}${auth ? `?a=${encodeURIComponent(auth)}` : ""}`);
44
+ } catch (err) {
45
+ // Constructing the socket throws synchronously on a malformed URL, and on
46
+ // a Content-Security-Policy that forbids the scheme — which is one of the
47
+ // ways a managed browser blocks WebSockets outright. Report it as a normal
48
+ // channel failure so the caller can fall back rather than crash on boot.
49
+ Promise.resolve().then(() => finish(0, err.message || "could not open"));
50
+ return { label: LABEL, send: () => false, close: () => { done = true; }, isOpen: () => false };
51
+ }
52
+
53
+ sock.onopen = () => onOpen();
54
+ sock.onmessage = e => onFrame(proto.parse(e.data));
55
+
56
+ // onerror carries no useful detail in any browser, and a close event always
57
+ // follows it — so the close is the single place failure is reported from.
58
+ sock.onerror = () => {};
59
+ sock.onclose = ev => finish(ev.code, ev.reason);
60
+
61
+ return {
62
+ label: LABEL,
63
+ isOpen: () => sock.readyState === WebSocket.OPEN,
64
+
65
+ send(obj) {
66
+ if (sock.readyState !== WebSocket.OPEN) return false;
67
+ sock.send(JSON.stringify(obj));
68
+ return true;
69
+ },
70
+
71
+ close(code = 1000) {
72
+ done = true;
73
+ try { sock.close(code); } catch { /* already gone */ }
74
+ },
75
+ };
76
+ }