@irtio/protocol 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +102 -9
- package/dist/index.js +96 -12
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _irtio_schema from '@irtio/schema';
|
|
2
|
-
import { ByteReader, RpcMap, ServerRpcs, AnySchema, RpcDesc, InstanceOf, Schema, SchemaDefs, SchemaRpc, SchemaRoles } from '@irtio/schema';
|
|
2
|
+
import { ByteReader, RpcMap, ServerRpcs, AnySchema, RpcDesc, InstanceOf, Schema, SchemaDefs, SchemaRpc, SchemaRoles, SchemaMessages } from '@irtio/schema';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Frame envelope: one type byte, then an opaque payload. `encodeFrame`/`decodeFrame` only
|
|
@@ -9,12 +9,18 @@ import { ByteReader, RpcMap, ServerRpcs, AnySchema, RpcDesc, InstanceOf, Schema,
|
|
|
9
9
|
* Wire protocol version (u8), bumped on any breaking change to framing or payload shapes.
|
|
10
10
|
*
|
|
11
11
|
* 2: `WELCOME`'s tick slot carries the room's tick rate in ticks per second, where version 1
|
|
12
|
-
* carried the interval in whole milliseconds. The slot kept its width and type, so a
|
|
13
|
-
* client would read `60` as a 60 ms interval and quietly run a four-times-too-long
|
|
14
|
-
* delay and a wrong physics timestep.
|
|
15
|
-
*
|
|
12
|
+
* carried the interval in whole milliseconds (`b28a325`). The slot kept its width and type, so a
|
|
13
|
+
* version 1 client would read `60` as a 60 ms interval and quietly run a four-times-too-long
|
|
14
|
+
* interpolation delay and a wrong physics timestep.
|
|
15
|
+
*
|
|
16
|
+
* 3: `CALL` carries the caller's `clientTick` between `rpcId` and the params (D72, lag
|
|
17
|
+
* compensation). The params are rest-of-buffer, so an older server reading a version 3 `CALL`
|
|
18
|
+
* would take four bytes of the stamp as the head of the params and decode nonsense. Either bump
|
|
19
|
+
* turns the mismatch into `E_PROTOCOL_VERSION` at join, which is a client bundle that needs
|
|
20
|
+
* redeploying rather than an afternoon of debugging. 2 and 3 landed on separate branches the
|
|
21
|
+
* same day and met at the M6 wave 1 merge; both are spent.
|
|
16
22
|
*/
|
|
17
|
-
declare const PROTOCOL_VERSION =
|
|
23
|
+
declare const PROTOCOL_VERSION = 3;
|
|
18
24
|
declare const FrameType: {
|
|
19
25
|
readonly HELLO: 1;
|
|
20
26
|
readonly WELCOME: 2;
|
|
@@ -250,6 +256,25 @@ declare const ErrorCode: {
|
|
|
250
256
|
readonly code: 33;
|
|
251
257
|
readonly message: "no capacity in region {region}; add a server or raise maxVms";
|
|
252
258
|
};
|
|
259
|
+
/**
|
|
260
|
+
* M6 lane A (D68-d): an operator deleted this room, and its stored state is gone.
|
|
261
|
+
*
|
|
262
|
+
* Its own code rather than `E_KICKED` or `E_ROOM_CLOSED`, and the difference between the three is
|
|
263
|
+
* the reason it exists. `E_KICKED` is the room's own code deciding one player should leave, and
|
|
264
|
+
* a client's right answer is usually to show a message and stay in the game. `E_ROOM_CLOSED` is
|
|
265
|
+
* a room ending normally; rejoining makes a fresh one and nothing was lost. This is neither: the
|
|
266
|
+
* room's state has been deleted from outside the game, deliberately, by somebody with a rooms
|
|
267
|
+
* API credential. A client that reconnects gets an empty room rather than the one it was in.
|
|
268
|
+
* Different thing to tell a player, different thing for a client to do, different code.
|
|
269
|
+
*
|
|
270
|
+
* Fatal, which is load-bearing: `@irtio/client`'s fatal path leaves for good rather than
|
|
271
|
+
* reconnecting, and that is what stops a deleted room from being immediately recreated by the
|
|
272
|
+
* very clients that were in it. Close code {@link CLOSE_ROOM_DELETED}.
|
|
273
|
+
*/
|
|
274
|
+
readonly E_ROOM_DELETED: {
|
|
275
|
+
readonly code: 34;
|
|
276
|
+
readonly message: "room {roomId} was deleted by an operator; its stored state is gone";
|
|
277
|
+
};
|
|
253
278
|
};
|
|
254
279
|
/**
|
|
255
280
|
* The WebSocket close code the router uses for {@link ErrorCode.E_PLACEMENT}: RFC 6455's 1013,
|
|
@@ -266,6 +291,16 @@ declare const CLOSE_TRY_AGAIN_LATER = 1013;
|
|
|
266
291
|
* they may not have. 4290 echoes HTTP 429 for the same reason a person would guess it does.
|
|
267
292
|
*/
|
|
268
293
|
declare const CLOSE_EGRESS_WALL = 4290;
|
|
294
|
+
/**
|
|
295
|
+
* The WebSocket close code for {@link ErrorCode.E_ROOM_DELETED} (D68-d).
|
|
296
|
+
*
|
|
297
|
+
* In the application range beside {@link CLOSE_EGRESS_WALL}, and its own value for the same
|
|
298
|
+
* reason: a developer watching sockets close has to be able to tell an operator deleting a room
|
|
299
|
+
* from a wall, from a policy refusal, and from a crash, without a log they may not have. 4291 sits
|
|
300
|
+
* next to 4290 because both are "the platform stopped this on purpose", and the two are one apart
|
|
301
|
+
* so a grep for `429` in a close-code histogram finds both.
|
|
302
|
+
*/
|
|
303
|
+
declare const CLOSE_ROOM_DELETED = 4291;
|
|
269
304
|
type ErrorCodeName = keyof typeof ErrorCode;
|
|
270
305
|
interface ErrorCatalogueEntry {
|
|
271
306
|
readonly name: ErrorCodeName;
|
|
@@ -455,6 +490,17 @@ declare function encodeSchemaFrame(canonical: string): Uint8Array;
|
|
|
455
490
|
interface Call {
|
|
456
491
|
readonly reqId: number;
|
|
457
492
|
readonly rpcId: number;
|
|
493
|
+
/**
|
|
494
|
+
* D72: the newest authoritative tick the caller had applied when it sent this `CALL`, so a room
|
|
495
|
+
* can answer the shot against the world the shooter was looking at (`ctx.clientTick`,
|
|
496
|
+
* `room.rewind`). `0` means "no stamp" — a server-to-client `CALL`, or a client that does not
|
|
497
|
+
* send one — and ticks start at 1, so zero can never be a real tick.
|
|
498
|
+
*
|
|
499
|
+
* It is a client-supplied number that selects which past the server answers from, and a client
|
|
500
|
+
* can lie about it. What a lie can reach is bounded by the room's history depth, and the room
|
|
501
|
+
* decides what it does with the answer; `concepts/lag-compensation.md` says both.
|
|
502
|
+
*/
|
|
503
|
+
readonly clientTick?: number;
|
|
458
504
|
/** Rest-of-buffer: opaque schema-codec-encoded params (not length-prefixed). */
|
|
459
505
|
readonly params: Uint8Array;
|
|
460
506
|
}
|
|
@@ -659,13 +705,60 @@ interface Msg {
|
|
|
659
705
|
readonly target: MsgTarget;
|
|
660
706
|
/** Rest-of-buffer: opaque application payload. */
|
|
661
707
|
readonly payload: Uint8Array;
|
|
708
|
+
/**
|
|
709
|
+
* D70: present when this is a **typed** message — one of the shapes the schema declares, whose
|
|
710
|
+
* `payload` is `encodeFields(schema.messages[index].fields, value)` rather than opaque bytes.
|
|
711
|
+
*
|
|
712
|
+
* Absent for a raw `room.message(target, bytes)`. The two never mix: a receiver hands a typed
|
|
713
|
+
* frame only to typed listeners and a raw frame only to raw ones, on the client and in a room
|
|
714
|
+
* alike, so game code is never handed one under the other's API.
|
|
715
|
+
*/
|
|
716
|
+
readonly typed?: {
|
|
717
|
+
readonly index: number;
|
|
718
|
+
};
|
|
662
719
|
}
|
|
720
|
+
/**
|
|
721
|
+
* D70 typed peer messages: an **envelope**, not a sixth address.
|
|
722
|
+
*
|
|
723
|
+
* The addressing byte is the one byte the format already spends on every MSG, so a typed message
|
|
724
|
+
* costs a discriminator it was going to pay for plus two bytes of index — no new `FrameType`, and
|
|
725
|
+
* directed delivery keeps working because the envelope carries a target of its own right after
|
|
726
|
+
* the index. Layout:
|
|
727
|
+
*
|
|
728
|
+
* [5][index u16 LE][inner target kind][inner target fields][encoded payload]
|
|
729
|
+
*
|
|
730
|
+
* The inner target is the same vocabulary as an untyped MSG minus voice: `all`/`client`/`role`
|
|
731
|
+
* inbound, and the sender's `client` or `server` on the way out (the fan-out rewrites the slot,
|
|
732
|
+
* exactly as it does for raw bytes). `voice` is refused on both encode and decode — a typed
|
|
733
|
+
* envelope is authored by game code, and voice signaling must stay unrepresentable there.
|
|
734
|
+
*
|
|
735
|
+
* `isVoiceMsg` is unaffected: a typed frame's first byte is 5, never 4.
|
|
736
|
+
*/
|
|
737
|
+
declare const MSG_KIND_TYPED = 5;
|
|
663
738
|
/**
|
|
664
739
|
* A one-byte peek: is this MSG payload voice signaling? The supervisor calls this on every MSG
|
|
665
740
|
* before deciding where the frame goes, so it must not allocate or decode. An empty payload is
|
|
666
741
|
* not voice (it is malformed, and the existing paths report that).
|
|
667
742
|
*/
|
|
668
743
|
declare function isVoiceMsg(payload: Uint8Array): boolean;
|
|
744
|
+
/**
|
|
745
|
+
* D70: the same one-byte peek for a typed envelope. The supervisor uses it on the hot frame path
|
|
746
|
+
* for the same reason it uses `isVoiceMsg` — to decide where a frame goes without decoding it.
|
|
747
|
+
*/
|
|
748
|
+
declare function isTypedMsg(payload: Uint8Array): boolean;
|
|
749
|
+
/**
|
|
750
|
+
* D70: may this typed envelope be **accepted from a client**?
|
|
751
|
+
*
|
|
752
|
+
* A three-byte peek, no allocation: an inbound typed message may address `all`, a client or a
|
|
753
|
+
* role, and nothing else. `server` has no meaning on the peer path (a room observes through
|
|
754
|
+
* `onMessage`; there is no typed server handler, by design) and `voice` must never be authorable
|
|
755
|
+
* by game code. Both are dropped by whoever sees the frame first, which is the supervisor above
|
|
756
|
+
* the relay fork and the relay host in the shared process.
|
|
757
|
+
*
|
|
758
|
+
* Returns `false` for anything too short to read, so a truncated frame is refused rather than
|
|
759
|
+
* assumed.
|
|
760
|
+
*/
|
|
761
|
+
declare function typedMsgFromClientOk(payload: Uint8Array): boolean;
|
|
669
762
|
declare function encodeMsg(m: Msg): Uint8Array;
|
|
670
763
|
declare function decodeMsg(bytes: Uint8Array): Msg;
|
|
671
764
|
|
|
@@ -794,7 +887,7 @@ type PresenceRecord = InstanceOf<typeof presenceEntity>;
|
|
|
794
887
|
*/
|
|
795
888
|
declare function withBuiltins<S extends AnySchema>(schema: S): Schema<SchemaDefs<S> & {
|
|
796
889
|
clients: typeof presenceEntity;
|
|
797
|
-
}, SchemaRpc<S>, SchemaRoles<S>>;
|
|
890
|
+
}, SchemaRpc<S>, SchemaRoles<S>, SchemaMessages<S>>;
|
|
798
891
|
/**
|
|
799
892
|
* The schema of a room with nothing deployed — no builder collections, just the
|
|
800
893
|
* built-in presence merged by `withBuiltins`. The supervisor's relay forwarder encodes presence
|
|
@@ -803,7 +896,7 @@ declare function withBuiltins<S extends AnySchema>(schema: S): Schema<SchemaDefs
|
|
|
803
896
|
*/
|
|
804
897
|
declare const relaySchema: Schema<{
|
|
805
898
|
clients: typeof presenceEntity;
|
|
806
|
-
}, {}, readonly string[]>;
|
|
899
|
+
}, {}, readonly string[], {}>;
|
|
807
900
|
/** The `schemaHash8` a relay HELLO carries: 8 zero bytes ("no schema"). */
|
|
808
901
|
declare const RELAY_HASH8: Uint8Array;
|
|
809
902
|
declare function isRelayHash8(hash8: Uint8Array): boolean;
|
|
@@ -1518,4 +1611,4 @@ declare function originAllowed(origins: readonly string[], allowNoOrigin: boolea
|
|
|
1518
1611
|
*/
|
|
1519
1612
|
declare function parseOriginList(raw: string | undefined): readonly string[];
|
|
1520
1613
|
|
|
1521
|
-
export { type AttributeOptions, BUS_CHANNEL_RE, BUS_ERRORS, BUS_LIMITS, BUS_MAILBOX_PREFIX, BUS_OUTBOX, BUS_OUTBOX_PREFIX, BUS_SHARD_STARTING, type BusErrorCode, type BusMailboxEntry, type BusOutboxEntry, type BusProblem, CLOSE_EGRESS_WALL, CLOSE_TRY_AGAIN_LATER, CODE_ALPHABET, CPU_QUOTA_PCT_MAX, CPU_QUOTA_PCT_MIN, type Call, type ChurnIds, type ClientCallable, type Credential, DEFAULT_MAX_AWAKE, DEFAULT_MEMORY_MB, DEFAULT_ROOM_TYPE, EMPTY_PROFILE, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, HELLO_SCHEMA_SWAP_BIT, type Hello, MATCH_CODE_LENGTH, MAX_AWAKE_MAX, type Msg, type MsgTarget, NATIVE_RESERVE_FRACTION, PHYSICS_HEADROOM_MIB, PRESENCE_COLLECTION, PROFILE_KINDS, PROTOCOL_VERSION, type ParsedRoomId, type Ping, type Pong, type PresenceRecord, type ProfileKind, ProfileLedger, type ProfileRow, type ProfileSnapshot, RELAY_HASH8, RETENTION_MAX_MS, RETENTION_MIN_MS, RETENTION_RE, ROOMS_PER_VCPU, ROOM_CLASS_MAX_MB, ROOM_ID_RE, ROOM_MEMORY_MB_MAX, ROOM_MEMORY_MB_MIN, ROOM_TYPE_DELIMITER, ROOM_TYPE_RE, type Reply, type RoomSizeClass, type RoomTypeSizing, SUPERVISOR_BASELINE_MIB, VM_MEM_MIB_MAX, VM_MEM_MIB_MIN, type VmCpuSizing, type VmSizing, type VoiceClientMessage, type VoicePeer, type VoiceServerMessage, type VoiceTransportInfo, type Welcome, YOUNG_GEN_MB, builtinRpcs, busChannelProblem, busPayloadProblem, correctPayload, declarationsFit, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeVoiceMessage, decodeWelcome, deltaPayload, diffProfiles, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeSchemaFrame, encodeVoiceMessage, encodeWelcome, encodeWriteFrame, errorByCode, formatError, formatRoomId, isFrameType, isLocalhostOrigin, isMailboxAlarm, isOutboxAlarm, isRelayHash8, isVoiceMsg, mergeProfiles, nativeReserveOf, originAllowed, parseOriginList, parseRetention, parseRoomId, presenceEntity, readCorrectAppliedTick, readCorrectClientTick, readSchemaPayload, relaySchema, requestOwnership, roomClassFor, roomClassOfSource, roomHoursSourceFor, rpcByIdOf, rpcIdOf, rpcTable, scaleProfile, schemaPayload, topProfileRows, typeFootprintMib, vmCpuFor, vmSizeFor, withBuiltins, writePayload };
|
|
1614
|
+
export { type AttributeOptions, BUS_CHANNEL_RE, BUS_ERRORS, BUS_LIMITS, BUS_MAILBOX_PREFIX, BUS_OUTBOX, BUS_OUTBOX_PREFIX, BUS_SHARD_STARTING, type BusErrorCode, type BusMailboxEntry, type BusOutboxEntry, type BusProblem, CLOSE_EGRESS_WALL, CLOSE_ROOM_DELETED, CLOSE_TRY_AGAIN_LATER, CODE_ALPHABET, CPU_QUOTA_PCT_MAX, CPU_QUOTA_PCT_MIN, type Call, type ChurnIds, type ClientCallable, type Credential, DEFAULT_MAX_AWAKE, DEFAULT_MEMORY_MB, DEFAULT_ROOM_TYPE, EMPTY_PROFILE, ERROR_CATALOGUE, type ErrorCatalogueEntry, ErrorCode, type ErrorCodeDef, type ErrorCodeName, type ErrorPayload, type Frame, FrameType, HELLO_SCHEMA_SWAP_BIT, type Hello, MATCH_CODE_LENGTH, MAX_AWAKE_MAX, MSG_KIND_TYPED, type Msg, type MsgTarget, NATIVE_RESERVE_FRACTION, PHYSICS_HEADROOM_MIB, PRESENCE_COLLECTION, PROFILE_KINDS, PROTOCOL_VERSION, type ParsedRoomId, type Ping, type Pong, type PresenceRecord, type ProfileKind, ProfileLedger, type ProfileRow, type ProfileSnapshot, RELAY_HASH8, RETENTION_MAX_MS, RETENTION_MIN_MS, RETENTION_RE, ROOMS_PER_VCPU, ROOM_CLASS_MAX_MB, ROOM_ID_RE, ROOM_MEMORY_MB_MAX, ROOM_MEMORY_MB_MIN, ROOM_TYPE_DELIMITER, ROOM_TYPE_RE, type Reply, type RoomSizeClass, type RoomTypeSizing, SUPERVISOR_BASELINE_MIB, VM_MEM_MIB_MAX, VM_MEM_MIB_MIN, type VmCpuSizing, type VmSizing, type VoiceClientMessage, type VoicePeer, type VoiceServerMessage, type VoiceTransportInfo, type Welcome, YOUNG_GEN_MB, builtinRpcs, busChannelProblem, busPayloadProblem, correctPayload, declarationsFit, decodeCall, decodeErrorPayload, decodeFrame, decodeHello, decodeMsg, decodePing, decodePong, decodeReply, decodeVoiceMessage, decodeWelcome, deltaPayload, diffProfiles, encodeCall, encodeCorrectFrame, encodeDeltaFrame, encodeErrorPayload, encodeFrame, encodeHello, encodeMsg, encodePing, encodePong, encodeReply, encodeSchemaFrame, encodeVoiceMessage, encodeWelcome, encodeWriteFrame, errorByCode, formatError, formatRoomId, isFrameType, isLocalhostOrigin, isMailboxAlarm, isOutboxAlarm, isRelayHash8, isTypedMsg, isVoiceMsg, mergeProfiles, nativeReserveOf, originAllowed, parseOriginList, parseRetention, parseRoomId, presenceEntity, readCorrectAppliedTick, readCorrectClientTick, readSchemaPayload, relaySchema, requestOwnership, roomClassFor, roomClassOfSource, roomHoursSourceFor, rpcByIdOf, rpcIdOf, rpcTable, scaleProfile, schemaPayload, topProfileRows, typeFootprintMib, typedMsgFromClientOk, vmCpuFor, vmSizeFor, withBuiltins, writePayload };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/frame.ts
|
|
2
|
-
var PROTOCOL_VERSION =
|
|
2
|
+
var PROTOCOL_VERSION = 3;
|
|
3
3
|
var FrameType = {
|
|
4
4
|
HELLO: 1,
|
|
5
5
|
WELCOME: 2,
|
|
@@ -148,10 +148,32 @@ var ErrorCode = {
|
|
|
148
148
|
E_PLACEMENT: {
|
|
149
149
|
code: 33,
|
|
150
150
|
message: "no capacity in region {region}; add a server or raise maxVms"
|
|
151
|
+
},
|
|
152
|
+
// ---- M6 lane A: rooms API ----
|
|
153
|
+
/**
|
|
154
|
+
* M6 lane A (D68-d): an operator deleted this room, and its stored state is gone.
|
|
155
|
+
*
|
|
156
|
+
* Its own code rather than `E_KICKED` or `E_ROOM_CLOSED`, and the difference between the three is
|
|
157
|
+
* the reason it exists. `E_KICKED` is the room's own code deciding one player should leave, and
|
|
158
|
+
* a client's right answer is usually to show a message and stay in the game. `E_ROOM_CLOSED` is
|
|
159
|
+
* a room ending normally; rejoining makes a fresh one and nothing was lost. This is neither: the
|
|
160
|
+
* room's state has been deleted from outside the game, deliberately, by somebody with a rooms
|
|
161
|
+
* API credential. A client that reconnects gets an empty room rather than the one it was in.
|
|
162
|
+
* Different thing to tell a player, different thing for a client to do, different code.
|
|
163
|
+
*
|
|
164
|
+
* Fatal, which is load-bearing: `@irtio/client`'s fatal path leaves for good rather than
|
|
165
|
+
* reconnecting, and that is what stops a deleted room from being immediately recreated by the
|
|
166
|
+
* very clients that were in it. Close code {@link CLOSE_ROOM_DELETED}.
|
|
167
|
+
*/
|
|
168
|
+
E_ROOM_DELETED: {
|
|
169
|
+
code: 34,
|
|
170
|
+
message: "room {roomId} was deleted by an operator; its stored state is gone"
|
|
151
171
|
}
|
|
172
|
+
// ---- end M6 lane A ----
|
|
152
173
|
};
|
|
153
174
|
var CLOSE_TRY_AGAIN_LATER = 1013;
|
|
154
175
|
var CLOSE_EGRESS_WALL = 4290;
|
|
176
|
+
var CLOSE_ROOM_DELETED = 4291;
|
|
155
177
|
var ERROR_CATALOGUE = Object.keys(ErrorCode).map((name) => ({ name, code: ErrorCode[name].code, message: ErrorCode[name].message }));
|
|
156
178
|
var BY_CODE = new Map(
|
|
157
179
|
ERROR_CATALOGUE.map((e) => [e.code, e])
|
|
@@ -347,6 +369,7 @@ function encodeCall(c) {
|
|
|
347
369
|
const w = new ByteWriter2();
|
|
348
370
|
w.u32(c.reqId);
|
|
349
371
|
w.u16(c.rpcId);
|
|
372
|
+
w.u32(c.clientTick ?? 0);
|
|
350
373
|
w.bytes(c.params);
|
|
351
374
|
return w.finish();
|
|
352
375
|
}
|
|
@@ -354,8 +377,9 @@ function decodeCall(bytes) {
|
|
|
354
377
|
const r = new ByteReader2(bytes);
|
|
355
378
|
const reqId = r.u32();
|
|
356
379
|
const rpcId = r.u16();
|
|
380
|
+
const clientTick = r.u32();
|
|
357
381
|
const params = r.rest();
|
|
358
|
-
return { reqId, rpcId, params };
|
|
382
|
+
return { reqId, rpcId, clientTick, params };
|
|
359
383
|
}
|
|
360
384
|
function encodeReply(r) {
|
|
361
385
|
const w = new ByteWriter2();
|
|
@@ -434,6 +458,11 @@ function withBuiltins(schema) {
|
|
|
434
458
|
{
|
|
435
459
|
rpc: schema.rpc,
|
|
436
460
|
roles: schema.roles,
|
|
461
|
+
// D70: message shapes travel with the extension. The builder's hash is what HELLO carries
|
|
462
|
+
// and what a typed message is validated against, but the runtime reaches its schema
|
|
463
|
+
// through `ext` in several places, and an extension that quietly dropped `messages` would
|
|
464
|
+
// make a declared message decodable on one side of the room and not the other.
|
|
465
|
+
...schema.messageDefs !== void 0 && Object.keys(schema.messageDefs).length > 0 ? { messages: schema.messageDefs } : {},
|
|
437
466
|
...schema.project !== void 0 ? { project: schema.project } : {},
|
|
438
467
|
allowReservedNames: true
|
|
439
468
|
}
|
|
@@ -828,36 +857,87 @@ var MSG_KIND_CLIENT = 1;
|
|
|
828
857
|
var MSG_KIND_ROLE = 2;
|
|
829
858
|
var MSG_KIND_SERVER = 3;
|
|
830
859
|
var MSG_KIND_VOICE = 4;
|
|
860
|
+
var MSG_KIND_TYPED = 5;
|
|
831
861
|
function isVoiceMsg(payload) {
|
|
832
862
|
return payload.length > 0 && payload[0] === MSG_KIND_VOICE;
|
|
833
863
|
}
|
|
834
|
-
function
|
|
835
|
-
|
|
836
|
-
|
|
864
|
+
function isTypedMsg(payload) {
|
|
865
|
+
return payload.length > 0 && payload[0] === MSG_KIND_TYPED;
|
|
866
|
+
}
|
|
867
|
+
function typedMsgFromClientOk(payload) {
|
|
868
|
+
if (payload.length < 4 || payload[0] !== MSG_KIND_TYPED) return false;
|
|
869
|
+
const inner = payload[3];
|
|
870
|
+
return inner === MSG_KIND_ALL || inner === MSG_KIND_CLIENT || inner === MSG_KIND_ROLE;
|
|
871
|
+
}
|
|
872
|
+
function writeTarget(w, target) {
|
|
873
|
+
switch (target.kind) {
|
|
837
874
|
case "all":
|
|
838
875
|
w.u8(MSG_KIND_ALL);
|
|
839
|
-
|
|
876
|
+
return;
|
|
840
877
|
case "client":
|
|
841
878
|
w.u8(MSG_KIND_CLIENT);
|
|
842
|
-
w.str(
|
|
843
|
-
|
|
879
|
+
w.str(target.clientId);
|
|
880
|
+
return;
|
|
844
881
|
case "role":
|
|
845
882
|
w.u8(MSG_KIND_ROLE);
|
|
846
|
-
w.str(
|
|
847
|
-
|
|
883
|
+
w.str(target.role);
|
|
884
|
+
return;
|
|
848
885
|
case "server":
|
|
849
886
|
w.u8(MSG_KIND_SERVER);
|
|
850
|
-
|
|
887
|
+
return;
|
|
851
888
|
case "voice":
|
|
852
889
|
w.u8(MSG_KIND_VOICE);
|
|
853
|
-
|
|
890
|
+
return;
|
|
854
891
|
}
|
|
892
|
+
}
|
|
893
|
+
function readTarget(r) {
|
|
894
|
+
const kind = r.u8();
|
|
895
|
+
switch (kind) {
|
|
896
|
+
case MSG_KIND_ALL:
|
|
897
|
+
return { kind: "all" };
|
|
898
|
+
case MSG_KIND_CLIENT:
|
|
899
|
+
return { kind: "client", clientId: r.str() };
|
|
900
|
+
case MSG_KIND_ROLE:
|
|
901
|
+
return { kind: "role", role: r.str() };
|
|
902
|
+
case MSG_KIND_SERVER:
|
|
903
|
+
return { kind: "server" };
|
|
904
|
+
case MSG_KIND_VOICE:
|
|
905
|
+
return { kind: "voice" };
|
|
906
|
+
default:
|
|
907
|
+
throw new Error(`decodeMsg: unknown target discriminator ${kind}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
function encodeMsg(m) {
|
|
911
|
+
const w = new ByteWriter3();
|
|
912
|
+
if (m.typed) {
|
|
913
|
+
if (m.target.kind === "voice") {
|
|
914
|
+
throw new Error("encodeMsg: a typed message cannot address voice");
|
|
915
|
+
}
|
|
916
|
+
const index = m.typed.index;
|
|
917
|
+
if (!Number.isInteger(index) || index < 0 || index > 65535) {
|
|
918
|
+
throw new Error(`encodeMsg: typed message index out of range: ${String(index)}`);
|
|
919
|
+
}
|
|
920
|
+
w.u8(MSG_KIND_TYPED);
|
|
921
|
+
w.u16(index);
|
|
922
|
+
writeTarget(w, m.target);
|
|
923
|
+
w.bytes(m.payload);
|
|
924
|
+
return w.finish();
|
|
925
|
+
}
|
|
926
|
+
writeTarget(w, m.target);
|
|
855
927
|
w.bytes(m.payload);
|
|
856
928
|
return w.finish();
|
|
857
929
|
}
|
|
858
930
|
function decodeMsg(bytes) {
|
|
859
931
|
const r = new ByteReader4(bytes);
|
|
860
932
|
const kind = r.u8();
|
|
933
|
+
if (kind === MSG_KIND_TYPED) {
|
|
934
|
+
const index = r.u16();
|
|
935
|
+
const target2 = readTarget(r);
|
|
936
|
+
if (target2.kind === "voice") {
|
|
937
|
+
throw new Error("decodeMsg: a typed message cannot address voice");
|
|
938
|
+
}
|
|
939
|
+
return { target: target2, payload: r.rest(), typed: { index } };
|
|
940
|
+
}
|
|
861
941
|
let target;
|
|
862
942
|
switch (kind) {
|
|
863
943
|
case MSG_KIND_ALL:
|
|
@@ -1225,6 +1305,7 @@ export {
|
|
|
1225
1305
|
BUS_OUTBOX_PREFIX,
|
|
1226
1306
|
BUS_SHARD_STARTING,
|
|
1227
1307
|
CLOSE_EGRESS_WALL,
|
|
1308
|
+
CLOSE_ROOM_DELETED,
|
|
1228
1309
|
CLOSE_TRY_AGAIN_LATER,
|
|
1229
1310
|
CODE_ALPHABET,
|
|
1230
1311
|
CPU_QUOTA_PCT_MAX,
|
|
@@ -1239,6 +1320,7 @@ export {
|
|
|
1239
1320
|
HELLO_SCHEMA_SWAP_BIT,
|
|
1240
1321
|
MATCH_CODE_LENGTH,
|
|
1241
1322
|
MAX_AWAKE_MAX,
|
|
1323
|
+
MSG_KIND_TYPED,
|
|
1242
1324
|
NATIVE_RESERVE_FRACTION,
|
|
1243
1325
|
PHYSICS_HEADROOM_MIB,
|
|
1244
1326
|
PRESENCE_COLLECTION,
|
|
@@ -1299,6 +1381,7 @@ export {
|
|
|
1299
1381
|
isMailboxAlarm,
|
|
1300
1382
|
isOutboxAlarm,
|
|
1301
1383
|
isRelayHash8,
|
|
1384
|
+
isTypedMsg,
|
|
1302
1385
|
isVoiceMsg,
|
|
1303
1386
|
mergeProfiles,
|
|
1304
1387
|
nativeReserveOf,
|
|
@@ -1322,6 +1405,7 @@ export {
|
|
|
1322
1405
|
schemaPayload,
|
|
1323
1406
|
topProfileRows,
|
|
1324
1407
|
typeFootprintMib,
|
|
1408
|
+
typedMsgFromClientOk,
|
|
1325
1409
|
vmCpuFor,
|
|
1326
1410
|
vmSizeFor,
|
|
1327
1411
|
withBuiltins,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@irtio/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "irtio wire protocol: frames, framing, session payloads, error codes, built-in presence",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dist"
|
|
21
21
|
],
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@irtio/schema": "0.
|
|
23
|
+
"@irtio/schema": "0.7.0"
|
|
24
24
|
},
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup",
|