@crowdedkingdoms/crowdyjs 13.0.2 → 13.1.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/binary-relay.d.ts +69 -0
- package/dist/binary-relay.d.ts.map +1 -0
- package/dist/binary-relay.js +226 -0
- package/dist/binary-wire.d.ts +111 -0
- package/dist/binary-wire.d.ts.map +1 -0
- package/dist/binary-wire.js +446 -0
- package/dist/crowdy-client.d.ts +14 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/domains/udp.d.ts +6 -0
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +78 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/realtime.d.ts +40 -0
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +128 -0
- package/package.json +2 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buddy client wire-format codec for the binary realtime relay
|
|
3
|
+
* (`crowdy-relay-v1`).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the game-api's `UdpMessageParserService` byte-for-byte: uplink
|
|
6
|
+
* serializers build the complete client→Buddy datagram (spatial header +
|
|
7
|
+
* payload + HMAC + gameTokenId + sequence) and downlink parsing produces the
|
|
8
|
+
* same `__typename`-tagged notification objects the GraphQL
|
|
9
|
+
* `udpNotifications` subscription delivers, so handler code is transport
|
|
10
|
+
* agnostic.
|
|
11
|
+
*
|
|
12
|
+
* The per-message HMAC key is the 64-octet game token string itself — the
|
|
13
|
+
* same bearer token the SDK already holds — computed with WebCrypto
|
|
14
|
+
* (HMAC-SHA256). Buddy verifies it server-side; the relay does not.
|
|
15
|
+
*/
|
|
16
|
+
import { decodeBase64, encodeBase64 } from './utils.js';
|
|
17
|
+
export const RELAY_MAX_DATAGRAM_BYTES = 1232;
|
|
18
|
+
/** Wire opcodes (mirror of game-api / Buddy `UdpMessageType`). */
|
|
19
|
+
export const WireMessageType = {
|
|
20
|
+
MESSAGE_BUNDLE: 2,
|
|
21
|
+
GENERIC_ERROR_MESSAGE: 3,
|
|
22
|
+
CHANNEL_MESSAGE_REQUEST: 17,
|
|
23
|
+
CHANNEL_MESSAGE_NOTIFICATION: 18,
|
|
24
|
+
ACTOR_UPDATE_REQUEST_2: 128,
|
|
25
|
+
ACTOR_UPDATE_RESPONSE_2: 129,
|
|
26
|
+
ACTOR_UPDATE_NOTIFICATION_2: 130,
|
|
27
|
+
VOXEL_UPDATE_REQUEST_2: 131,
|
|
28
|
+
VOXEL_UPDATE_RESPONSE_2: 132,
|
|
29
|
+
VOXEL_UPDATE_NOTIFICATION_2: 133,
|
|
30
|
+
CLIENT_AUDIO_PACKET_2: 134,
|
|
31
|
+
CLIENT_AUDIO_NOTIFICATION_2: 135,
|
|
32
|
+
CLIENT_TEXT_PACKET_2: 136,
|
|
33
|
+
CLIENT_TEXT_NOTIFICATION_2: 137,
|
|
34
|
+
CLIENT_EVENT_NOTIFICATION_2: 138,
|
|
35
|
+
SERVER_EVENT_NOTIFICATION_2: 139,
|
|
36
|
+
SINGLE_ACTOR_MESSAGE: 142,
|
|
37
|
+
};
|
|
38
|
+
const SPATIAL_HEADER_SIZE = 68;
|
|
39
|
+
const UUID_SIZE = 32;
|
|
40
|
+
const HMAC_SIZE = 32;
|
|
41
|
+
/** Client→server tail with auth: hmac(32) + gameTokenId(8) + seq(1). */
|
|
42
|
+
const TAIL_AUTH_BYTES = 41;
|
|
43
|
+
/** Server→client tail: epochMillis(8) + seq(1). */
|
|
44
|
+
const TAIL_NO_AUTH_BYTES = 9;
|
|
45
|
+
/**
|
|
46
|
+
* Wire error byte → GraphQL `UdpErrorCode` enum name (mirror of the game-api
|
|
47
|
+
* `ErrorType` numeric enum, which GraphQL serializes by name).
|
|
48
|
+
*/
|
|
49
|
+
const UDP_ERROR_NAMES = {
|
|
50
|
+
0: 'NO_ERROR',
|
|
51
|
+
1: 'UNKNOWN_ERROR',
|
|
52
|
+
2: 'EMAIL_NOT_FOUND',
|
|
53
|
+
3: 'BAD_PASSWORD',
|
|
54
|
+
4: 'EMAIL_ALREADY_EXISTS',
|
|
55
|
+
5: 'INVALID_TOKEN',
|
|
56
|
+
6: 'APP_NOT_FOUND',
|
|
57
|
+
7: 'UNAUTHORIZED',
|
|
58
|
+
8: 'APP_NOT_LOADED',
|
|
59
|
+
9: 'EMAIL_TOO_SHORT',
|
|
60
|
+
10: 'EMAIL_TOO_LONG',
|
|
61
|
+
11: 'PASSWORD_TOO_SHORT',
|
|
62
|
+
12: 'PASSWORD_TOO_LONG',
|
|
63
|
+
13: 'GAME_TOKEN_WRONG_SIZE',
|
|
64
|
+
14: 'NAME_TOO_LONG',
|
|
65
|
+
15: 'INVALID_REQUEST',
|
|
66
|
+
16: 'EMAIL_INVALID',
|
|
67
|
+
17: 'INVALID_TOKEN_LENGTH',
|
|
68
|
+
18: 'INVALID_APP_ID',
|
|
69
|
+
19: 'CHUNK_NOT_FOUND',
|
|
70
|
+
20: 'USER_NOT_AUTHENTICATED',
|
|
71
|
+
21: 'INVALID_STATE_DATA',
|
|
72
|
+
22: 'USER_NOT_APP_ADMIN',
|
|
73
|
+
23: 'GRID_OUTSIDE_ASSIGNMENT',
|
|
74
|
+
24: 'NO_MATCHING_GRID_ASSIGNMENT',
|
|
75
|
+
25: 'INVALID_GRID_COORDINATES',
|
|
76
|
+
26: 'GRID_ALREADY_EXISTS',
|
|
77
|
+
27: 'GRID_OVERLAPS_EXISTING',
|
|
78
|
+
28: 'GAMERTAG_ALREADY_EXISTS',
|
|
79
|
+
29: 'GRID_NOT_FOUND',
|
|
80
|
+
30: 'CANNOT_DELETE_DEFAULT_WORLD_GRID',
|
|
81
|
+
31: 'GRID_HAS_NESTED_CHILDREN',
|
|
82
|
+
32: 'TOKEN_EXPIRED',
|
|
83
|
+
};
|
|
84
|
+
const textEncoder = new TextEncoder();
|
|
85
|
+
const textDecoder = new TextDecoder();
|
|
86
|
+
function latin1Bytes(value) {
|
|
87
|
+
const bytes = new Uint8Array(value.length);
|
|
88
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
89
|
+
bytes[i] = value.charCodeAt(i) & 0xff;
|
|
90
|
+
}
|
|
91
|
+
return bytes;
|
|
92
|
+
}
|
|
93
|
+
/** Import the game token as an HMAC-SHA256 WebCrypto key (once per session). */
|
|
94
|
+
export async function createSignContext(gameTokenId, gameTokenString) {
|
|
95
|
+
const tokenBytes = latin1Bytes(gameTokenString);
|
|
96
|
+
const key = await crypto.subtle.importKey('raw', tokenBytes, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
97
|
+
return { gameTokenId, tokenBytes, key };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Spatial-message HMAC: key = token bytes; signed data =
|
|
101
|
+
* `dataBeforeHmac || tokenBytes` (mirror of `computeSpatialHmac`).
|
|
102
|
+
*/
|
|
103
|
+
async function signWithToken(ctx, dataBeforeHmac) {
|
|
104
|
+
const signed = new Uint8Array(dataBeforeHmac.length + ctx.tokenBytes.length);
|
|
105
|
+
signed.set(dataBeforeHmac, 0);
|
|
106
|
+
signed.set(ctx.tokenBytes, dataBeforeHmac.length);
|
|
107
|
+
const mac = await crypto.subtle.sign('HMAC', ctx.key, signed);
|
|
108
|
+
return new Uint8Array(mac);
|
|
109
|
+
}
|
|
110
|
+
function clampDistance(distance) {
|
|
111
|
+
return Math.max(0, Math.min(8, distance));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Build a complete client→Buddy spatial datagram:
|
|
115
|
+
* `header(68) + payload + hmac(32) + gameTokenId(8) + seq(1)` with
|
|
116
|
+
* `containsAuth = 1` (mirror of `serializeSpatialMessage`).
|
|
117
|
+
*/
|
|
118
|
+
async function serializeSpatial(ctx, messageType, appId, chunk, distance, decayRate, uuid, payload, sequenceNumber) {
|
|
119
|
+
const dataLen = SPATIAL_HEADER_SIZE + payload.length;
|
|
120
|
+
const data = new Uint8Array(dataLen);
|
|
121
|
+
const view = new DataView(data.buffer);
|
|
122
|
+
let off = 0;
|
|
123
|
+
view.setUint8(off, messageType);
|
|
124
|
+
off += 1;
|
|
125
|
+
view.setBigInt64(off, BigInt(appId), true);
|
|
126
|
+
off += 8;
|
|
127
|
+
view.setBigInt64(off, BigInt(chunk.x), true);
|
|
128
|
+
off += 8;
|
|
129
|
+
view.setBigInt64(off, BigInt(chunk.y), true);
|
|
130
|
+
off += 8;
|
|
131
|
+
view.setBigInt64(off, BigInt(chunk.z), true);
|
|
132
|
+
off += 8;
|
|
133
|
+
view.setUint8(off, clampDistance(distance));
|
|
134
|
+
off += 1;
|
|
135
|
+
view.setUint8(off, decayRate & 0xff);
|
|
136
|
+
off += 1;
|
|
137
|
+
view.setUint8(off, 1); // containsAuth
|
|
138
|
+
off += 1;
|
|
139
|
+
const uuidBytes = textEncoder.encode(uuid);
|
|
140
|
+
if (uuidBytes.length > UUID_SIZE) {
|
|
141
|
+
throw new Error(`UUID too long: ${uuidBytes.length} > ${UUID_SIZE}`);
|
|
142
|
+
}
|
|
143
|
+
data.set(uuidBytes, off);
|
|
144
|
+
off += UUID_SIZE;
|
|
145
|
+
data.set(payload, off);
|
|
146
|
+
const hmac = await signWithToken(ctx, data);
|
|
147
|
+
const total = dataLen + TAIL_AUTH_BYTES;
|
|
148
|
+
if (total > RELAY_MAX_DATAGRAM_BYTES) {
|
|
149
|
+
throw new Error(`Datagram exceeds maximum size: ${total} > ${RELAY_MAX_DATAGRAM_BYTES}`);
|
|
150
|
+
}
|
|
151
|
+
const out = new Uint8Array(total);
|
|
152
|
+
out.set(data, 0);
|
|
153
|
+
out.set(hmac, dataLen);
|
|
154
|
+
const tailView = new DataView(out.buffer);
|
|
155
|
+
tailView.setBigInt64(dataLen + HMAC_SIZE, ctx.gameTokenId, true);
|
|
156
|
+
tailView.setUint8(dataLen + HMAC_SIZE + 8, sequenceNumber & 0xff);
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
export function serializeActorUpdate(ctx, input) {
|
|
160
|
+
return serializeSpatial(ctx, WireMessageType.ACTOR_UPDATE_REQUEST_2, input.appId, input.chunk, input.distance ?? 8, input.decayRate ?? 1, input.uuid, decodeBase64(input.state ?? ''), input.sequenceNumber ?? 0);
|
|
161
|
+
}
|
|
162
|
+
export function serializeVoxelUpdate(ctx, input) {
|
|
163
|
+
const voxelState = decodeBase64(input.voxelState ?? '');
|
|
164
|
+
const payload = new Uint8Array(10 + voxelState.length);
|
|
165
|
+
const view = new DataView(payload.buffer);
|
|
166
|
+
view.setInt16(0, input.voxel.x, true);
|
|
167
|
+
view.setInt16(2, input.voxel.y, true);
|
|
168
|
+
view.setInt16(4, input.voxel.z, true);
|
|
169
|
+
view.setInt16(6, input.voxelType, true);
|
|
170
|
+
view.setUint16(8, voxelState.length, true);
|
|
171
|
+
payload.set(voxelState, 10);
|
|
172
|
+
return serializeSpatial(ctx, WireMessageType.VOXEL_UPDATE_REQUEST_2, input.appId, input.chunk, input.distance ?? 8, input.decayRate ?? 0, input.uuid, payload, input.sequenceNumber ?? 0);
|
|
173
|
+
}
|
|
174
|
+
export function serializeAudioPacket(ctx, input) {
|
|
175
|
+
return serializeSpatial(ctx, WireMessageType.CLIENT_AUDIO_PACKET_2, input.appId, input.chunk, input.distance ?? 1, input.decayRate ?? 0, input.uuid, decodeBase64(input.audioData ?? ''), input.sequenceNumber ?? 0);
|
|
176
|
+
}
|
|
177
|
+
export function serializeTextPacket(ctx, input) {
|
|
178
|
+
return serializeSpatial(ctx, WireMessageType.CLIENT_TEXT_PACKET_2, input.appId, input.chunk, input.distance ?? 8, input.decayRate ?? 0, input.uuid, textEncoder.encode(input.text ?? ''), input.sequenceNumber ?? 0);
|
|
179
|
+
}
|
|
180
|
+
export function serializeClientEvent(ctx, input) {
|
|
181
|
+
const state = decodeBase64(input.state ?? '');
|
|
182
|
+
const payload = new Uint8Array(2 + state.length);
|
|
183
|
+
new DataView(payload.buffer).setUint16(0, input.eventType, true);
|
|
184
|
+
payload.set(state, 2);
|
|
185
|
+
return serializeSpatial(ctx, WireMessageType.CLIENT_EVENT_NOTIFICATION_2, input.appId, input.chunk, input.distance ?? 8, input.decayRate ?? 0, input.uuid, payload, input.sequenceNumber ?? 0);
|
|
186
|
+
}
|
|
187
|
+
export function serializeSingleActorMessage(ctx, input) {
|
|
188
|
+
return serializeSpatial(ctx, WireMessageType.SINGLE_ACTOR_MESSAGE, input.appId, input.chunk, 0, 0, input.targetUuid, input.payload ? decodeBase64(input.payload) : new Uint8Array(0), input.sequenceNumber ?? 0);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* CHANNEL_MESSAGE_REQUEST:
|
|
192
|
+
* `[1B type=17][8B channelId][32B uuid][2B payloadLen][payload][1B containsAuth]
|
|
193
|
+
* [32B HMAC][8B gameTokenId][1B seq]` — HMAC over everything before it.
|
|
194
|
+
*/
|
|
195
|
+
export async function serializeChannelMessage(ctx, input) {
|
|
196
|
+
const payload = input.payload ? decodeBase64(input.payload) : new Uint8Array(0);
|
|
197
|
+
if (payload.length > 1024) {
|
|
198
|
+
throw new Error(`Channel payload exceeds 1024 bytes: ${payload.length}`);
|
|
199
|
+
}
|
|
200
|
+
const uuidBytes = textEncoder.encode(input.uuid);
|
|
201
|
+
if (uuidBytes.length !== UUID_SIZE) {
|
|
202
|
+
throw new Error(`Invalid uuid length: must be exactly ${UUID_SIZE} bytes when UTF-8 encoded. Received ${uuidBytes.length} bytes.`);
|
|
203
|
+
}
|
|
204
|
+
const prefixLen = 1 + 8 + UUID_SIZE + 2 + payload.length + 1;
|
|
205
|
+
const prefix = new Uint8Array(prefixLen);
|
|
206
|
+
const view = new DataView(prefix.buffer);
|
|
207
|
+
let off = 0;
|
|
208
|
+
view.setUint8(off, WireMessageType.CHANNEL_MESSAGE_REQUEST);
|
|
209
|
+
off += 1;
|
|
210
|
+
view.setBigUint64(off, BigInt(input.channelId), true);
|
|
211
|
+
off += 8;
|
|
212
|
+
prefix.set(uuidBytes, off);
|
|
213
|
+
off += UUID_SIZE;
|
|
214
|
+
view.setUint16(off, payload.length, true);
|
|
215
|
+
off += 2;
|
|
216
|
+
prefix.set(payload, off);
|
|
217
|
+
off += payload.length;
|
|
218
|
+
view.setUint8(off, 1); // containsAuth
|
|
219
|
+
const hmac = await signWithToken(ctx, prefix);
|
|
220
|
+
const out = new Uint8Array(prefixLen + HMAC_SIZE + 8 + 1);
|
|
221
|
+
out.set(prefix, 0);
|
|
222
|
+
out.set(hmac, prefixLen);
|
|
223
|
+
const tail = new DataView(out.buffer);
|
|
224
|
+
tail.setBigUint64(prefixLen + HMAC_SIZE, ctx.gameTokenId, true);
|
|
225
|
+
tail.setUint8(prefixLen + HMAC_SIZE + 8, (input.sequenceNumber ?? 0) & 0xff);
|
|
226
|
+
return out;
|
|
227
|
+
}
|
|
228
|
+
function stripNulls(value) {
|
|
229
|
+
return value.replace(/\0/g, '');
|
|
230
|
+
}
|
|
231
|
+
function parseSpatialParts(bytes, view) {
|
|
232
|
+
if (bytes.length < SPATIAL_HEADER_SIZE) {
|
|
233
|
+
throw new Error(`Spatial message too short for header: ${bytes.length} < ${SPATIAL_HEADER_SIZE}`);
|
|
234
|
+
}
|
|
235
|
+
let off = 1;
|
|
236
|
+
const appId = view.getBigInt64(off, true);
|
|
237
|
+
off += 8;
|
|
238
|
+
const chunkX = view.getBigInt64(off, true);
|
|
239
|
+
off += 8;
|
|
240
|
+
const chunkY = view.getBigInt64(off, true);
|
|
241
|
+
off += 8;
|
|
242
|
+
const chunkZ = view.getBigInt64(off, true);
|
|
243
|
+
off += 8;
|
|
244
|
+
const distance = view.getUint8(off);
|
|
245
|
+
off += 1;
|
|
246
|
+
const decayRate = view.getUint8(off);
|
|
247
|
+
off += 1;
|
|
248
|
+
const containsAuth = view.getUint8(off) !== 0;
|
|
249
|
+
off += 1;
|
|
250
|
+
const uuid = stripNulls(textDecoder.decode(bytes.subarray(off, off + UUID_SIZE)));
|
|
251
|
+
off += UUID_SIZE;
|
|
252
|
+
const tailBytes = containsAuth ? TAIL_AUTH_BYTES : TAIL_NO_AUTH_BYTES;
|
|
253
|
+
let payload;
|
|
254
|
+
let sequenceNumber = 0;
|
|
255
|
+
let epochMillis = 0n;
|
|
256
|
+
if (bytes.length >= SPATIAL_HEADER_SIZE + tailBytes) {
|
|
257
|
+
const payloadEnd = bytes.length - tailBytes;
|
|
258
|
+
payload = bytes.subarray(off, payloadEnd);
|
|
259
|
+
let tOff = payloadEnd;
|
|
260
|
+
if (containsAuth)
|
|
261
|
+
tOff += HMAC_SIZE;
|
|
262
|
+
epochMillis = view.getBigInt64(tOff, true);
|
|
263
|
+
sequenceNumber = view.getUint8(tOff + 8);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
payload = bytes.subarray(off);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
appId: appId.toString(),
|
|
270
|
+
chunkX: chunkX.toString(),
|
|
271
|
+
chunkY: chunkY.toString(),
|
|
272
|
+
chunkZ: chunkZ.toString(),
|
|
273
|
+
distance,
|
|
274
|
+
decayRate,
|
|
275
|
+
uuid,
|
|
276
|
+
payload,
|
|
277
|
+
sequenceNumber,
|
|
278
|
+
epochMillis: epochMillis.toString(),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function spatialCommon(p) {
|
|
282
|
+
return {
|
|
283
|
+
appId: p.appId,
|
|
284
|
+
chunkX: p.chunkX,
|
|
285
|
+
chunkY: p.chunkY,
|
|
286
|
+
chunkZ: p.chunkZ,
|
|
287
|
+
distance: p.distance,
|
|
288
|
+
decayRate: p.decayRate,
|
|
289
|
+
uuid: p.uuid,
|
|
290
|
+
sequenceNumber: p.sequenceNumber,
|
|
291
|
+
epochMillis: p.epochMillis,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function parseOne(bytes) {
|
|
295
|
+
if (bytes.length < 1)
|
|
296
|
+
return null;
|
|
297
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
298
|
+
const messageType = bytes[0];
|
|
299
|
+
if (messageType === WireMessageType.GENERIC_ERROR_MESSAGE) {
|
|
300
|
+
if (bytes.length < 3)
|
|
301
|
+
return null;
|
|
302
|
+
return {
|
|
303
|
+
__typename: 'GenericErrorResponse',
|
|
304
|
+
sequenceNumber: view.getUint8(1),
|
|
305
|
+
errorCode: (UDP_ERROR_NAMES[view.getUint8(2)] ??
|
|
306
|
+
'UNKNOWN_ERROR'),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
if (messageType === WireMessageType.CHANNEL_MESSAGE_NOTIFICATION) {
|
|
310
|
+
// [1B type][8B channelId][32B senderUuid][2B payloadLen][payload][8B epochMillis][1B seq]
|
|
311
|
+
if (bytes.length < 1 + 8 + UUID_SIZE + 2)
|
|
312
|
+
return null;
|
|
313
|
+
let off = 1;
|
|
314
|
+
const channelId = view.getBigUint64(off, true);
|
|
315
|
+
off += 8;
|
|
316
|
+
const uuid = stripNulls(textDecoder.decode(bytes.subarray(off, off + UUID_SIZE)));
|
|
317
|
+
off += UUID_SIZE;
|
|
318
|
+
const payloadLen = view.getUint16(off, true);
|
|
319
|
+
off += 2;
|
|
320
|
+
if (bytes.length < off + payloadLen + 8 + 1)
|
|
321
|
+
return null;
|
|
322
|
+
const payload = bytes.subarray(off, off + payloadLen);
|
|
323
|
+
off += payloadLen;
|
|
324
|
+
const epochMillis = view.getBigUint64(off, true);
|
|
325
|
+
off += 8;
|
|
326
|
+
const sequenceNumber = view.getUint8(off);
|
|
327
|
+
return {
|
|
328
|
+
__typename: 'ChannelMessageNotification',
|
|
329
|
+
channelId: channelId.toString(),
|
|
330
|
+
uuid,
|
|
331
|
+
payload: encodeBase64(payload),
|
|
332
|
+
sequenceNumber,
|
|
333
|
+
epochMillis: epochMillis.toString(),
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if ((messageType & 0x80) === 0) {
|
|
337
|
+
return null; // control opcodes (COMMAND_RECONNECT etc.) never reach the client
|
|
338
|
+
}
|
|
339
|
+
const p = parseSpatialParts(bytes, view);
|
|
340
|
+
switch (messageType) {
|
|
341
|
+
case WireMessageType.ACTOR_UPDATE_RESPONSE_2:
|
|
342
|
+
return {
|
|
343
|
+
__typename: 'ActorUpdateResponse',
|
|
344
|
+
...spatialCommon(p),
|
|
345
|
+
};
|
|
346
|
+
case WireMessageType.ACTOR_UPDATE_NOTIFICATION_2:
|
|
347
|
+
return {
|
|
348
|
+
__typename: 'ActorUpdateNotification',
|
|
349
|
+
...spatialCommon(p),
|
|
350
|
+
state: encodeBase64(p.payload),
|
|
351
|
+
};
|
|
352
|
+
case WireMessageType.VOXEL_UPDATE_RESPONSE_2:
|
|
353
|
+
return {
|
|
354
|
+
__typename: 'VoxelUpdateResponse',
|
|
355
|
+
...spatialCommon(p),
|
|
356
|
+
};
|
|
357
|
+
case WireMessageType.VOXEL_UPDATE_NOTIFICATION_2: {
|
|
358
|
+
if (p.payload.length < 10)
|
|
359
|
+
return null;
|
|
360
|
+
const pv = new DataView(p.payload.buffer, p.payload.byteOffset, p.payload.byteLength);
|
|
361
|
+
const stateLength = pv.getUint16(8, true);
|
|
362
|
+
return {
|
|
363
|
+
__typename: 'VoxelUpdateNotification',
|
|
364
|
+
...spatialCommon(p),
|
|
365
|
+
voxelX: pv.getInt16(0, true),
|
|
366
|
+
voxelY: pv.getInt16(2, true),
|
|
367
|
+
voxelZ: pv.getInt16(4, true),
|
|
368
|
+
voxelType: pv.getInt16(6, true),
|
|
369
|
+
voxelState: encodeBase64(p.payload.subarray(10, 10 + stateLength)),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
case WireMessageType.CLIENT_AUDIO_NOTIFICATION_2:
|
|
373
|
+
return {
|
|
374
|
+
__typename: 'ClientAudioNotification',
|
|
375
|
+
...spatialCommon(p),
|
|
376
|
+
audioData: encodeBase64(p.payload),
|
|
377
|
+
};
|
|
378
|
+
case WireMessageType.CLIENT_TEXT_NOTIFICATION_2:
|
|
379
|
+
return {
|
|
380
|
+
__typename: 'ClientTextNotification',
|
|
381
|
+
...spatialCommon(p),
|
|
382
|
+
text: stripNulls(textDecoder.decode(p.payload)),
|
|
383
|
+
};
|
|
384
|
+
case WireMessageType.CLIENT_EVENT_NOTIFICATION_2:
|
|
385
|
+
case WireMessageType.SERVER_EVENT_NOTIFICATION_2: {
|
|
386
|
+
if (p.payload.length < 2)
|
|
387
|
+
return null;
|
|
388
|
+
const pv = new DataView(p.payload.buffer, p.payload.byteOffset, p.payload.byteLength);
|
|
389
|
+
return {
|
|
390
|
+
__typename: messageType === WireMessageType.CLIENT_EVENT_NOTIFICATION_2
|
|
391
|
+
? 'ClientEventNotification'
|
|
392
|
+
: 'ServerEventNotification',
|
|
393
|
+
...spatialCommon(p),
|
|
394
|
+
eventType: pv.getUint16(0, true),
|
|
395
|
+
state: encodeBase64(p.payload.subarray(2)),
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
case WireMessageType.SINGLE_ACTOR_MESSAGE: {
|
|
399
|
+
const { distance: _d, decayRate: _r, ...rest } = spatialCommon(p);
|
|
400
|
+
return {
|
|
401
|
+
__typename: 'SingleActorMessageNotification',
|
|
402
|
+
...rest,
|
|
403
|
+
payload: encodeBase64(p.payload),
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
default:
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Parse one relay BINARY frame (one Buddy datagram, possibly a
|
|
412
|
+
* MESSAGE_BUNDLE) into notification objects. Unparseable inner messages are
|
|
413
|
+
* skipped, mirroring the server-side bundle behavior.
|
|
414
|
+
*/
|
|
415
|
+
export function parseRelayFrame(bytes) {
|
|
416
|
+
if (bytes.length < 1)
|
|
417
|
+
return [];
|
|
418
|
+
if (bytes[0] === WireMessageType.MESSAGE_BUNDLE) {
|
|
419
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
420
|
+
const out = [];
|
|
421
|
+
let off = 1;
|
|
422
|
+
while (off + 2 <= bytes.length) {
|
|
423
|
+
const len = view.getUint16(off, true);
|
|
424
|
+
off += 2;
|
|
425
|
+
if (off + len > bytes.length)
|
|
426
|
+
break;
|
|
427
|
+
try {
|
|
428
|
+
const parsed = parseOne(bytes.subarray(off, off + len));
|
|
429
|
+
if (parsed)
|
|
430
|
+
out.push(parsed);
|
|
431
|
+
}
|
|
432
|
+
catch {
|
|
433
|
+
// skip unparseable bundle member
|
|
434
|
+
}
|
|
435
|
+
off += len;
|
|
436
|
+
}
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
const parsed = parseOne(bytes);
|
|
441
|
+
return parsed ? [parsed] : [];
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
}
|
package/dist/crowdy-client.d.ts
CHANGED
|
@@ -114,6 +114,20 @@ export interface CrowdyClientConfig {
|
|
|
114
114
|
* instead of HTTP POST (requires active `udp.subscribe`).
|
|
115
115
|
*/
|
|
116
116
|
wsUplinkMutations?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Use the game-api's binary realtime relay (`crowdy-relay-v1`): raw
|
|
119
|
+
* WebSocket BINARY frames carrying complete client-signed Buddy wire
|
|
120
|
+
* datagrams both ways, bypassing the GraphQL hot path. All `udp.*` /
|
|
121
|
+
* `world()` / stores APIs behave identically; the SDK falls back to the
|
|
122
|
+
* GraphQL transport when the relay endpoint is unavailable. Check server
|
|
123
|
+
* support via `gameClientBootstrap.binaryRelayEnabled`.
|
|
124
|
+
*/
|
|
125
|
+
binaryTransport?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Absolute ws(s) URL of the binary relay endpoint. Defaults to the
|
|
128
|
+
* realtime WebSocket URL with its path replaced by `/realtime`.
|
|
129
|
+
*/
|
|
130
|
+
binaryRelayUrl?: string;
|
|
117
131
|
};
|
|
118
132
|
/**
|
|
119
133
|
* Optional sticky-LB cookie jar shared with the game-api HTTP client. Node
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crowdy-client.d.ts","sourceRoot":"","sources":["../src/crowdy-client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"crowdy-client.d.ts","sourceRoot":"","sources":["../src/crowdy-client.ts"],"names":[],"mappings":"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"}
|
package/dist/domains/udp.d.ts
CHANGED
|
@@ -44,6 +44,12 @@ export declare class UdpAPI {
|
|
|
44
44
|
constructor(gql: GraphQLClient, subs: SubscriptionManager, metrics?: RealtimeMetrics | undefined);
|
|
45
45
|
/** Count a message accepted for sending (see `client.metrics`). */
|
|
46
46
|
private record;
|
|
47
|
+
/**
|
|
48
|
+
* Try to send over the binary relay (`crowdy-relay-v1`). Returns `null`
|
|
49
|
+
* when the relay is not active/ready so the caller falls back to the
|
|
50
|
+
* GraphQL mutation. Client-side serialize + HMAC; no GraphQL on this path.
|
|
51
|
+
*/
|
|
52
|
+
private sendViaRelay;
|
|
47
53
|
/**
|
|
48
54
|
* Open (or re-fetch) the UDP proxy session for this game token. Idempotent:
|
|
49
55
|
* if a session is already open it returns the existing status; on first open
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"udp.d.ts","sourceRoot":"","sources":["../../src/domains/udp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EACV,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAEL,KAAK,uBAAuB,EAG5B,KAAK,6BAA6B,EAElC,KAAK,gCAAgC,EAErC,KAAK,gCAAgC,EAErC,KAAK,gCAAgC,EAErC,KAAK,+BAA+B,EAEpC,KAAK,gCAAgC,EAErC,KAAK,uCAAuC,EAE5C,KAAK,mCAAmC,EACzC,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"udp.d.ts","sourceRoot":"","sources":["../../src/domains/udp.ts"],"names":[],"mappings":"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"}
|
package/dist/domains/udp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ConnectUdpProxyDocument, DisconnectUdpProxyDocument, UdpProxyConnectionStatusDocument, SendActorUpdateDocument, SendVoxelUpdateDocument, SendAudioPacketDocument, SendTextPacketDocument, SendClientEventDocument, SendSingleActorMessageDocument, SendChannelMessageDocument, } from '../generated/graphql.js';
|
|
2
2
|
import { payloadBytesOf } from '../metrics.js';
|
|
3
3
|
import { SequenceAllocator } from '../utils.js';
|
|
4
|
+
import { serializeActorUpdate, serializeAudioPacket, serializeChannelMessage, serializeClientEvent, serializeSingleActorMessage, serializeTextPacket, serializeVoxelUpdate, } from '../binary-wire.js';
|
|
4
5
|
/**
|
|
5
6
|
* UDP proxy access for browser-style clients that can't open raw UDP sockets.
|
|
6
7
|
* Exposed as `client.udp`; for game loops prefer the ergonomic, app-scoped
|
|
@@ -45,6 +46,31 @@ export class UdpAPI {
|
|
|
45
46
|
record(kind, input) {
|
|
46
47
|
this.metrics?.recordSent(kind, payloadBytesOf(input));
|
|
47
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* Try to send over the binary relay (`crowdy-relay-v1`). Returns `null`
|
|
51
|
+
* when the relay is not active/ready so the caller falls back to the
|
|
52
|
+
* GraphQL mutation. Client-side serialize + HMAC; no GraphQL on this path.
|
|
53
|
+
*/
|
|
54
|
+
async sendViaRelay(serialize) {
|
|
55
|
+
if (!this.subs.usingBinaryTransport())
|
|
56
|
+
return null;
|
|
57
|
+
if (!this.subs.binarySendReady()) {
|
|
58
|
+
// Relay configured but not open yet (e.g. right after subscribe or a
|
|
59
|
+
// token rotation). Wait briefly rather than falling back to HTTP — an
|
|
60
|
+
// HTTP send would open a parallel GraphQL proxy session for the same
|
|
61
|
+
// token whose idle teardown deauthorizes it under the relay session.
|
|
62
|
+
const ready = await this.subs.ensureBinaryReady(2000);
|
|
63
|
+
if (!ready)
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
return await this.subs.sendBinaryFrame(serialize);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Relay hiccup (socket flapped mid-send) — let GraphQL carry this one.
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
48
74
|
/**
|
|
49
75
|
* Open (or re-fetch) the UDP proxy session for this game token. Idempotent:
|
|
50
76
|
* if a session is already open it returns the existing status; on first open
|
|
@@ -60,6 +86,19 @@ export class UdpAPI {
|
|
|
60
86
|
* valid bearer game token.
|
|
61
87
|
*/
|
|
62
88
|
async connect() {
|
|
89
|
+
if (this.subs.usingBinaryTransport()) {
|
|
90
|
+
// Binary relay mode: the relay session (opened by subscribe) IS the
|
|
91
|
+
// proxy session. Never open a parallel GraphQL proxy session for the
|
|
92
|
+
// same token — its idle teardown would deauthorize the token on the
|
|
93
|
+
// game server underneath the relay.
|
|
94
|
+
const ready = await this.subs.ensureBinaryReady();
|
|
95
|
+
return {
|
|
96
|
+
connected: ready,
|
|
97
|
+
serverIp6: null,
|
|
98
|
+
serverClientPort: null,
|
|
99
|
+
lastMessageTime: null,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
63
102
|
const data = await this.gql.request(ConnectUdpProxyDocument, undefined);
|
|
64
103
|
return data.connectUdpProxy;
|
|
65
104
|
}
|
|
@@ -73,6 +112,10 @@ export class UdpAPI {
|
|
|
73
112
|
* @throws {CrowdyGraphQLError} on auth failures.
|
|
74
113
|
*/
|
|
75
114
|
async disconnect() {
|
|
115
|
+
if (this.subs.usingBinaryTransport()) {
|
|
116
|
+
this.subs.disconnect();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
76
119
|
const data = await this.gql.request(DisconnectUdpProxyDocument, undefined);
|
|
77
120
|
return data.disconnectUdpProxy;
|
|
78
121
|
}
|
|
@@ -120,6 +163,11 @@ export class UdpAPI {
|
|
|
120
163
|
* token, or `BAD_USER_INPUT` for a malformed packet.
|
|
121
164
|
*/
|
|
122
165
|
async sendActorUpdate(input) {
|
|
166
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeActorUpdate(ctx, input));
|
|
167
|
+
if (viaRelay !== null) {
|
|
168
|
+
this.record('actorUpdate', input);
|
|
169
|
+
return viaRelay;
|
|
170
|
+
}
|
|
123
171
|
if (this.subs.wsUplinkReady()) {
|
|
124
172
|
try {
|
|
125
173
|
const data = await this.subs.executeMutation(SendActorUpdateDocument, {
|
|
@@ -219,6 +267,11 @@ export class UdpAPI {
|
|
|
219
267
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
220
268
|
*/
|
|
221
269
|
async sendVoxelUpdate(input) {
|
|
270
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeVoxelUpdate(ctx, input));
|
|
271
|
+
if (viaRelay !== null) {
|
|
272
|
+
this.record('voxelUpdate', input);
|
|
273
|
+
return viaRelay;
|
|
274
|
+
}
|
|
222
275
|
const data = await this.gql.request(SendVoxelUpdateDocument, { input });
|
|
223
276
|
this.record('voxelUpdate', input);
|
|
224
277
|
return data.sendVoxelUpdate;
|
|
@@ -272,6 +325,11 @@ export class UdpAPI {
|
|
|
272
325
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
273
326
|
*/
|
|
274
327
|
async sendAudioPacket(input) {
|
|
328
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeAudioPacket(ctx, input));
|
|
329
|
+
if (viaRelay !== null) {
|
|
330
|
+
this.record('audio', input);
|
|
331
|
+
return viaRelay;
|
|
332
|
+
}
|
|
275
333
|
const data = await this.gql.request(SendAudioPacketDocument, { input });
|
|
276
334
|
this.record('audio', input);
|
|
277
335
|
return data.sendAudioPacket;
|
|
@@ -321,6 +379,11 @@ export class UdpAPI {
|
|
|
321
379
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
322
380
|
*/
|
|
323
381
|
async sendTextPacket(input) {
|
|
382
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeTextPacket(ctx, input));
|
|
383
|
+
if (viaRelay !== null) {
|
|
384
|
+
this.record('text', input);
|
|
385
|
+
return viaRelay;
|
|
386
|
+
}
|
|
324
387
|
const data = await this.gql.request(SendTextPacketDocument, { input });
|
|
325
388
|
this.record('text', input);
|
|
326
389
|
return data.sendTextPacket;
|
|
@@ -374,6 +437,11 @@ export class UdpAPI {
|
|
|
374
437
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
375
438
|
*/
|
|
376
439
|
async sendClientEvent(input) {
|
|
440
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeClientEvent(ctx, input));
|
|
441
|
+
if (viaRelay !== null) {
|
|
442
|
+
this.record('clientEvent', input);
|
|
443
|
+
return viaRelay;
|
|
444
|
+
}
|
|
377
445
|
const data = await this.gql.request(SendClientEventDocument, { input });
|
|
378
446
|
this.record('clientEvent', input);
|
|
379
447
|
return data.sendClientEvent;
|
|
@@ -425,6 +493,11 @@ export class UdpAPI {
|
|
|
425
493
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
426
494
|
*/
|
|
427
495
|
async sendSingleActorMessage(input) {
|
|
496
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeSingleActorMessage(ctx, input));
|
|
497
|
+
if (viaRelay !== null) {
|
|
498
|
+
this.record('singleActorMessage', input);
|
|
499
|
+
return viaRelay;
|
|
500
|
+
}
|
|
428
501
|
const data = await this.gql.request(SendSingleActorMessageDocument, {
|
|
429
502
|
input,
|
|
430
503
|
});
|
|
@@ -452,6 +525,11 @@ export class UdpAPI {
|
|
|
452
525
|
* @throws {CrowdyGraphQLError} on auth/validation failures.
|
|
453
526
|
*/
|
|
454
527
|
async sendChannelMessage(input) {
|
|
528
|
+
const viaRelay = await this.sendViaRelay((ctx) => serializeChannelMessage(ctx, input));
|
|
529
|
+
if (viaRelay !== null) {
|
|
530
|
+
this.record('channelMessage', input);
|
|
531
|
+
return viaRelay;
|
|
532
|
+
}
|
|
455
533
|
const data = await this.gql.request(SendChannelMessageDocument, { input });
|
|
456
534
|
this.record('channelMessage', input);
|
|
457
535
|
return data.sendChannelMessage;
|