@crowdedkingdoms/crowdyjs 8.4.6 → 8.6.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.
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Realtime traffic metrics — SDK-owned counters for every spatial message the
3
+ * client sends (the `client.udp.send*` mutations, including the ones issued
4
+ * internally by the World Stores layers) and every notification delivered on
5
+ * the shared `udpNotifications` subscription.
6
+ *
7
+ * Exposed as `client.metrics`; call {@link RealtimeMetrics.snapshot} from a
8
+ * HUD/diagnostics loop. Byte counts measure the app-defined **payload** field
9
+ * of each message (`state` / `audioData` / `text` / `payload` / `voxelState`),
10
+ * not wire framing or GraphQL envelope overhead.
11
+ */
12
+ const WINDOW_SECONDS = 10;
13
+ /**
14
+ * Counter store behind `client.metrics`. All methods are cheap (plain counter
15
+ * increments and a fixed ring of per-second rate buckets), so recording on
16
+ * every message adds no meaningful overhead to the send/receive hot paths.
17
+ */
18
+ export class RealtimeMetrics {
19
+ /** @param now - Clock override for tests. Defaults to `Date.now`. */
20
+ constructor(now = Date.now) {
21
+ this.now = now;
22
+ this.perKind = new Map();
23
+ this.totalSent = 0;
24
+ this.totalReceived = 0;
25
+ this.totalBytesSent = 0;
26
+ this.totalBytesReceived = 0;
27
+ this.buckets = Array.from({ length: WINDOW_SECONDS }, () => ({
28
+ second: -1,
29
+ sent: 0,
30
+ received: 0,
31
+ bytesSent: 0,
32
+ bytesReceived: 0,
33
+ }));
34
+ this.startedAtMs = this.now();
35
+ }
36
+ /** Record one outbound message. Called by the SDK's `udp.send*` methods. */
37
+ recordSent(kind, payloadBytes) {
38
+ this.totalSent += 1;
39
+ this.totalBytesSent += payloadBytes;
40
+ const entry = this.kindEntry(kind);
41
+ entry.sent.messages += 1;
42
+ entry.sent.bytes += payloadBytes;
43
+ const bucket = this.bucket();
44
+ bucket.sent += 1;
45
+ bucket.bytesSent += payloadBytes;
46
+ }
47
+ /** Record one delivered notification. Called by the realtime dispatch. */
48
+ recordReceived(kind, payloadBytes) {
49
+ this.totalReceived += 1;
50
+ this.totalBytesReceived += payloadBytes;
51
+ const entry = this.kindEntry(kind);
52
+ entry.received.messages += 1;
53
+ entry.received.bytes += payloadBytes;
54
+ const bucket = this.bucket();
55
+ bucket.received += 1;
56
+ bucket.bytesReceived += payloadBytes;
57
+ }
58
+ /**
59
+ * A point-in-time copy of all counters plus rates averaged over the sliding
60
+ * window. Safe to call every frame; allocation is proportional to the number
61
+ * of distinct message kinds.
62
+ */
63
+ snapshot() {
64
+ const nowMs = this.now();
65
+ const currentSecond = Math.floor(nowMs / 1000);
66
+ let sent = 0;
67
+ let received = 0;
68
+ let bytesSent = 0;
69
+ let bytesReceived = 0;
70
+ for (const bucket of this.buckets) {
71
+ if (bucket.second < 0 || currentSecond - bucket.second >= WINDOW_SECONDS)
72
+ continue;
73
+ sent += bucket.sent;
74
+ received += bucket.received;
75
+ bytesSent += bucket.bytesSent;
76
+ bytesReceived += bucket.bytesReceived;
77
+ }
78
+ // Average over the tracked lifetime when younger than the full window so
79
+ // early rates aren't diluted by empty seconds that never happened.
80
+ const elapsedSeconds = Math.max(1, Math.min(WINDOW_SECONDS, (nowMs - this.startedAtMs) / 1000));
81
+ const perKind = {};
82
+ for (const [kind, entry] of this.perKind) {
83
+ perKind[kind] = {
84
+ sent: { ...entry.sent },
85
+ received: { ...entry.received },
86
+ };
87
+ }
88
+ return {
89
+ totals: {
90
+ sent: this.totalSent,
91
+ received: this.totalReceived,
92
+ bytesSent: this.totalBytesSent,
93
+ bytesReceived: this.totalBytesReceived,
94
+ },
95
+ perKind,
96
+ rates: {
97
+ sentPerSecond: sent / elapsedSeconds,
98
+ receivedPerSecond: received / elapsedSeconds,
99
+ bytesSentPerSecond: bytesSent / elapsedSeconds,
100
+ bytesReceivedPerSecond: bytesReceived / elapsedSeconds,
101
+ },
102
+ startedAt: this.startedAtMs,
103
+ };
104
+ }
105
+ /** Zero every counter and restart the rate window. */
106
+ reset() {
107
+ this.totalSent = 0;
108
+ this.totalReceived = 0;
109
+ this.totalBytesSent = 0;
110
+ this.totalBytesReceived = 0;
111
+ this.perKind.clear();
112
+ for (const bucket of this.buckets) {
113
+ bucket.second = -1;
114
+ bucket.sent = 0;
115
+ bucket.received = 0;
116
+ bucket.bytesSent = 0;
117
+ bucket.bytesReceived = 0;
118
+ }
119
+ this.startedAtMs = this.now();
120
+ }
121
+ kindEntry(kind) {
122
+ let entry = this.perKind.get(kind);
123
+ if (!entry) {
124
+ entry = {
125
+ sent: { messages: 0, bytes: 0 },
126
+ received: { messages: 0, bytes: 0 },
127
+ };
128
+ this.perKind.set(kind, entry);
129
+ }
130
+ return entry;
131
+ }
132
+ bucket() {
133
+ const second = Math.floor(this.now() / 1000);
134
+ const bucket = this.buckets[second % WINDOW_SECONDS];
135
+ if (bucket.second !== second) {
136
+ bucket.second = second;
137
+ bucket.sent = 0;
138
+ bucket.received = 0;
139
+ bucket.bytesSent = 0;
140
+ bucket.bytesReceived = 0;
141
+ }
142
+ return bucket;
143
+ }
144
+ }
145
+ /**
146
+ * The size of a message's app-defined payload field: the first of `state`,
147
+ * `audioData`, `text`, `payload`, or `voxelState` present as a string. Base64
148
+ * and ASCII payloads measure 1 byte per character; multi-byte UTF-8 text is
149
+ * approximated by its UTF-16 length.
150
+ */
151
+ export function payloadBytesOf(record) {
152
+ for (const key of ['state', 'audioData', 'text', 'payload', 'voxelState']) {
153
+ const value = record[key];
154
+ if (typeof value === 'string')
155
+ return value.length;
156
+ }
157
+ return 0;
158
+ }
@@ -2,6 +2,7 @@ import type { SessionStore } from './session.js';
2
2
  import type { CrowdyLogger } from './logger.js';
