2026-02-20 08:16:43 -05:00
|
|
|
import { z } from 'zod';
|
|
|
|
|
|
|
|
|
|
export const itemSchema = z.object({
|
|
|
|
|
id: z.string(),
|
2026-02-24 18:48:08 -05:00
|
|
|
type: z.string().min(1),
|
2026-02-20 08:16:43 -05:00
|
|
|
title: z.string(),
|
|
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
createdBy: z.string(),
|
|
|
|
|
createdAt: z.number().int(),
|
|
|
|
|
updatedAt: z.number().int(),
|
|
|
|
|
version: z.number().int(),
|
|
|
|
|
capabilities: z.array(z.string()),
|
2026-02-21 16:13:48 -05:00
|
|
|
useSound: z.string().optional(),
|
2026-02-21 16:01:40 -05:00
|
|
|
emitSound: z.string().optional(),
|
2026-02-20 08:16:43 -05:00
|
|
|
params: z.record(z.string(), z.unknown()),
|
|
|
|
|
carrierId: z.string().nullable().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const welcomeMessageSchema = z.object({
|
|
|
|
|
type: z.literal('welcome'),
|
|
|
|
|
id: z.string(),
|
|
|
|
|
users: z.array(
|
|
|
|
|
z.object({
|
|
|
|
|
id: z.string(),
|
|
|
|
|
nickname: z.string(),
|
|
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
items: z.array(itemSchema).optional(),
|
2026-02-21 19:12:58 -05:00
|
|
|
worldConfig: z
|
|
|
|
|
.object({
|
|
|
|
|
gridSize: z.number().int().positive(),
|
|
|
|
|
})
|
|
|
|
|
.optional(),
|
2026-02-22 18:20:13 -05:00
|
|
|
serverInfo: z
|
|
|
|
|
.object({
|
|
|
|
|
instanceId: z.string(),
|
|
|
|
|
version: z.string().optional(),
|
|
|
|
|
})
|
|
|
|
|
.optional(),
|
2026-02-21 19:12:58 -05:00
|
|
|
uiDefinitions: z
|
|
|
|
|
.object({
|
2026-02-24 18:48:08 -05:00
|
|
|
itemTypeOrder: z.array(z.string().min(1)),
|
2026-02-21 19:12:58 -05:00
|
|
|
itemTypes: z.array(
|
|
|
|
|
z.object({
|
2026-02-24 18:48:08 -05:00
|
|
|
type: z.string().min(1),
|
2026-02-21 19:12:58 -05:00
|
|
|
label: z.string().optional(),
|
2026-02-21 20:47:02 -05:00
|
|
|
tooltip: z.string().optional(),
|
2026-02-21 19:12:58 -05:00
|
|
|
editableProperties: z.array(z.string()),
|
2026-02-24 18:48:08 -05:00
|
|
|
capabilities: z.array(z.string()).optional(),
|
2026-02-21 20:47:02 -05:00
|
|
|
propertyMetadata: z
|
|
|
|
|
.record(
|
|
|
|
|
z.string(),
|
|
|
|
|
z.object({
|
|
|
|
|
valueType: z.enum(['boolean', 'text', 'number', 'list', 'sound']).optional(),
|
2026-02-24 18:48:08 -05:00
|
|
|
label: z.string().optional(),
|
2026-02-21 20:47:02 -05:00
|
|
|
tooltip: z.string().optional(),
|
2026-02-22 03:50:52 -05:00
|
|
|
maxLength: z.number().int().positive().optional(),
|
2026-02-24 18:48:08 -05:00
|
|
|
options: z.array(z.string()).optional(),
|
|
|
|
|
visibleWhen: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
|
2026-02-21 20:47:02 -05:00
|
|
|
range: z
|
|
|
|
|
.object({
|
|
|
|
|
min: z.number(),
|
|
|
|
|
max: z.number(),
|
|
|
|
|
step: z.number().optional(),
|
|
|
|
|
})
|
|
|
|
|
.optional(),
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.optional(),
|
2026-02-21 19:12:58 -05:00
|
|
|
globalProperties: z.record(z.string(), z.unknown()).optional(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
.optional(),
|
2026-02-20 08:16:43 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const signalMessageSchema = z.object({
|
|
|
|
|
type: z.literal('signal'),
|
|
|
|
|
senderId: z.string(),
|
|
|
|
|
senderNickname: z.string().optional(),
|
|
|
|
|
x: z.number().int().optional(),
|
|
|
|
|
y: z.number().int().optional(),
|
|
|
|
|
targetId: z.string().optional(),
|
|
|
|
|
sdp: z.any().optional(),
|
|
|
|
|
ice: z.any().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const updatePositionSchema = z.object({
|
|
|
|
|
type: z.literal('update_position'),
|
|
|
|
|
id: z.string(),
|
|
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const updateNicknameSchema = z.object({
|
|
|
|
|
type: z.literal('update_nickname'),
|
|
|
|
|
id: z.string(),
|
|
|
|
|
nickname: z.string().min(1).max(32),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const userLeftSchema = z.object({
|
|
|
|
|
type: z.literal('user_left'),
|
|
|
|
|
id: z.string(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const chatMessageSchema = z.object({
|
|
|
|
|
type: z.literal('chat_message'),
|
|
|
|
|
message: z.string(),
|
|
|
|
|
senderId: z.string().optional(),
|
|
|
|
|
senderNickname: z.string().optional(),
|
|
|
|
|
system: z.boolean().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const pongSchema = z.object({
|
|
|
|
|
type: z.literal('pong'),
|
|
|
|
|
clientSentAt: z.number().int(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const nicknameResultSchema = z.object({
|
|
|
|
|
type: z.literal('nickname_result'),
|
|
|
|
|
accepted: z.boolean(),
|
|
|
|
|
requestedNickname: z.string(),
|
|
|
|
|
effectiveNickname: z.string(),
|
|
|
|
|
reason: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const itemUpsertSchema = z.object({
|
|
|
|
|
type: z.literal('item_upsert'),
|
|
|
|
|
item: itemSchema,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const itemRemoveSchema = z.object({
|
|
|
|
|
type: z.literal('item_remove'),
|
|
|
|
|
itemId: z.string(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const itemActionResultSchema = z.object({
|
|
|
|
|
type: z.literal('item_action_result'),
|
|
|
|
|
ok: z.boolean(),
|
|
|
|
|
action: z.enum(['add', 'pickup', 'drop', 'delete', 'use', 'update']),
|
|
|
|
|
message: z.string(),
|
|
|
|
|
itemId: z.string().optional(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export const itemUseSoundSchema = z.object({
|
|
|
|
|
type: z.literal('item_use_sound'),
|
|
|
|
|
itemId: z.string(),
|
|
|
|
|
sound: z.string(),
|
|
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-22 23:42:17 -05:00
|
|
|
export const itemPianoNoteSchema = z.object({
|
|
|
|
|
type: z.literal('item_piano_note'),
|
|
|
|
|
itemId: z.string(),
|
|
|
|
|
senderId: z.string(),
|
|
|
|
|
keyId: z.string(),
|
|
|
|
|
midi: z.number().int().min(0).max(127),
|
|
|
|
|
on: z.boolean(),
|
|
|
|
|
instrument: z.string(),
|
2026-02-23 00:22:36 -05:00
|
|
|
voiceMode: z.enum(['mono', 'poly']),
|
|
|
|
|
octave: z.number().int().min(-2).max(2),
|
2026-02-22 23:42:17 -05:00
|
|
|
attack: z.number().int().min(0).max(100),
|
|
|
|
|
decay: z.number().int().min(0).max(100),
|
2026-02-23 00:05:01 -05:00
|
|
|
release: z.number().int().min(0).max(100),
|
|
|
|
|
brightness: z.number().int().min(0).max(100),
|
2026-02-22 23:42:17 -05:00
|
|
|
x: z.number().int(),
|
|
|
|
|
y: z.number().int(),
|
|
|
|
|
emitRange: z.number().int().min(1),
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-20 08:16:43 -05:00
|
|
|
export const incomingMessageSchema = z.discriminatedUnion('type', [
|
|
|
|
|
welcomeMessageSchema,
|
|
|
|
|
signalMessageSchema,
|
|
|
|
|
updatePositionSchema,
|
|
|
|
|
updateNicknameSchema,
|
|
|
|
|
userLeftSchema,
|
|
|
|
|
chatMessageSchema,
|
|
|
|
|
pongSchema,
|
|
|
|
|
nicknameResultSchema,
|
|
|
|
|
itemUpsertSchema,
|
|
|
|
|
itemRemoveSchema,
|
|
|
|
|
itemActionResultSchema,
|
|
|
|
|
itemUseSoundSchema,
|
2026-02-22 23:42:17 -05:00
|
|
|
itemPianoNoteSchema,
|
2026-02-20 08:16:43 -05:00
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
export type IncomingMessage = z.infer<typeof incomingMessageSchema>;
|
|
|
|
|
|
|
|
|
|
export type OutgoingMessage =
|
|
|
|
|
| { type: 'signal'; targetId: string; sdp?: RTCSessionDescriptionInit; ice?: RTCIceCandidateInit }
|
|
|
|
|
| { type: 'update_position'; x: number; y: number }
|
|
|
|
|
| { type: 'update_nickname'; nickname: string }
|
|
|
|
|
| { type: 'chat_message'; message: string }
|
|
|
|
|
| { type: 'ping'; clientSentAt: number }
|
2026-02-24 18:48:08 -05:00
|
|
|
| { type: 'item_add'; itemType: string }
|
2026-02-20 08:16:43 -05:00
|
|
|
| { type: 'item_pickup'; itemId: string }
|
|
|
|
|
| { type: 'item_drop'; itemId: string; x: number; y: number }
|
|
|
|
|
| { type: 'item_delete'; itemId: string }
|
|
|
|
|
| { type: 'item_use'; itemId: string }
|
2026-02-22 23:42:17 -05:00
|
|
|
| { type: 'item_piano_note'; itemId: string; keyId: string; midi: number; on: boolean }
|
2026-02-23 02:00:01 -05:00
|
|
|
| { type: 'item_piano_recording'; itemId: string; action: 'toggle_record' | 'playback' | 'stop_playback' | 'stop_record' }
|
2026-02-20 08:16:43 -05:00
|
|
|
| {
|
|
|
|
|
type: 'item_update';
|
|
|
|
|
itemId: string;
|
|
|
|
|
title?: string;
|
|
|
|
|
params?: Record<string, unknown>;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export type RemoteUser = {
|
|
|
|
|
id: string;
|
|
|
|
|
nickname: string;
|
|
|
|
|
x: number;
|
|
|
|
|
y: number;
|
|
|
|
|
};
|