@glade-chat/glade.js 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -12,14 +12,13 @@ A powerful, fully-typed Node.js library for the [Glade](https://glade.chat) API
12
12
 
13
13
  ## About
14
14
 
15
- glade.js is an object-oriented library that makes it easy to interact with Glade Houses,
16
- Rooms, Messages, Members, Roles, DMs, friends, presence, and voice over REST and the
15
+ glade.js is an object-oriented library that makes it easy to interact with Glade - Houses,
16
+ Rooms, Messages, Members, Roles, DMs, friends, presence, and voice - over REST and the
17
17
  real-time gateway.
18
18
 
19
19
  - Object-oriented
20
20
  - Cache-backed and event-driven
21
21
  - Handles login and token refresh for you
22
- - First-class TypeScript types, zero build step (pure ESM)
23
22
 
24
23
  ## Installation
25
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glade-chat/glade.js",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A powerful Node.js library for interacting with the Glade (glade.chat) API and real-time gateway.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -83,6 +83,7 @@ export class Client extends EventEmitter {
83
83
  onDisconnect: (reason) => this.emit(Events.Disconnect, reason),
84
84
  onError: (err) => this.#emitError(err),
85
85
  ws: this.options.ws,
86
+ heartbeatMs: this.options.heartbeatMs,
86
87
  debug,
87
88
  });
88
89
 
@@ -21,6 +21,7 @@ export class Gateway {
21
21
  * @param {(err: Error) => void} [opts.onError]
22
22
  * @param {(msg: string) => void} [opts.debug]
23
23
  * @param {object} [opts.ws] Extra socket.io-client options.
24
+ * @param {number} [opts.heartbeatMs] Presence heartbeat interval; <=0 disables it.
24
25
  */
25
26
  constructor(opts) {
26
27
  this.url = opts.url;
@@ -42,6 +43,10 @@ export class Gateway {
42
43
  this._handshakeRefreshAttempts = 0;
43
44
  /** Cap on consecutive handshake refreshes to avoid a reconnect loop. */
44
45
  this._maxHandshakeRefresh = opts.maxHandshakeRefresh ?? 5;
46
+ /** Presence heartbeat interval (ms); the server offlines us without it. */
47
+ this._heartbeatMs = opts.heartbeatMs ?? 30_000;
48
+ /** @type {ReturnType<typeof setInterval> | null} */
49
+ this._heartbeatTimer = null;
45
50
  }
46
51
 
47
52
  /** Whether the underlying socket is currently connected. */
@@ -71,11 +76,13 @@ export class Gateway {
71
76
  this.id = this.socket.id;
72
77
  this._handshakeRefreshAttempts = 0;
73
78
  this.#debug(`Connected (socket ${this.id})`);
79
+ this.#startHeartbeat();
74
80
  this.onConnect();
75
81
  });
76
82
 
77
83
  this.socket.on('disconnect', (reason) => {
78
84
  this.#debug(`Disconnected: ${reason}`);
85
+ this.#stopHeartbeat();
79
86
  this.onDisconnect(reason);
80
87
  });
81
88
 
@@ -113,6 +120,7 @@ export class Gateway {
113
120
 
114
121
  /** Closes the gateway connection. */
115
122
  disconnect() {
123
+ this.#stopHeartbeat();
116
124
  if (this.socket) {
117
125
  this.socket.disconnect();
118
126
  this.socket = null;
@@ -120,6 +128,24 @@ export class Gateway {
120
128
  }
121
129
  }
122
130
 
131
+ /** Periodically tell the server we're alive so it keeps us marked online. */
132
+ #startHeartbeat() {
133
+ this.#stopHeartbeat();
134
+ if (!(this._heartbeatMs > 0)) return;
135
+ this._heartbeatTimer = setInterval(() => {
136
+ if (this.socket?.connected) this.socket.emit('presence:heartbeat');
137
+ }, this._heartbeatMs);
138
+ // Don't let the heartbeat keep the process alive on its own.
139
+ if (typeof this._heartbeatTimer.unref === 'function') this._heartbeatTimer.unref();
140
+ }
141
+
142
+ #stopHeartbeat() {
143
+ if (this._heartbeatTimer) {
144
+ clearInterval(this._heartbeatTimer);
145
+ this._heartbeatTimer = null;
146
+ }
147
+ }
148
+
123
149
  /**
124
150
  * Emits an event to the server without waiting for an acknowledgement.
125
151
  * @param {string} event
@@ -18,6 +18,11 @@ export const DefaultOptions = {
18
18
  * Keeps a long-running gateway connection authenticated.
19
19
  */
20
20
  refreshSkewMs: 60_000,
21
+ /**
22
+ * How often (ms) to send a presence heartbeat so the server keeps the
23
+ * connection marked online. Must stay below the server's liveness TTL.
24
+ */
25
+ heartbeatMs: 30_000,
21
26
  /** Whether to cache structures received from REST and the gateway. */
22
27
  cache: true,
23
28
  /**
@@ -132,6 +137,7 @@ export const GatewayCommand = {
132
137
  TypingStart: 'typing:start',
133
138
  TypingStop: 'typing:stop',
134
139
  PresenceSet: 'presence:set',
140
+ PresenceHeartbeat: 'presence:heartbeat',
135
141
  VoiceJoin: 'voice:join',
136
142
  VoiceLeave: 'voice:leave',
137
143
  VoiceSync: 'voice:sync',
package/types/index.d.ts CHANGED
@@ -51,6 +51,8 @@ export interface ClientOptions {
51
51
  autoRefresh?: boolean;
52
52
  /** Ms before token expiry to refresh proactively. Default `60000`. */
53
53
  refreshSkewMs?: number;
54
+ /** Presence heartbeat interval in ms; keeps the connection marked online. Default `30000`. */
55
+ heartbeatMs?: number;
54
56
  /** Cache structures from REST/gateway. Default `true`. */
55
57
  cache?: boolean;
56
58
  /** Auto-subscribe to cached rooms' realtime events on ready. Default `true`. */
@@ -232,6 +234,7 @@ export class Gateway {
232
234
  debug?: (msg: string) => void;
233
235
  ws?: Record<string, unknown>;
234
236
  maxHandshakeRefresh?: number;
237
+ heartbeatMs?: number;
235
238
  });
236
239
  socket: Socket | null;
237
240
  id: string | null;