Initial commit: ssessesse ki battle

This commit is contained in:
2026-10-13 11:15:26 +09:00
commit 4f24129e29
104 changed files with 10520 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createPeer } from '../src/client/peer.js';
import { PROTOCOL_VERSION } from '../constants/webrtc.js';
import { RULES_VERSION } from '../constants/gameplay.js';
class Signal {
handlers = new Map<string, Function[]>(); peer?: Signal; connected = false; disconnected = false;
on(name: string, fn: Function) { this.handlers.set(name, [...this.handlers.get(name) ?? [], fn]); return this; }
once(name: string, fn: Function) { return this.on(name, fn); }
fire(name: string, data?: any) { for (const fn of this.handlers.get(name) ?? []) fn(data); }
connect() { this.connected = true; queueMicrotask(() => { this.fire('connect'); if (this.peer?.connected) { this.fire('signal:peers', { count: 2 }); this.peer.fire('signal:peers', { count: 2 }); } }); }
disconnect() { this.disconnected = true; this.connected = false; return this; }
timeout() { return this; }
async emitWithAck(_event: string, packet: any) { queueMicrotask(() => this.peer?.fire('signal', packet)); return { ok: true }; }
removeAllListeners() { this.handlers.clear(); return this; }
}
class Channel {
readyState = 'connecting'; onopen?: () => void; onmessage?: (event: { data: string }) => void; onclose?: () => void;
peer?: Channel;
send(data: string) { queueMicrotask(() => this.peer?.onmessage?.({ data })); }
close() { this.readyState = 'closed'; this.onclose?.(); }
}
class PC {
connectionState = 'new'; localDescription: any; remoteDescription: any; channel?: Channel; peer?: PC;
ondatachannel?: (event: any) => void; onicecandidate?: (event: any) => void; onconnectionstatechange?: () => void;
createDataChannel(_label: string, options: any) { assert.equal(options.ordered, true); assert.equal(options.maxRetransmits, undefined); return this.channel = new Channel(); }
async createOffer() { return { type: 'offer', sdp: 'offer' }; }
async createAnswer() { return { type: 'answer', sdp: 'answer' }; }
async setLocalDescription(value: any) { this.localDescription = value; }
async setRemoteDescription(value: any) {
this.remoteDescription = value;
if (value.type === 'answer') {
const remote = new Channel(); this.channel!.peer = remote; remote.peer = this.channel;
this.peer!.channel = remote; this.peer!.ondatachannel?.({ channel: remote });
this.connectionState = this.peer!.connectionState = 'connected';
this.channel!.readyState = remote.readyState = 'open'; this.channel!.onopen?.(); remote.onopen?.();
}
}
async addIceCandidate() {} restartIce() {} close() { this.connectionState = 'closed'; }
async getStats() { return new Map(); }
getConfiguration() { return { iceServers: [], bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }; }
setConfiguration(value: any) { if (value.bundlePolicy !== 'max-bundle' || value.rtcpMuxPolicy !== 'require') throw new Error('INVALID_MODIFICATION'); }
}
test('peer connects reliable channel, closes signaling after mutual ready and keeps delivering data', async () => {
const sa = new Signal(), sb = new Signal(); sa.peer = sb; sb.peer = sa;
const pa = new PC(), pb = new PC(); pa.peer = pb; pb.peer = pa;
const config = { matchId: 'm1', roomId: 'r1', epoch: 0, iceServers: [] };
const a = createPeer({ ...config, slot: 0, createSignaling: () => sa as any, createConnection: () => pa as any });
const b = createPeer({ ...config, slot: 1, createSignaling: () => sb as any, createConnection: () => pb as any });
try {
const messages: any[] = []; b.onMessage(m => messages.push(m));
await Promise.all([a.connect(), b.connect()]);
assert.equal(sa.disconnected, true); assert.equal(sb.disconnected, true);
const message = { protocolVersion: PROTOCOL_VERSION, rulesVersion: RULES_VERSION, matchId: 'm1', connectionEpoch: 0, turnId: 1, type: 'turn:start' as const, startsAt: 1000 };
a.send(message); await new Promise(r => setImmediate(r)); assert.deepEqual(messages, [message]);
assert.equal(pa.connectionState, 'connected');
} finally { a.close(); b.close(); }
});
test('late ICE rejection after mutual ready does not invalidate a working data channel', async () => {
const sa = new Signal(), sb = new Signal(); sa.peer = sb; sb.peer = sa;
const pa = new PC(), pb = new PC(); pa.peer = pb; pb.peer = pa;
const normal = sa.emitWithAck.bind(sa); let rejectIce!: (error: Error) => void;
sa.emitWithAck = (event, packet) => packet.type === 'ice' ? new Promise((_resolve, reject) => { rejectIce = reject; }) : normal(event, packet);
const a = createPeer({ matchId: 'late', roomId: 'r', slot: 0, iceServers: [], createSignaling: () => sa as any, createConnection: () => pa as any });
const b = createPeer({ matchId: 'late', roomId: 'r', slot: 1, iceServers: [], createSignaling: () => sb as any, createConnection: () => pb as any });
try {
const connecting = a.connect(); pa.onicecandidate?.({ candidate: { toJSON: () => ({ candidate: 'test' }) } });
await Promise.all([connecting, b.connect()]);
rejectIce(new Error('PEER_NOT_READY')); await new Promise(r => setImmediate(r));
assert.equal(a.connected, true); assert.equal(b.connected, true);
} finally { a.close(); b.close(); }
});
test('an ICE failure from the previous epoch cannot reject the new connection attempt', async () => {
const sa = new Signal(), sb = new Signal(); sa.peer = sb; sb.peer = sa;
const pa = new PC(), pb = new PC(); pa.peer = pb; pb.peer = pa;
const normal = sa.emitWithAck.bind(sa); let rejectIce!: (error: Error) => void;
sa.emitWithAck = (event, packet) => packet.type === 'ice' ? new Promise((_resolve, reject) => { rejectIce = reject; }) : normal(event, packet);
const a = createPeer({ matchId: 'generation', roomId: 'r', slot: 0, iceServers: [], createSignaling: () => sa as any, createConnection: () => pa as any });
const b = createPeer({ matchId: 'generation', roomId: 'r', slot: 1, iceServers: [], createSignaling: () => sb as any, createConnection: () => pb as any });
try {
const first = a.connect(); pa.onicecandidate?.({ candidate: { toJSON: () => ({ candidate: 'old' }) } });
await Promise.all([first, b.connect()]);
const recovery = a.restart(); let failed = false; void recovery.catch(() => { failed = true; });
rejectIce(new Error('OLD_ICE')); await new Promise(r => setImmediate(r));
assert.equal(failed, false); assert.equal(sa.connected, true);
await Promise.all([recovery, b.restart()]); assert.equal(a.connected, true);
} finally { a.close(); b.close(); }
});
test('ICE restart refreshes servers without changing immutable connection configuration', async () => {
const sa = new Signal(), sb = new Signal(); sa.peer = sb; sb.peer = sa;
const pa = new PC(), pb = new PC(); pa.peer = pb; pb.peer = pa;
const a = createPeer({ matchId: 'restart', roomId: 'r', slot: 0, iceServers: [], refreshIce: async () => [{ urls: 'stun:example.test' }], createSignaling: () => sa as any, createConnection: () => pa as any });
const b = createPeer({ matchId: 'restart', roomId: 'r', slot: 1, iceServers: [], refreshIce: async () => [], createSignaling: () => sb as any, createConnection: () => pb as any });
try {
await Promise.all([a.connect(), b.connect()]);
await Promise.all([a.restart(), b.restart()]);
assert.equal(a.connected, true); assert.equal(b.connected, true); assert.equal(a.epoch, 1);
} finally { a.close(); b.close(); }
});
test('recovery replaces a closed data channel and can replace a connection that rejects ICE reconfiguration', async () => {
const sa = new Signal(), sb = new Signal(); sa.peer = sb; sb.peer = sa;
const pairs = Array.from({ length: 2 }, () => { const a = new PC(), b = new PC(); a.peer = b; b.peer = a; return [a, b]; });
let ai = 0, bi = 0;
pairs[0][0].setConfiguration = () => { throw new Error('InvalidModificationError'); };
const a = createPeer({ matchId: 'fallback', roomId: 'r', slot: 0, iceServers: [], refreshIce: async () => [], createSignaling: () => sa as any, createConnection: () => pairs[ai++][0] as any });
const b = createPeer({ matchId: 'fallback', roomId: 'r', slot: 1, iceServers: [], refreshIce: async () => [], createSignaling: () => sb as any, createConnection: () => pairs[bi++][1] as any });
try {
await Promise.all([a.connect(), b.connect()]);
pairs[0][1].channel!.close();
await Promise.all([a.restart(), b.restart()]);
assert.equal(a.connected, true); assert.equal(b.connected, true);
assert.equal(pairs[0][0].connectionState, 'closed'); assert.equal(pairs[0][1].connectionState, 'closed');
assert.equal(ai, 2); assert.equal(bi, 2);
} finally { a.close(); b.close(); }
});