Back to Browser Side Quest
SOURCE / PINNED RELEASE

Made of little things.

Browser Side Quest

Release
b9d42f5768e4…
Author-recorded commit
c267690145fa…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/browser-side-quest

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

src/shared/format.ts
// Port of BrowserQuest server/js/format.js (MPL-2.0).
// HELLO gains an optional trailing string: the player's self-claimed Nostr pubkey.
import { Messages } from './gametypes.js';

type Field = 'n' | 's';

const formats: Record<number, { required: Field[]; optional?: Field[] }> = {
  [Messages.HELLO]: { required: ['s', 'n', 'n'], optional: ['s'] },
  [Messages.MOVE]: { required: ['n', 'n'] },
  [Messages.LOOTMOVE]: { required: ['n', 'n', 'n'] },
  [Messages.AGGRO]: { required: ['n'] },
  [Messages.ATTACK]: { required: ['n'] },
  [Messages.HIT]: { required: ['n'] },
  [Messages.HURT]: { required: ['n'] },
  [Messages.CHAT]: { required: ['s'] },
  [Messages.LOOT]: { required: ['n'] },
  [Messages.TELEPORT]: { required: ['n', 'n'] },
  [Messages.ZONE]: { required: [] },
  [Messages.OPEN]: { required: ['n'] },
  [Messages.CHECK]: { required: ['n'] },
};

const isNumber = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);

function matches(field: Field, value: unknown): boolean {
  return field === 'n' ? isNumber(value) : typeof value === 'string';
}

/** Validate a client→world message. */
export function check(message: unknown): message is unknown[] {
  if (!Array.isArray(message) || message.length === 0) return false;
  const [type, ...args] = message;
  if (!isNumber(type)) return false;

  if (type === Messages.WHO) {
    return args.length > 0 && args.every(isNumber);
  }

  const format = formats[type];
  if (!format) return false;
  const optional = format.optional ?? [];
  if (args.length < format.required.length) return false;
  if (args.length > format.required.length + optional.length) return false;
  const fields = [...format.required, ...optional];
  return args.every((value, i) => matches(fields[i], value));
}