Back to Napcraft
SOURCE / PINNED RELEASE

Made of little things.

Napcraft

Release
ee47ac6a25df…
Author-recorded commit
410dea87e109…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce

Archive hash verified: 9e6e0cabb18d0d46…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

tests/local-relay.mjs
// Test-only ICE configuration and network delay around the real host/WebRTC path.
import { spawn } from 'node:child_process';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { createServer, createConnection } from 'node:net';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
export const browserArgs = [
  '--allow-loopback-in-peer-connection',
  '--disable-features=WebRtcHideLocalIpsWithMdns',
  '--disable-background-timer-throttling',
  '--disable-renderer-backgrounding',
  '--disable-backgrounding-occluded-windows',
];
export async function startLocalRelay() {
  const reservation = createServer();
  await new Promise((r) => reservation.listen(0, '127.0.0.1', r));
  const port = reservation.address().port;
  await new Promise((r) => reservation.close(r));
  const directory = await mkdtemp(join(tmpdir(), 'napcraft-relay-')),
    credential = crypto.randomUUID();
  const file = join(directory, 'turn.conf');
  await writeFile(
    file,
    `listening-ip=127.0.0.1\nrelay-ip=127.0.0.1\nlistening-port=${port}\nrealm=napcraft-test\nlt-cred-mech\nuser=napcraft-test:${credential}\nallow-loopback-peers\nno-multicast-peers\nrelay-threads=1\nlog-file=stdout\npidfile=${directory}/turn.pid\n`,
    { mode: 0o600 },
  );
  const child = spawn(
    process.env.NAPCRAFT_TURN || '/opt/homebrew/bin/turnserver',
    ['-c', file],
    { stdio: 'ignore' },
  );
  let error;
  child.on('error', (e) => (error = e));
  const close = async () => {
    if (child.exitCode === null) {
      child.kill('SIGTERM');
      await new Promise((r) => child.once('exit', r));
    }
    await rm(directory, { recursive: true, force: true });
  };
  try {
    const start = Date.now();
    while (true) {
      if (error) throw error;
      if (child.exitCode !== null) throw Error('Local coturn failed to start');
      const connected = await new Promise((resolve) => {
        const socket = createConnection({ host: '127.0.0.1', port });
        socket.once('connect', () => {
          socket.destroy();
          resolve(true);
        });
        socket.once('error', () => resolve(false));
      });
      if (connected) break;
      if (Date.now() - start > 15000)
        throw Error('Local coturn startup timeout');
      await new Promise((r) => setTimeout(r, 100));
    }
  } catch (e) {
    await close();
    throw e;
  }
  return {
    config: {
      urls: `turn:127.0.0.1:${port}?transport=tcp`,
      username: 'napcraft-test',
      credential,
    },
    close,
  };
}
export async function useLocalRelay(
  context,
  config,
  latency = 50,
  jitter = 15,
) {
  await context.addInitScript(
    ({ config, latency, jitter }) => {
      if (window.parent !== window) return;
      window.testConnections = [];
      const Original = window.RTCPeerConnection;
      window.RTCPeerConnection = class extends Original {
        constructor(...args) {
          super({
            ...args[0],
            iceServers: [config],
            iceTransportPolicy: 'relay',
          });
          window.testConnections.push(this);
        }
      };
      const send = RTCDataChannel.prototype.send;
      const deadlines = new WeakMap();
      let seed = 17;
      RTCDataChannel.prototype.send = function (data) {
        seed = (seed * 16807) % 2147483647;
        const wait = Math.max(
          0,
          latency + ((seed / 2147483647) * 2 - 1) * jitter,
        );
        const at = Math.max(performance.now() + wait, deadlines.get(this) || 0);
        deadlines.set(this, at);
        setTimeout(
          () => {
            if (this.readyState === 'open') send.call(this, data);
          },
          Math.max(0, at - performance.now()),
        );
      };
    },
    { config, latency, jitter },
  );
}