so-called-expert 0.1.0 → 0.1.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "so-called-expert",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A multiplayer terminal trivia game where everyone claims to be an expert. Hosted by Claude.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/client.js CHANGED
@@ -1,11 +1,31 @@
1
1
  // The player's side: one WebSocket to the host plus a full-screen terminal UI.
2
2
  import crypto from 'node:crypto';
3
+ import dns from 'node:dns';
3
4
  import readline from 'node:readline';
4
5
  import WebSocket from 'ws';
5
6
  import { renderFrame } from './ui.js';
6
7
 
7
8
  const INPUT_LIMITS = { pick: 80, question: 120 };
8
9
 
10
+ /**
11
+ * Hostname lookup that falls back to public resolvers. A tunnel hostname that
12
+ * was looked up before it existed can be stuck as "not found" in the local
13
+ * DNS cache; asking a public resolver directly gets around that.
14
+ */
15
+ function lookup(hostname, options, callback) {
16
+ if (typeof options === 'function') [options, callback] = [{}, options];
17
+ dns.lookup(hostname, options, (err, address, family) => {
18
+ if (!err) return callback(null, address, family);
19
+ const resolver = new dns.Resolver({ timeout: 3000, tries: 1 });
20
+ resolver.setServers(['1.1.1.1', '8.8.8.8']);
21
+ resolver.resolve4(hostname, (err2, addresses) => {
22
+ if (err2 || !addresses?.length) return callback(err);
23
+ if (options?.all) callback(null, addresses.map((a) => ({ address: a, family: 4 })));
24
+ else callback(null, addresses[0], 4);
25
+ });
26
+ });
27
+ }
28
+
9
29
  /**
10
30
  * Runs until the player quits. Resolves with an exit message (or null).
11
31
  */
@@ -69,7 +89,7 @@ export function runClient({ url, name, hostKey, onExit }) {
69
89
  };
70
90
 
71
91
  const connect = () => {
72
- ws = new WebSocket(url, { handshakeTimeout: 10_000 });
92
+ ws = new WebSocket(url, { handshakeTimeout: 10_000, lookup });
73
93
  ws.on('open', () => {
74
94
  attempts = 0;
75
95
  everConnected = true;
package/src/tunnel.js CHANGED
@@ -2,6 +2,7 @@
2
2
  // machine over the internet without any port forwarding or account.
3
3
  import { spawn } from 'node:child_process';
4
4
  import fs from 'node:fs';
5
+ import dns from 'node:dns/promises';
5
6
 
6
7
  const URL_PATTERN = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
7
8
 
@@ -46,5 +47,33 @@ export async function startTunnel(port, { timeoutMs = 30_000 } = {}) {
46
47
  throw err;
47
48
  });
48
49
 
50
+ await waitForDns(new URL(url).hostname);
49
51
  return { url, stop };
50
52
  }
53
+
54
+ /**
55
+ * A quick tunnel's hostname takes a few seconds to appear in DNS. If a guest
56
+ * looks it up too early, their resolver remembers "no such host" and the join
57
+ * keeps failing. So before the code is shown, poll the domain's authoritative
58
+ * nameservers directly (they do not cache misses) until the name exists.
59
+ */
60
+ async function waitForDns(hostname, maxMs = 30_000) {
61
+ const started = Date.now();
62
+ const pause = (ms) => new Promise((r) => setTimeout(r, ms));
63
+ try {
64
+ const zone = hostname.split('.').slice(-2).join('.');
65
+ const nameservers = await dns.resolveNs(zone);
66
+ const addresses = (await Promise.all(nameservers.slice(0, 2).map((ns) => dns.resolve4(ns)))).flat();
67
+ while (Date.now() - started < maxMs) {
68
+ // A new resolver each time: a reused one caches the "not found" answer itself.
69
+ const resolver = new dns.Resolver({ timeout: 2000, tries: 1 });
70
+ resolver.setServers(addresses);
71
+ const found = await resolver.resolve4(hostname).then((a) => a.length > 0, () => false);
72
+ if (found) return;
73
+ await pause(1000);
74
+ }
75
+ } catch {
76
+ // Direct DNS is blocked on this network: fall back to a fixed wait.
77
+ await pause(Math.max(0, 8000 - (Date.now() - started)));
78
+ }
79
+ }