@anvil-js/client 0.0.1

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.
@@ -0,0 +1,608 @@
1
+ import { v4 } from 'uuid';
2
+ import { useEffect, useState } from 'react';
3
+
4
+ // src/ws/codec.ts
5
+ var ConnectionCodec = class {
6
+ /** typeId → packetName (frames we receive) */
7
+ incoming = /* @__PURE__ */ new Map();
8
+ /** packetName → typeId (frames we send) */
9
+ outgoing = /* @__PURE__ */ new Map();
10
+ nextOutgoingTypeId = 100;
11
+ constructor() {
12
+ this.incoming.set(0, "connection.ConnectionRegisterPacketTypeIdPacket");
13
+ this.outgoing.set("connection.ConnectionRegisterPacketTypeIdPacket", 0);
14
+ }
15
+ /**
16
+ * Decode a single binary frame. Returns null for registration
17
+ * frames (which mutate the codec in place) and for unknown
18
+ * typeIds.
19
+ */
20
+ decode(buffer) {
21
+ const view = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
22
+ if (view.byteLength < 4) return null;
23
+ const dv = new DataView(view.buffer, view.byteOffset, view.byteLength);
24
+ let off = 0;
25
+ const typeId = dv.getInt32(off);
26
+ off += 4;
27
+ const name = this.incoming.get(typeId);
28
+ if (!name) return null;
29
+ const idLen = dv.getInt32(off);
30
+ off += 4;
31
+ const id = new TextDecoder("utf-8").decode(view.subarray(off, off + idLen));
32
+ off += idLen;
33
+ const payloadLen = dv.getInt32(off);
34
+ off += 4;
35
+ const payloadJson = new TextDecoder("utf-8").decode(view.subarray(off, off + payloadLen));
36
+ if (name === "connection.ConnectionRegisterPacketTypeIdPacket") {
37
+ const reg = JSON.parse(payloadJson);
38
+ this.incoming.set(reg.b, reg.a);
39
+ return null;
40
+ }
41
+ return { type: name, id, payload: JSON.parse(payloadJson) };
42
+ }
43
+ /**
44
+ * Encode a packet to a binary frame. If the packet name has
45
+ * not been registered yet, a fresh typeId is allocated and
46
+ * a registration frame is yielded first.
47
+ */
48
+ encode(name, payload, id = v4()) {
49
+ const frames = [];
50
+ if (!this.outgoing.has(name)) {
51
+ this.outgoing.set(name, this.nextOutgoingTypeId++);
52
+ frames.push(
53
+ ...this.encode(
54
+ "connection.ConnectionRegisterPacketTypeIdPacket",
55
+ { a: name, b: this.outgoing.get(name) },
56
+ ""
57
+ )
58
+ );
59
+ }
60
+ const typeId = this.outgoing.get(name);
61
+ const payloadBytes = new TextEncoder().encode(JSON.stringify(payload));
62
+ const idBytes = id ? new TextEncoder().encode(id) : new Uint8Array(0);
63
+ const total = 4 + 4 + idBytes.length + 4 + payloadBytes.length;
64
+ const buf = new Uint8Array(total);
65
+ const dv = new DataView(buf.buffer);
66
+ let off = 0;
67
+ dv.setInt32(off, typeId);
68
+ off += 4;
69
+ dv.setInt32(off, idBytes.length);
70
+ off += 4;
71
+ buf.set(idBytes, off);
72
+ off += idBytes.length;
73
+ dv.setInt32(off, payloadBytes.length);
74
+ off += 4;
75
+ buf.set(payloadBytes, off);
76
+ frames.push(buf);
77
+ return frames;
78
+ }
79
+ /** Register a packet type up-front (returns the typeId assigned). */
80
+ registerOutgoing(name) {
81
+ if (!this.outgoing.has(name)) {
82
+ this.outgoing.set(name, this.nextOutgoingTypeId++);
83
+ }
84
+ return this.outgoing.get(name);
85
+ }
86
+ };
87
+ var AnvilWsClient = class {
88
+ ws = null;
89
+ codec = new ConnectionCodec();
90
+ config;
91
+ listeners = /* @__PURE__ */ new Map();
92
+ pendingRequests = /* @__PURE__ */ new Map();
93
+ state = "disconnected";
94
+ bootstrapReceived = false;
95
+ reconnectTimer = null;
96
+ defaultRequestTimeoutMs = 1e4;
97
+ constructor(config) {
98
+ this.config = {
99
+ maxProtocolVersion: 9,
100
+ autoReconnect: true,
101
+ reconnectDelayMs: 2e3,
102
+ authenticationToken: "",
103
+ ...config
104
+ };
105
+ }
106
+ // ----- Lifecycle -----
107
+ connect() {
108
+ return new Promise((resolve, reject) => {
109
+ if (this.state === "ready" || this.state === "connected") {
110
+ resolve();
111
+ return;
112
+ }
113
+ this.state = "connecting";
114
+ const params = new URLSearchParams();
115
+ params.set("essential-user-uuid", this.config.userUuid);
116
+ params.set("essential-user-name", this.config.userName);
117
+ params.set("essential-max-protocol-version", String(this.config.maxProtocolVersion));
118
+ if (this.config.authenticationToken) {
119
+ params.set("essential-authentication-token", this.config.authenticationToken);
120
+ }
121
+ const url = `${this.config.url}?${params.toString()}`;
122
+ this.ws = new WebSocket(url);
123
+ this.ws.binaryType = "arraybuffer";
124
+ this.ws.onopen = () => {
125
+ this.state = "connected";
126
+ this.emit("open", void 0);
127
+ this.preRegisterOutgoing();
128
+ resolve();
129
+ };
130
+ this.ws.onerror = (e) => {
131
+ this.emit("error", new Error("WebSocket error"));
132
+ reject(new Error("WebSocket error"));
133
+ };
134
+ this.ws.onclose = (ev) => {
135
+ this.state = "disconnected";
136
+ this.emit("close", { code: ev.code, reason: ev.reason });
137
+ for (const [, req] of this.pendingRequests) {
138
+ clearTimeout(req.timer);
139
+ req.reject(new Error("Connection closed"));
140
+ }
141
+ this.pendingRequests.clear();
142
+ if (this.config.autoReconnect && this.bootstrapReceived) {
143
+ this.scheduleReconnect();
144
+ }
145
+ };
146
+ this.ws.onmessage = (ev) => {
147
+ this.handleFrame(ev.data);
148
+ };
149
+ });
150
+ }
151
+ disconnect() {
152
+ this.config.autoReconnect = false;
153
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
154
+ if (this.ws) {
155
+ this.ws.close(1e3, "Client disconnect");
156
+ this.ws = null;
157
+ }
158
+ }
159
+ scheduleReconnect() {
160
+ if (this.reconnectTimer) return;
161
+ this.reconnectTimer = setTimeout(() => {
162
+ this.reconnectTimer = null;
163
+ this.connect().catch(() => {
164
+ });
165
+ }, this.config.reconnectDelayMs);
166
+ }
167
+ preRegisterOutgoing() {
168
+ const TYPES = [
169
+ "chat.ClientChatChannelMessageCreatePacket",
170
+ "chat.ClientChatChannelMessagesRetrievePacket",
171
+ "chat.ClientChatChannelCreatePacket",
172
+ "chat.ClientChatChannelMessageUpdatePacket",
173
+ "chat.ChatChannelMessageDeletePacket",
174
+ "chat.ClientChatChannelMessageReportPacket",
175
+ "chat.ChatChannelMemberAddPacket",
176
+ "chat.ChatChannelMemberRemovePacket",
177
+ "chat.ClientChatChannelMutePacket",
178
+ "chat.ClientChatChannelReadStatePacket",
179
+ "chat.ClientChatChannelMessageReadStatePacket",
180
+ "relationships.ClientRelationshipCreatePacket",
181
+ "relationships.RelationshipDeletePacket",
182
+ "relationships.ClientLookupUuidByNamePacket",
183
+ "profile.ClientProfileRequestPacket",
184
+ "profile.ClientProfileActivityPacket",
185
+ "cosmetic.ClientCosmeticRequestPacket",
186
+ "cosmetic.ClientCosmeticBulkRequestUnlockStatePacket",
187
+ "cosmetic.ClientCosmeticAnimationTriggerPacket",
188
+ "skin.ClientSkinCreatePacket",
189
+ "skin.ClientSkinUpdateLastUsedStatePacket",
190
+ "skin.ClientSkinUpdateFavoriteStatePacket",
191
+ "skin.ClientSelectedSkinsRequestPacket",
192
+ "cosmetic.outfit.ClientCosmeticOutfitCreatePacket",
193
+ "cosmetic.outfit.ClientCosmeticOutfitSelectPacket",
194
+ "cosmetic.outfit.ClientCosmeticOutfitEquippedCosmeticsUpdatePacket",
195
+ "cosmetic.emote.ClientCosmeticEmoteWheelUpdatePacket",
196
+ "cosmetic.emote.ClientCosmeticEmoteWheelSelectPacket",
197
+ "wardrobe.ClientWardrobeSettingsPacket",
198
+ "checkout.ClientCheckoutCosmeticsPacket",
199
+ "coins.ClientCoinsBalancePacket",
200
+ "notices.ClientNoticeRequestPacket",
201
+ "serverdiscovery.ClientServerDiscoveryRequestPacket",
202
+ "knownservers.ClientKnownServersRequestPacket",
203
+ "multiplayer.ClientMultiplayerIceRelayPacket",
204
+ "pingproxy.ClientPingProxyRequestPacket",
205
+ "social.ClientCommunityRulesAgreedPacket"
206
+ ];
207
+ for (const t of TYPES) this.codec.registerOutgoing(t);
208
+ }
209
+ // ----- Frame handling -----
210
+ handleFrame(data) {
211
+ const packet = this.codec.decode(data);
212
+ if (!packet) return;
213
+ this.emit("raw", packet);
214
+ this.dispatch(packet);
215
+ }
216
+ dispatch(packet) {
217
+ if (packet.id && this.pendingRequests.has(packet.id)) {
218
+ const req = this.pendingRequests.get(packet.id);
219
+ this.pendingRequests.delete(packet.id);
220
+ clearTimeout(req.timer);
221
+ req.resolve(packet);
222
+ return;
223
+ }
224
+ if (!this.bootstrapReceived && packet.type === "social.ServerCommunityRulesStatePacket") {
225
+ this.bootstrapReceived = true;
226
+ this.state = "ready";
227
+ this.emit("bootstrap", void 0);
228
+ }
229
+ switch (packet.type) {
230
+ case "chat.ServerChatChannelMessagePacket": {
231
+ const msgs = packet.payload.a || [];
232
+ for (const m of msgs) {
233
+ this.emit("chat", {
234
+ id: m.a,
235
+ channelId: m.b ?? 0,
236
+ senderUuid: m.c,
237
+ content: m.d,
238
+ replyToId: m.e ?? null,
239
+ editedAt: m.f ?? null,
240
+ deleted: m.g ?? false,
241
+ createdAt: m.h ?? Date.now()
242
+ });
243
+ }
244
+ break;
245
+ }
246
+ case "relationships.ServerRelationshipPopulatePacket": {
247
+ const rels = packet.payload.a || [];
248
+ for (const r of rels) {
249
+ this.emit("friendAdded", {
250
+ userA: r.a,
251
+ userB: r.b,
252
+ type: r.c,
253
+ status: r.d,
254
+ since: r.e
255
+ });
256
+ }
257
+ break;
258
+ }
259
+ case "relationships.ServerRelationshipDeletedPacket": {
260
+ const dels = packet.payload.a || [];
261
+ for (const d of dels) {
262
+ this.emit("friendRemoved", { userA: d.a, userB: d.b, type: d.c });
263
+ }
264
+ break;
265
+ }
266
+ case "profile.ServerProfileStatusPacket": {
267
+ this.emit("profileStatus", {
268
+ uuid: packet.payload.a,
269
+ status: packet.payload.b,
270
+ lastOnlineTimestamp: packet.payload.lastOnlineTimestamp ?? 0
271
+ });
272
+ break;
273
+ }
274
+ case "profile.ServerProfileActivityPacket": {
275
+ this.emit("profileActivity", {
276
+ uuid: packet.payload.a,
277
+ activity: packet.payload.b,
278
+ metadata: packet.payload.c
279
+ });
280
+ break;
281
+ }
282
+ case "cosmetic.ServerCosmeticAnimationTriggerPacket": {
283
+ this.emit("cosmeticAnimation", {
284
+ userUuid: packet.payload.a,
285
+ cosmeticId: packet.payload.b,
286
+ animationId: packet.payload.c
287
+ });
288
+ break;
289
+ }
290
+ case "notices.ServerNoticePopulatePacket": {
291
+ const items = packet.payload.items || packet.payload.a || [];
292
+ for (const n of items) {
293
+ this.emit("notice", { id: n.a, title: n.b, body: n.c, category: n.d });
294
+ }
295
+ break;
296
+ }
297
+ case "cosmetic.ServerCosmeticsUserUnlockedPacket": {
298
+ this.emit("cosmeticsUnlocked", {
299
+ userUuid: packet.payload.c,
300
+ unlockedIds: packet.payload.a ?? [],
301
+ gifted: packet.payload.b,
302
+ unlockMap: packet.payload.d ?? {}
303
+ });
304
+ break;
305
+ }
306
+ case "cosmetic.ServerCosmeticsUserEquippedPacket": {
307
+ this.emit("equippedUpdate", {
308
+ userUuid: packet.payload.a,
309
+ equipped: packet.payload.b
310
+ });
311
+ break;
312
+ }
313
+ case "cosmetic.ServerCosmeticPlayerSettingsPacket": {
314
+ this.emit("playerSettings", {
315
+ userUuid: packet.payload.a,
316
+ settings: packet.payload.b
317
+ });
318
+ break;
319
+ }
320
+ case "cosmetic.ServerCosmeticsSkinTexturePacket": {
321
+ this.emit("skinTexture", {
322
+ userUuid: packet.payload.a,
323
+ skinTexture: packet.payload.b
324
+ });
325
+ break;
326
+ }
327
+ case "upnp.ServerUPnPSessionPopulatePacket": {
328
+ const sessions = packet.payload.a || [];
329
+ for (const s of sessions) {
330
+ this.emit("upnpSession", {
331
+ hostUuid: s.a,
332
+ ip: s.b,
333
+ port: s.c,
334
+ privacy: s.d,
335
+ worldName: s.e,
336
+ protocolVersion: s.f,
337
+ invites: s.g ?? [],
338
+ createdAt: s.h,
339
+ rawStatus: s.i
340
+ });
341
+ }
342
+ break;
343
+ }
344
+ case "upnp.ServerUPnPSessionRemovePacket": {
345
+ const removed = packet.payload.a || [];
346
+ for (const hostUuid of removed) {
347
+ this.emit("upnpSessionRemoved", { hostUuid });
348
+ }
349
+ break;
350
+ }
351
+ case "upnp.ServerUPnPSessionInviteAddPacket": {
352
+ this.emit("upnpInvite", { hostUuid: packet.payload.a });
353
+ break;
354
+ }
355
+ case "serverdiscovery.ServerServerDiscoveryResponsePacket": {
356
+ this.emit("serverList", {
357
+ recommended: packet.payload.recommended || [],
358
+ featured: packet.payload.featured || []
359
+ });
360
+ break;
361
+ }
362
+ }
363
+ }
364
+ // ----- Request/response -----
365
+ sendRequest(type, payload, timeoutMs = this.defaultRequestTimeoutMs) {
366
+ return new Promise((resolve, reject) => {
367
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
368
+ reject(new Error("WebSocket not connected"));
369
+ return;
370
+ }
371
+ const id = crypto.randomUUID();
372
+ const timer = setTimeout(() => {
373
+ this.pendingRequests.delete(id);
374
+ reject(new Error(`Request ${type} timed out`));
375
+ }, timeoutMs);
376
+ this.pendingRequests.set(id, { resolve, reject, type, timer });
377
+ for (const frame of this.codec.encode(type, payload, id)) {
378
+ this.ws.send(frame);
379
+ }
380
+ });
381
+ }
382
+ // ----- High-level API -----
383
+ // Chat
384
+ sendChatMessage(channelId, content, replyToId) {
385
+ return this.sendRequest("chat.ClientChatChannelMessageCreatePacket", {
386
+ a: channelId,
387
+ b: content,
388
+ c: replyToId ?? null
389
+ });
390
+ }
391
+ retrieveChatMessages(channelId, limit = 50, beforeMessageId) {
392
+ return this.sendRequest("chat.ClientChatChannelMessagesRetrievePacket", {
393
+ a: channelId,
394
+ b: limit,
395
+ c: beforeMessageId ?? null
396
+ });
397
+ }
398
+ createChannel(type, name, memberUuids = []) {
399
+ return this.sendRequest("chat.ClientChatChannelCreatePacket", {
400
+ a: type,
401
+ b: name,
402
+ c: memberUuids
403
+ });
404
+ }
405
+ deleteChatMessage(messageId) {
406
+ return this.sendRequest("chat.ChatChannelMessageDeletePacket", { a: messageId });
407
+ }
408
+ // Social
409
+ addFriend(targetUuid) {
410
+ return this.sendRequest("relationships.ClientRelationshipCreatePacket", {
411
+ a: targetUuid,
412
+ b: "FRIENDS"
413
+ });
414
+ }
415
+ removeFriend(targetUuid) {
416
+ return this.sendRequest("relationships.RelationshipDeletePacket", {
417
+ a: targetUuid,
418
+ b: "FRIENDS"
419
+ });
420
+ }
421
+ blockUser(targetUuid) {
422
+ return this.sendRequest("relationships.ClientRelationshipCreatePacket", {
423
+ a: targetUuid,
424
+ b: "BLOCKED"
425
+ });
426
+ }
427
+ lookupUuidByName(username) {
428
+ return this.sendRequest("relationships.ClientLookupUuidByNamePacket", { username });
429
+ }
430
+ // Profile
431
+ getProfile(uuid) {
432
+ return this.sendRequest("profile.ClientProfileRequestPacket", { a: uuid });
433
+ }
434
+ setActivity(activity, metadata = {}) {
435
+ return this.sendRequest("profile.ClientProfileActivityPacket", { a: activity, c: metadata });
436
+ }
437
+ // Cosmetics
438
+ listCosmetics() {
439
+ return this.sendRequest("cosmetic.ClientCosmeticRequestPacket", {});
440
+ }
441
+ triggerCosmeticAnimation(cosmeticId, animationId) {
442
+ return this.sendRequest("cosmetic.ClientCosmeticAnimationTriggerPacket", {
443
+ a: cosmeticId,
444
+ b: animationId
445
+ });
446
+ }
447
+ unlockCosmetics(cosmeticIds) {
448
+ return this.sendRequest("checkout.ClientCheckoutCosmeticsPacket", {
449
+ cosmetic_ids: cosmeticIds
450
+ });
451
+ }
452
+ // Skins
453
+ createSkin(name, model, hash) {
454
+ return this.sendRequest("skin.ClientSkinCreatePacket", { a: name, b: model, c: hash });
455
+ }
456
+ selectLastUsedSkin(skinId) {
457
+ return this.sendRequest("skin.ClientSkinUpdateLastUsedStatePacket", { a: skinId });
458
+ }
459
+ favoriteSkin(skinId, favorited) {
460
+ return this.sendRequest("skin.ClientSkinUpdateFavoriteStatePacket", {
461
+ a: skinId,
462
+ b: favorited
463
+ });
464
+ }
465
+ // Outfits
466
+ createOutfit(name, equippedCosmetics, settings = {}) {
467
+ return this.sendRequest("cosmetic.outfit.ClientCosmeticOutfitCreatePacket", {
468
+ name,
469
+ skin_id: null,
470
+ equipped_cosmetics: equippedCosmetics,
471
+ cosmetic_settings: settings
472
+ });
473
+ }
474
+ selectOutfit(outfitId) {
475
+ return this.sendRequest("cosmetic.outfit.ClientCosmeticOutfitSelectPacket", { a: outfitId });
476
+ }
477
+ setEquippedCosmetic(outfitId, slot, cosmeticId) {
478
+ return this.sendRequest("cosmetic.outfit.ClientCosmeticOutfitEquippedCosmeticsUpdatePacket", {
479
+ a: outfitId,
480
+ b: slot,
481
+ c: cosmeticId
482
+ });
483
+ }
484
+ // Emote wheel
485
+ updateEmoteWheel(wheelId, slots) {
486
+ return this.sendRequest("cosmetic.emote.ClientCosmeticEmoteWheelUpdatePacket", {
487
+ a: wheelId,
488
+ b: slots
489
+ });
490
+ }
491
+ selectEmoteWheel(wheelId) {
492
+ return this.sendRequest("cosmetic.emote.ClientCosmeticEmoteWheelSelectPacket", { a: wheelId });
493
+ }
494
+ // Wardrobe settings
495
+ getWardrobeSettings() {
496
+ return this.sendRequest("wardrobe.ClientWardrobeSettingsPacket", {});
497
+ }
498
+ // Discovery
499
+ listNotices() {
500
+ return this.sendRequest("notices.ClientNoticeRequestPacket", {});
501
+ }
502
+ listServerDiscovery() {
503
+ return this.sendRequest("serverdiscovery.ClientServerDiscoveryRequestPacket", {});
504
+ }
505
+ listKnownServers() {
506
+ return this.sendRequest("knownservers.ClientKnownServersRequestPacket", {});
507
+ }
508
+ // Multiplayer
509
+ relayIcePacket(targetUuid, payload) {
510
+ return this.sendRequest("multiplayer.ClientMultiplayerIceRelayPacket", {
511
+ a: targetUuid,
512
+ b: Array.from(payload)
513
+ });
514
+ }
515
+ pingServer(host, port, protocolVersion) {
516
+ return this.sendRequest("pingproxy.ClientPingProxyRequestPacket", {
517
+ a: host,
518
+ b: port,
519
+ c: protocolVersion ?? 0
520
+ });
521
+ }
522
+ // UPnP server hosting
523
+ createSession(ip, port, privacy, worldName, protocolVersion) {
524
+ return this.sendRequest("upnp.ClientUPnPSessionCreatePacket", {
525
+ a: ip,
526
+ b: port,
527
+ c: privacy,
528
+ d: protocolVersion,
529
+ e: worldName
530
+ });
531
+ }
532
+ closeSession() {
533
+ return this.sendRequest("upnp.ClientUPnPSessionClosePacket", {});
534
+ }
535
+ updateSession(patch) {
536
+ return this.sendRequest("upnp.ClientUPnPSessionUpdatePacket", {
537
+ a: patch.ip,
538
+ b: patch.port,
539
+ c: patch.privacy
540
+ });
541
+ }
542
+ inviteToSession(inviteeUuids) {
543
+ return this.sendRequest("upnp.ClientUPnPSessionInvitesAddPacket", { a: inviteeUuids });
544
+ }
545
+ revokeSessionInvites(inviteeUuids) {
546
+ return this.sendRequest("upnp.ClientUPnPSessionInvitesRemovePacket", { a: inviteeUuids });
547
+ }
548
+ pushServerStatus(rawStatus) {
549
+ return this.sendRequest("upnp.ClientUPnPSessionPingProxyUpdatePacket", { a: rawStatus });
550
+ }
551
+ // Social invite
552
+ inviteFriendToServer(targetUuid, address) {
553
+ return this.sendRequest("social.ClientSocialInviteRequestPacket", {
554
+ a: targetUuid,
555
+ b: address
556
+ });
557
+ }
558
+ // ----- Event emitter -----
559
+ on(event, listener) {
560
+ if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
561
+ this.listeners.get(event).add(listener);
562
+ return () => this.off(event, listener);
563
+ }
564
+ off(event, listener) {
565
+ this.listeners.get(event)?.delete(listener);
566
+ }
567
+ emit(event, data) {
568
+ for (const l of this.listeners.get(event) ?? []) {
569
+ try {
570
+ l(data);
571
+ } catch (e) {
572
+ }
573
+ }
574
+ }
575
+ isReady() {
576
+ return this.state === "ready";
577
+ }
578
+ isConnected() {
579
+ return this.state === "connected" || this.state === "ready";
580
+ }
581
+ };
582
+ function useWsEvent(client, event, handler) {
583
+ useEffect(() => {
584
+ const off = client.on(event, handler);
585
+ return off;
586
+ }, [client, event]);
587
+ }
588
+ function useWsState(client) {
589
+ const [state, setState] = useState(
590
+ client.isConnected() ? client.isReady() ? "ready" : "connected" : "disconnected"
591
+ );
592
+ useEffect(() => {
593
+ const update = () => setState(client.isConnected() ? client.isReady() ? "ready" : "connected" : "disconnected");
594
+ const off1 = client.on("open", update);
595
+ const off2 = client.on("bootstrap", update);
596
+ const off3 = client.on("close", update);
597
+ return () => {
598
+ off1();
599
+ off2();
600
+ off3();
601
+ };
602
+ }, [client]);
603
+ return state;
604
+ }
605
+
606
+ export { AnvilWsClient, ConnectionCodec, useWsEvent, useWsState };
607
+ //# sourceMappingURL=index.js.map
608
+ //# sourceMappingURL=index.js.map