@mulingai-npm/redis 3.40.15 → 3.40.17

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.
@@ -11,6 +11,7 @@ export type MulingstreamSpeakerData = {
11
11
  recordingsDuration: number;
12
12
  targetLanguages: string[];
13
13
  timestamp: string;
14
+ lastHeartbeat?: number;
14
15
  isDemoSession?: boolean;
15
16
  demoExpiresAt?: number;
16
17
  };
@@ -20,6 +21,13 @@ export declare class MulingstreamSpeakerManager {
20
21
  private buildLockKey;
21
22
  acquireRoomLock(roomId: string, socketId: string, ttl?: number): Promise<boolean>;
22
23
  releaseRoomLock(roomId: string, socketId: string): Promise<void>;
24
+ /**
25
+ * Force-delete the room lock regardless of which socket owns it. Used by the
26
+ * speaker-joining recovery path when the lock is orphaned (no speaker records
27
+ * exist for the room but the lock key remains because the previous process
28
+ * crashed after SETNX and before addSpeaker).
29
+ */
30
+ forceReleaseRoomLock(roomId: string): Promise<void>;
23
31
  private parseHash;
24
32
  private serialize;
25
33
  private buildId;
@@ -37,6 +45,17 @@ export declare class MulingstreamSpeakerManager {
37
45
  getSpeakersLength(roomId: string): Promise<number>;
38
46
  isRoomEmpty(roomId: string): Promise<boolean>;
39
47
  updateSourceLanguage(socketId: string, newLang: string): Promise<boolean>;
48
+ /**
49
+ * Update lastHeartbeat timestamp for a speaker (called on every HEARTBEAT event from frontend).
50
+ * Returns the updated speaker, or null if not found.
51
+ * Mirrors MulingstreamListenerManager.updateHeartbeat — see MULINGSTREAM_RELIABILITY_ROADMAP.md §0.2.
52
+ */
53
+ updateHeartbeat(speakerId: string): Promise<MulingstreamSpeakerData | null>;
54
+ /**
55
+ * Update socketId for a speaker (called by heartbeat self-heal when reconnect creates a new socket).
56
+ * Mirrors the listener-side self-heal pattern — see MULINGSTREAM_RELIABILITY_ROADMAP.md §0.2.
57
+ */
58
+ updateSocketId(speakerId: string, newSocketId: string): Promise<boolean>;
40
59
  updateTargetLanguages(socketId: string, languages: string[]): Promise<boolean>;
41
60
  updateSourceLanguageByRoomId(roomId: string, newLang: string): Promise<number>;
42
61
  updateTargetLanguagesByRoomId(roomId: string, languages: string[]): Promise<number>;
@@ -20,6 +20,15 @@ class MulingstreamSpeakerManager {
20
20
  await this.redisClient.del(lockKey);
21
21
  }
22
22
  }
23
+ /**
24
+ * Force-delete the room lock regardless of which socket owns it. Used by the
25
+ * speaker-joining recovery path when the lock is orphaned (no speaker records
26
+ * exist for the room but the lock key remains because the previous process
27
+ * crashed after SETNX and before addSpeaker).
28
+ */
29
+ async forceReleaseRoomLock(roomId) {
30
+ await this.redisClient.del(this.buildLockKey(roomId));
31
+ }
23
32
  parseHash(hash) {
24
33
  return {
25
34
  speakerId: hash.speakerId,
@@ -33,6 +42,7 @@ class MulingstreamSpeakerManager {
33
42
  recordingsDuration: parseInt(hash.recordingsDuration, 10) || 0,
34
43
  targetLanguages: JSON.parse(hash.targetLanguages || '[]'),
35
44
  timestamp: hash.timestamp,
45
+ lastHeartbeat: hash.lastHeartbeat ? parseInt(hash.lastHeartbeat, 10) : undefined,
36
46
  isDemoSession: hash.isDemoSession === 'true',
37
47
  demoExpiresAt: parseInt(hash.demoExpiresAt, 10) || 0
38
48
  };
@@ -53,6 +63,9 @@ class MulingstreamSpeakerManager {
53
63
  if (data.roomUuid !== undefined) {
54
64
  result.roomUuid = data.roomUuid;
55
65
  }
66
+ if (data.lastHeartbeat !== undefined) {
67
+ result.lastHeartbeat = data.lastHeartbeat.toString();
68
+ }
56
69
  if (data.isDemoSession) {
57
70
  result.isDemoSession = 'true';
58
71
  }
@@ -202,6 +215,30 @@ class MulingstreamSpeakerManager {
202
215
  await this.redisClient.hset(this.buildKey(speaker.speakerId), { sourceLanguage: newLang });
203
216
  return true;
204
217
  }
218
+ /**
219
+ * Update lastHeartbeat timestamp for a speaker (called on every HEARTBEAT event from frontend).
220
+ * Returns the updated speaker, or null if not found.
221
+ * Mirrors MulingstreamListenerManager.updateHeartbeat — see MULINGSTREAM_RELIABILITY_ROADMAP.md §0.2.
222
+ */
223
+ async updateHeartbeat(speakerId) {
224
+ const speaker = await this.getSpeakerBySpeakerId(speakerId);
225
+ if (speaker === null)
226
+ return null;
227
+ const now = Date.now();
228
+ await this.redisClient.hset(this.buildKey(speakerId), { lastHeartbeat: now.toString() });
229
+ return { ...speaker, lastHeartbeat: now };
230
+ }
231
+ /**
232
+ * Update socketId for a speaker (called by heartbeat self-heal when reconnect creates a new socket).
233
+ * Mirrors the listener-side self-heal pattern — see MULINGSTREAM_RELIABILITY_ROADMAP.md §0.2.
234
+ */
235
+ async updateSocketId(speakerId, newSocketId) {
236
+ const speaker = await this.getSpeakerBySpeakerId(speakerId);
237
+ if (speaker === null)
238
+ return false;
239
+ await this.redisClient.hset(this.buildKey(speakerId), { socketId: newSocketId });
240
+ return true;
241
+ }
205
242
  async updateTargetLanguages(socketId, languages) {
206
243
  const speaker = await this.getSpeakerBySocketId(socketId);
207
244
  if (speaker === null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "3.40.15",
3
+ "version": "3.40.17",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {