@ovencord/ws 2.0.2 → 2.0.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "@ovencord/ws",
4
- "version": "2.0.2",
4
+ "version": "2.0.3",
5
5
  "description": "Wrapper around Discord's gateway",
6
6
  "scripts": {
7
7
  "test": "bun test",
@@ -27,4 +27,8 @@ export interface IShardingStrategy {
27
27
  * Spawns all the shards
28
28
  */
29
29
  spawn(shardIds: number[]): Awaitable<void>;
30
+ /**
31
+ * Gets all the shards managed by this strategy
32
+ */
33
+ getShards(): Awaitable<Collection<number, { ping?: number; status: WebSocketShardStatus }>>;
30
34
  }
@@ -81,4 +81,14 @@ export class SimpleShardingStrategy implements IShardingStrategy {
81
81
  public async fetchStatus() {
82
82
  return this.shards.mapValues((shard) => shard.status);
83
83
  }
84
+
85
+ /**
86
+ * {@inheritDoc IShardingStrategy.getShards}
87
+ */
88
+ public async getShards() {
89
+ return this.shards.mapValues((shard) => ({
90
+ ping: shard.ping,
91
+ status: shard.status,
92
+ }));
93
+ }
84
94
  }
@@ -22,6 +22,11 @@ export class WorkerShardingStrategy implements IShardingStrategy {
22
22
  this.options = options;
23
23
  }
24
24
 
25
+ public async getShards(): Promise<Collection<number, { ping?: number; status: WebSocketShardStatus; }>> {
26
+ const statuses = await this.fetchStatus();
27
+ return statuses.mapValues((status) => ({ status }));
28
+ }
29
+
25
30
 
26
31
  public async spawn(shardIds: number[]) {
27
32
  for (const shardId of shardIds) {
@@ -1,9 +1,9 @@
1
1
  import { Collection } from '@ovencord/collection';
2
2
  import { lazy } from '@ovencord/util';
3
- import { APIVersion, GatewayOpcodes } from 'discord-api-types/v10';
3
+ import { APIVersion, GatewayOpcodes, type GatewayPresenceUpdateData } from 'discord-api-types/v10';
4
4
  import { SimpleShardingStrategy } from '../strategies/sharding/SimpleShardingStrategy.js';
5
5
  import { SimpleIdentifyThrottler } from '../throttling/SimpleIdentifyThrottler.js';
6
- import type { SessionInfo, OptionalWebSocketManagerOptions, WebSocketManager } from '../ws/WebSocketManager.js';
6
+ import type { SessionInfo, OptionalWebSocketManagerOptions, WebSocketManager, ShardRange } from '../ws/WebSocketManager.js';
7
7
  import type { SendRateLimitState } from '../ws/WebSocketShard.js';
8
8
 
9
9
  /**
@@ -40,11 +40,11 @@ export const DefaultWebSocketManagerOptions = {
40
40
  const info = await manager.fetchGatewayInformation();
41
41
  return new SimpleIdentifyThrottler(info.session_start_limit.max_concurrency);
42
42
  },
43
- buildStrategy: (manager) => new SimpleShardingStrategy(manager),
44
- shardCount: null,
45
- shardIds: null,
46
- largeThreshold: null,
47
- initialPresence: null,
43
+ buildStrategy: (manager: WebSocketManager) => new SimpleShardingStrategy(manager),
44
+ shardCount: null as number | null,
45
+ shardIds: null as number[] | ShardRange | null,
46
+ largeThreshold: null as number | null,
47
+ initialPresence: null as GatewayPresenceUpdateData | null,
48
48
  identifyProperties: {
49
49
  browser: DefaultDeviceProperty,
50
50
  device: DefaultDeviceProperty,
@@ -52,9 +52,9 @@ export const DefaultWebSocketManagerOptions = {
52
52
  },
53
53
  version: APIVersion,
54
54
  encoding: Encoding.JSON,
55
- compression: null,
55
+ compression: null as CompressionMethod | null,
56
56
  useIdentifyCompression: false,
57
- retrieveSessionInfo(shardId) {
57
+ retrieveSessionInfo(shardId: number) {
58
58
  const store = getDefaultSessionStore();
59
59
  return store.get(shardId) ?? null;
60
60
  },
@@ -13,7 +13,7 @@ import type {
13
13
  import type { IShardingStrategy } from '../strategies/sharding/IShardingStrategy.js';
14
14
  import type { IIdentifyThrottler } from '../throttling/IIdentifyThrottler.js';
15
15
  import { DefaultWebSocketManagerOptions, type CompressionMethod, type Encoding } from '../utils/constants.js';
16
- import type { WebSocketShardDestroyOptions, WebSocketShardEvents, WebSocketShardStatus } from './WebSocketShard.js';
16
+ import { WebSocketShardStatus, type WebSocketShardDestroyOptions, type WebSocketShardEvents } from './WebSocketShard.js';
17
17
 
18
18
  /**
19
19
  * Represents a range of shard ids
@@ -264,6 +264,32 @@ export class WebSocketManager extends AsyncEventEmitter<ManagerShardEventsMap> i
264
264
  return this.#token;
265
265
  }
266
266
 
267
+ /**
268
+ * Gets the average ping of all active shards
269
+ *
270
+ * @returns The average ping in milliseconds, or -1 if no shards are ready
271
+ */
272
+ public get ping(): number {
273
+ return -1; // Will be calculated asynchronously
274
+ }
275
+
276
+ /**
277
+ * Gets the average ping of all active shards asynchronously
278
+ *
279
+ * @returns The average ping in milliseconds, or -1 if no shards are ready
280
+ */
281
+ public async getPing(): Promise<number> {
282
+ const shards = await this.strategy.getShards();
283
+ const activeShards = [...shards.values()].filter(
284
+ (shard) => shard.status === WebSocketShardStatus.Ready && shard.ping !== undefined && shard.ping >= 0
285
+ );
286
+
287
+ if (activeShards.length === 0) return -1;
288
+
289
+ const totalPing = activeShards.reduce((sum, shard) => sum + (shard.ping ?? 0), 0);
290
+ return Math.round(totalPing / activeShards.length);
291
+ }
292
+
267
293
  public constructor(options: CreateWebSocketManagerOptions) {
268
294
  if (typeof options.fetchGatewayInformation !== 'function') {
269
295
  throw new TypeError('fetchGatewayInformation is required');
@@ -98,6 +98,13 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
98
98
 
99
99
  private lastHeartbeatAt = -1;
100
100
 
101
+ public lastPingTimestamp = -1;
102
+
103
+ /**
104
+ * The latency of the last heartbeat in milliseconds
105
+ */
106
+ public ping = -1;
107
+
101
108
  // Indicates whether the shard has already resolved its original connect() call
102
109
  private initialConnectResolved = false;
103
110
 
@@ -510,6 +517,8 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
510
517
 
511
518
  const session = await this.strategy.retrieveSessionInfo(this.id);
512
519
 
520
+ this.lastPingTimestamp = Number(Bun.nanoseconds());
521
+
513
522
  await this.send({
514
523
  op: GatewayOpcodes.Heartbeat,
515
524
 
@@ -689,10 +698,13 @@ export class WebSocketShard extends AsyncEventEmitter<WebSocketShardEventsMap> {
689
698
  this.isAck = true;
690
699
 
691
700
  const ackAt = Date.now();
701
+ const latency = Math.round((Number(Bun.nanoseconds()) - this.lastPingTimestamp) / 1_000_000);
702
+ this.ping = latency;
703
+
692
704
  this.emit(WebSocketShardEvents.HeartbeatComplete, {
693
705
  ackAt,
694
706
  heartbeatAt: this.lastHeartbeatAt,
695
- latency: ackAt - this.lastHeartbeatAt,
707
+ latency,
696
708
  });
697
709
 
698
710
  break;