feat: integrate erqiwang rooms and use spec-compliant Web compilation

This commit is contained in:
2026-09-07 22:36:24 +08:00
parent 30a1b4114c
commit a7ec8aa6a7
49 changed files with 11603 additions and 1183 deletions
@@ -1,7 +1,8 @@
export {
hasOwn,
optionalField,
requireArray,
requireArray,
requireRoomtype,
requireBoolean,
requireInteger,
requireNumber,
@@ -1,3 +1,4 @@
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
import {
buildFirstSliceEnvelope,
type FirstSliceEnvelope,
@@ -5,7 +6,8 @@ import {
import {
hasOwn,
optionalField,
requireArray,
requireArray,
requireRoomtype,
requireInteger,
requireNumber,
requireRecord,
@@ -22,7 +24,7 @@ export type ReconnectSnapshot =
export interface ParsedRoomSnapshot {
readonly roomcode: string;
readonly seat: number;
readonly roomtype: readonly unknown[];
readonly roomtype: Roomtype;
readonly players: readonly unknown[];
}
@@ -40,7 +42,7 @@ export function reconnectFrom(raw: JsonRecord): ReconnectSnapshot {
export function parseRoomEntryFields(raw: JsonRecord): ParsedRoomSnapshot {
const roomcode = requireString(raw.roomcode, '$.data.roomcode');
const seat = requireInteger(raw.seat, '$.data.seat');
const roomtype = requireArray(raw.roomtype, '$.data.roomtype');
const roomtype = requireRoomtype(raw.roomtype, '$.data.roomtype');
const players = requireArray(raw.players, '$.data.players');
if (seat < 0 || seat >= players.length) {
throw new RangeError(`$.data.seat: expected server seat index within players, received number`);
@@ -246,7 +248,7 @@ export function parseKickServerPayload(value: unknown): ParsedKickPayload {
export interface CreateRoomEnvironment {
readonly roomtype: readonly unknown[];
readonly roomtype: Roomtype;
readonly location: unknown;
readonly ip: string;
}
@@ -267,7 +269,7 @@ export function buildCreateRoomRequest(
agentid: requireStringOrFiniteNumber(identity.agentid, '$.identity.agentid'),
playerid: requireInteger(playerid, '$.playerid'),
gameid: requireStringOrFiniteNumber(identity.gameid, '$.identity.gameid'),
roomtype: requireArray(raw.roomtype, '$.command.roomtype'),
roomtype: requireRoomtype(raw.roomtype, '$.command.roomtype'),
ip: requireString(raw.ip, '$.command.ip'),
location: raw.location,
});
@@ -275,7 +277,7 @@ export function buildCreateRoomRequest(
export interface ParsedCreateRoomResponse {
readonly state: number;
readonly room: { readonly roomcode: string; readonly seat: number; readonly roomtype: readonly unknown[] } | null;
readonly room: { readonly roomcode: string; readonly seat: number; readonly roomtype: Roomtype } | null;
readonly needprepare: number;
readonly infinite: number;
readonly raw: JsonRecord;
@@ -296,7 +298,7 @@ export function parseCreateRoomResponse(value: unknown): ParsedCreateRoomRespons
state,
room: {roomcode: requireString(raw.roomcode, '$.data.roomcode'),
seat: requireInteger(raw.seat, '$.data.seat'),
roomtype: requireArray(raw.roomtype, '$.data.roomtype')},
roomtype: requireRoomtype(raw.roomtype, '$.data.roomtype')},
needprepare: flag('needprepare'), infinite: flag('infinite'), raw,
};
}
@@ -1,3 +1,4 @@
import type {Roomtype} from '../../sdk/contracts/roomtype.ts';
export type JsonRecord = Readonly<Record<string, unknown>>;
function receivedType(value: unknown): string {
@@ -66,3 +67,9 @@ export function optionalField<T>(
if (!hasOwn(record, key)) return undefined;
return validate(record[key], `${path}.${key}`);
}
/** Validate the transport shape without interpreting game-specific options. */
export function requireRoomtype(value: unknown, path: string): Roomtype {
if (typeof value === 'string' || Array.isArray(value)) return value;
return invalid(path, 'string or array', value);
}
@@ -1,4 +1,4 @@
import {requireArray,requireRecord,requireString} from './validation.ts';
import {requireArray,requireRoomtype,requireRecord,requireString} from './validation.ts';
import type {VipCode,VipRoom} from '../../core/types/vip-room.ts';
export function vipCode(value:unknown,path:string):VipCode {
if(typeof value==='string'&&value.length>0)return value;
@@ -12,7 +12,8 @@ export function parseVipRooms(payload:unknown):readonly VipRoom[]{
const p=`$.data.roomInfo[${index}]`,r=requireRecord(value,p);
const playerList=requireArray(r.playerList,p+'.playerList').map((v,i)=>Object.freeze([...requireArray(v,`${p}.playerList[${i}]`)]));
const codes=r.roomcodes?requireRecord(r.roomcodes,p+'.roomcodes'):null;
const roomcodes=codes?Object.freeze({roomtype:Object.freeze([...requireArray(codes.roomtype,p+'.roomcodes.roomtype')]),roomlist:Object.freeze(requireArray(codes.roomlist,p+'.roomcodes.roomlist').map((v,i)=>vipCode(v,`${p}.roomcodes.roomlist[${i}]`)))}):null;
const rawType=codes?requireRoomtype(codes.roomtype,p+'.roomcodes.roomtype'):null;
const roomcodes=codes?Object.freeze({roomtype:typeof rawType==='string'?rawType:Object.freeze([...(rawType as readonly unknown[])]),roomlist:Object.freeze(requireArray(codes.roomlist,p+'.roomcodes.roomlist').map((v,i)=>vipCode(v,`${p}.roomcodes.roomlist[${i}]`)))}):null;
const deskState=roomcodes?requireArray(r.deskState,p+'.deskState'):[];
const seatCount=roomcodes?requireArray(r.seatCount,p+'.seatCount').map((v,i)=>vipCode(v,`${p}.seatCount[${i}]`)):[];
if(roomcodes&&[roomcodes.roomlist.length,deskState.length,seatCount.length].some(n=>n!==playerList.length))throw TypeError(p+': desk arrays must have matching lengths');