screeps-connectivity 0.2.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.3
4
+
5
+ ### Patch Changes
6
+
7
+ - b14a86d: Fix foreign creep badge and username display in observed rooms.
8
+
9
+ When observing a room from another player, newly spawned creeps weren't showing
10
+ the owner's badge and displayed player ID instead of username. Fixed by:
11
+
12
+ - Merging user data across ticks instead of replacing, preserving player info
13
+ - Adding `badge?: Badge` to the users type throughout the codebase
14
+ - Adding `refreshForeignCreepBadges()` to update creep visuals when badge data arrives
15
+
16
+ ## 0.2.2
17
+
18
+ ### Patch Changes
19
+
20
+ - a42c89c: Guard against null or missing `objects` field in room update messages, and catch listener errors in `SocketClient.emit` so a bad listener cannot trigger a fatal socket error and kick the user out.
21
+
3
22
  ## 0.2.1
4
23
 
5
24
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screeps-connectivity",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "repository": {
@@ -87,7 +87,8 @@ export class SocketClient {
87
87
  }
88
88
  this.ws.onmessage = (event) => {
89
89
  this.handleMessage(event).catch(err => {
90
- this.logger.log('message parse error', err)
90
+ const raw = typeof event.data === 'string' ? event.data.slice(0, 200) : '(binary)'
91
+ this.logger.log('message parse error', err instanceof Error ? err.stack ?? err.message : String(err), 'raw:', raw)
91
92
  this.emit('socket:error', err instanceof Error ? err : new Error(String(err)))
92
93
  })
93
94
  }
@@ -157,7 +158,16 @@ export class SocketClient {
157
158
  }
158
159
 
159
160
  private emit(channel: string, data: unknown): void {
160
- this.listeners.get(channel)?.forEach(cb => cb(data))
161
+ this.listeners.get(channel)?.forEach(cb => {
162
+ try {
163
+ cb(data)
164
+ } catch (err) {
165
+ // A listener error on one channel must not prevent other listeners from
166
+ // running, and must not be rethrown into handleMessage's .catch() where
167
+ // it would be treated as a fatal socket error and kick the user out.
168
+ this.logger.log('listener error on channel', channel, err instanceof Error ? err.stack ?? err.message : String(err))
169
+ }
170
+ })
161
171
  }
162
172
 
163
173
  private async handleMessage(event: MessageEvent): Promise<void> {
@@ -2,7 +2,7 @@ import { TypedStore } from './TypedStore.js'
2
2
  import { RoomTerrain } from '../types/game.js'
3
3
  import type { Logger } from '../logger.js'
4
4
  import type { RoomStoreEvents } from '../types/events.js'
5
- import type { RoomObject, RoomObjectMap, RoomObjectDiff } from '../types/game.js'
5
+ import type { Badge, RoomObject, RoomObjectMap, RoomObjectDiff } from '../types/game.js'
6
6
  import type { HttpClient } from '../http/HttpClient.js'
7
7
  import type { SocketClient } from '../socket/SocketClient.js'
8
8
  import type { Cache } from '../cache/Cache.js'
@@ -13,7 +13,7 @@ export class RoomStore extends TypedStore<RoomStoreEvents> {
13
13
  private readonly socket: SocketClient
14
14
  private readonly cache: Cache
15
15
  private readonly roomObjects = new Map<string, RoomObjectMap>()
16
- private readonly roomUsers = new Map<string, Record<string, { _id: string; username: string }>>()
16
+ private readonly roomUsers = new Map<string, Record<string, { _id: string; username: string; badge?: Badge }>>()
17
17
  private readonly roomSubCount = new Map<string, number>()
18
18
  private readonly lastFlagsString = new Map<string, string>()
19
19
 
@@ -175,21 +175,30 @@ export class RoomStore extends TypedStore<RoomStoreEvents> {
175
175
  })
176
176
 
177
177
  const listenerSub = this.socket.on(channel, (data) => {
178
- const update = data as { objects: RoomObjectDiff; gameTime?: number; visual?: string; flags?: string; users?: Record<string, { _id: string; username: string }> }
178
+ const update = data as { objects?: RoomObjectDiff | null; gameTime?: number; visual?: string; flags?: string; users?: Record<string, { _id: string; username: string; badge?: Badge }> }
179
179
  const current: RoomObjectMap = { ...(this.roomObjects.get(mapKey) ?? {}) }
180
180
 
181
- for (const [id, obj] of Object.entries(update.objects)) {
182
- if (obj === null) {
183
- delete current[id]
184
- } else if (current[id]) {
185
- current[id] = { ...current[id], ...obj } as RoomObject
186
- } else {
187
- current[id] = obj as RoomObject
181
+ if (update.objects == null) {
182
+ // Some server implementations send tick messages without an objects field
183
+ // (e.g. heartbeat/visual-only ticks). Log it so it is visible in debug
184
+ // output but do not crash — we still process gameTime, visual and flags.
185
+ if (update.objects === null) {
186
+ this.logger.log('room update has null objects field', room, shard, `gameTime: ${update.gameTime}`)
187
+ }
188
+ } else {
189
+ for (const [id, obj] of Object.entries(update.objects)) {
190
+ if (obj === null) {
191
+ delete current[id]
192
+ } else if (current[id]) {
193
+ current[id] = { ...current[id], ...obj } as RoomObject
194
+ } else {
195
+ current[id] = obj as RoomObject
196
+ }
188
197
  }
189
198
  }
190
199
 
191
200
  // Build diff that includes flags so ObjectLayer can incremental-update them
192
- const diff: RoomObjectDiff = { ...update.objects }
201
+ const diff: RoomObjectDiff = { ...(update.objects ?? {}) }
193
202
 
194
203
  // Parse and inject flags from the dedicated flags field — only when the
195
204
  // server-sent flag string actually changed. The flags field is included
@@ -236,7 +245,8 @@ export class RoomStore extends TypedStore<RoomStoreEvents> {
236
245
  }
237
246
 
238
247
  if (update.users) {
239
- this.roomUsers.set(mapKey, update.users)
248
+ const existing = this.roomUsers.get(mapKey) ?? {}
249
+ this.roomUsers.set(mapKey, { ...existing, ...update.users })
240
250
  }
241
251
  this.roomObjects.set(mapKey, current)
242
252
  const users = this.roomUsers.get(mapKey)
@@ -252,6 +262,7 @@ export class RoomStore extends TypedStore<RoomStoreEvents> {
252
262
  if (remaining <= 0) {
253
263
  this.roomSubCount.delete(mapKey)
254
264
  this.roomObjects.delete(mapKey)
265
+ this.roomUsers.delete(mapKey)
255
266
  this.lastFlagsString.delete(mapKey)
256
267
  this.logger.log('unsubscribe', room, shard, '(last ref)', 'active:', this.activeRooms())
257
268
  } else {
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ Badge,
2
3
  ConsoleMessage,
3
4
  CpuStats,
4
5
  RoomMap2Data,
@@ -20,7 +21,7 @@ export interface RoomStoreEvents {
20
21
  objects: RoomObjectMap;
21
22
  diff: RoomObjectDiff;
22
23
  visual: string;
23
- users?: Record<string, { _id: string; username: string }>
24
+ users?: Record<string, { _id: string; username: string; badge?: Badge }>
24
25
  }
25
26
  'room:terrainavailable': { room: string; shard: string | null; terrain: RoomTerrain }
26
27
  'room:error': { room: string; shard: string | null; message: string }