3
3
  import { CrowdyRealtimeError } from './errors.js';
4
4
  import type { LbCookieStore } from './lb-cookie-store.js';
5
+ import type { RealtimeMetrics } from './metrics.js';
5
6
  import { type UdpNotificationsSubscription } from './generated/graphql.js';
6
7
  /**
7
8
  * Lifecycle state of the realtime WebSocket connection, as reported by
@@ -224,6 +225,7 @@ export interface RealtimeConfig {
224
225
  */
225
226
  export declare class RealtimeClient {
226
227
  private readonly session;
228
+ private readonly metrics?;
227
229
  private readonly wsUrl;
228
230
  private readonly logger;
229
231
  private readonly retryAttempts;
@@ -249,8 +251,10 @@ export declare class RealtimeClient {
249
251
  * the token tears the connection down (emitting an `AUTH_CLEARED`
250
252
  * {@link CrowdyRealtimeError}), while a token change made while connected
251
253
  * forces a reconnect using the new token.
254
+ * @param metrics - Optional traffic counters (`client.metrics`); each
255
+ * delivered notification is recorded once, regardless of subscriber count.
252
256
  */
253
- constructor(config: RealtimeConfig | undefined, session: SessionStore);
257
+ constructor(config: RealtimeConfig | undefined, session: SessionStore, metrics?: RealtimeMetrics | undefined);
254
258
  /**
255
259
  * The current connection state.
256
260
  *
@@ -1 +1 @@
1
- {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
package/dist/realtime.js CHANGED
@@ -2,6 +2,7 @@ import { print } from 'graphql';
2
2
  import { createClient } from 'graphql-ws';
3
3
  import { silentLogger } from './logger.js';
4
4
  import { CrowdyRealtimeError } from './errors.js';
5
+ import { payloadBytesOf } from './metrics.js';
5
6
  import { UdpNotificationsDocument, } from './generated/graphql.js';
6
7
  /**
7
8
  * Manages the single WebSocket subscription to the game-api's
@@ -30,9 +31,12 @@ export class RealtimeClient {
30
31
  * the token tears the connection down (emitting an `AUTH_CLEARED`
31
32
  * {@link CrowdyRealtimeError}), while a token change made while connected
32
33
  * forces a reconnect using the new token.
34
+ * @param metrics - Optional traffic counters (`client.metrics`); each
35
+ * delivered notification is recorded once, regardless of subscriber count.
33
36
  */
34
- constructor(config = {}, session) {
37
+ constructor(config = {}, session, metrics) {
35
38
  this.session = session;
39
+ this.metrics = metrics;
36
40
  this.client = null;
37
41
  this.release = null;
38
42
  this.desired = false;
@@ -293,6 +297,7 @@ export class RealtimeClient {
293
297
  this.ensureSubscription();
294
298
  }
295
299
  dispatch(notification) {
300
+ this.metrics?.recordReceived(notificationKind(notification.__typename), payloadBytesOf(notification));
296
301
  this.resolvePending(notification);
297
302
  // A non-retryable connection event (e.g. APP_ID_REQUIRED, AUTH_REQUIRED)
298
303
  // means the server completed the subscription and resubscribing would just
@@ -405,6 +410,26 @@ export class RealtimeClient {
405
410
  }
406
411
  }
407
412
  }
413
+ /** GraphQL `__typename` → the handler-style kind name used by `client.metrics`. */
414
+ const NOTIFICATION_KINDS = {
415
+ ActorUpdateNotification: 'actorUpdate',
416
+ ActorUpdateResponse: 'actorUpdateResponse',
417
+ VoxelUpdateNotification: 'voxelUpdate',
418
+ VoxelUpdateResponse: 'voxelUpdateResponse',
419
+ ClientAudioNotification: 'audio',
420
+ ClientTextNotification: 'text',
421
+ ClientEventNotification: 'clientEvent',
422
+ ServerEventNotification: 'serverEvent',
423
+ SingleActorMessageNotification: 'singleActorMessage',
424
+ ChannelMessageNotification: 'channelMessage',
425
+ GenericErrorResponse: 'genericError',
426
+ RealtimeConnectionEvent: 'connectionEvent',
427
+ };
428
+ function notificationKind(typename) {
429
+ if (!typename)
430
+ return 'unknown';
431
+ return NOTIFICATION_KINDS[typename] ?? typename;
432
+ }
408
433
  function isNodeRuntime() {
409
434
  return (typeof process !== 'undefined' &&
410
435
  typeof process.versions?.node === 'string');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crowdedkingdoms/crowdyjs",
3
- "version": "8.4.6",
3
+ "version": "8.6.0",
4
4
  "description": "Client SDK for Crowded Kingdoms GraphQL API with UDP proxy support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",