screeps-connectivity 0.2.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/eslint.config.js +54 -0
  3. package/package.json +45 -0
  4. package/src/ScreepsClient.ts +172 -0
  5. package/src/badge/colors.ts +83 -0
  6. package/src/badge/generateSvg.ts +70 -0
  7. package/src/badge/index.ts +1 -0
  8. package/src/badge/paths.ts +385 -0
  9. package/src/cache/Cache.ts +71 -0
  10. package/src/cache/Map2Storage.ts +112 -0
  11. package/src/file-storage.ts +2 -0
  12. package/src/http/HttpClient.ts +160 -0
  13. package/src/http/auth/AuthStrategy.ts +5 -0
  14. package/src/http/auth/GuestAuth.ts +13 -0
  15. package/src/http/auth/PasswordAuth.ts +17 -0
  16. package/src/http/auth/SteamTicketAuth.ts +17 -0
  17. package/src/http/auth/TokenAuth.ts +14 -0
  18. package/src/http/decompress.ts +37 -0
  19. package/src/http/endpoints/auth.ts +23 -0
  20. package/src/http/endpoints/experimental.ts +13 -0
  21. package/src/http/endpoints/game.ts +103 -0
  22. package/src/http/endpoints/leaderboard.ts +16 -0
  23. package/src/http/endpoints/power-creeps.ts +24 -0
  24. package/src/http/endpoints/register.ts +19 -0
  25. package/src/http/endpoints/user-messages.ts +20 -0
  26. package/src/http/endpoints/user.ts +95 -0
  27. package/src/http/fetchServerVersion.ts +151 -0
  28. package/src/index.ts +55 -0
  29. package/src/logger.ts +25 -0
  30. package/src/socket/MessageParser.ts +44 -0
  31. package/src/socket/SocketClient.ts +203 -0
  32. package/src/storage/FileStorage.ts +44 -0
  33. package/src/storage/IndexedDBStorage.ts +77 -0
  34. package/src/storage/NullStorage.ts +8 -0
  35. package/src/storage/StorageAdapter.ts +6 -0
  36. package/src/stores/MapStatsStore.ts +115 -0
  37. package/src/stores/MapStore.ts +254 -0
  38. package/src/stores/NavigationStore.ts +61 -0
  39. package/src/stores/RoomStore.ts +264 -0
  40. package/src/stores/ServerStore.ts +128 -0
  41. package/src/stores/TypedStore.ts +31 -0
  42. package/src/stores/UserStore.ts +189 -0
  43. package/src/subscription/index.ts +18 -0
  44. package/src/types/api.ts +252 -0
  45. package/src/types/events.ts +72 -0
  46. package/src/types/game.ts +160 -0
  47. package/tests/.gitkeep +0 -0
  48. package/tests/ScreepsClient.test.ts +229 -0
  49. package/tests/badge/generateSvg.test.ts +174 -0
  50. package/tests/cache/Cache.test.ts +99 -0
  51. package/tests/cache/Map2Storage.test.ts +130 -0
  52. package/tests/http/HttpClient.test.ts +188 -0
  53. package/tests/http/decompress.test.ts +52 -0
  54. package/tests/http/endpoints/auth.test.ts +126 -0
  55. package/tests/http/endpoints/game.test.ts +210 -0
  56. package/tests/http/endpoints/power-creeps.test.ts +81 -0
  57. package/tests/http/endpoints/user-messages.test.ts +68 -0
  58. package/tests/http/endpoints/user.test.ts +139 -0
  59. package/tests/socket/MessageParser.test.ts +55 -0
  60. package/tests/socket/SocketClient.test.ts +144 -0
  61. package/tests/storage/FileStorage.test.ts +64 -0
  62. package/tests/storage/IndexedDBStorage.test.ts +36 -0
  63. package/tests/storage/NullStorage.test.ts +24 -0
  64. package/tests/stores/MapStatsStore.test.ts +234 -0
  65. package/tests/stores/MapStore.test.ts +537 -0
  66. package/tests/stores/NavigationStore.test.ts +166 -0
  67. package/tests/stores/RoomStore.test.ts +130 -0
  68. package/tests/stores/ServerStore.test.ts +48 -0
  69. package/tests/stores/TypedStore.test.ts +54 -0
  70. package/tests/stores/UserStore.test.ts +136 -0
  71. package/tests/subscription/SubscriptionGroup.test.ts +34 -0
  72. package/tests/types/game.test.ts +42 -0
  73. package/tsconfig.json +17 -0
  74. package/tsup.config.ts +9 -0
  75. package/vitest.config.ts +7 -0
@@ -0,0 +1,128 @@
1
+ import { TypedStore } from './TypedStore.js'
2
+ import type { Logger } from '../logger.js'
3
+ import type { ServerStoreEvents } from '../types/events.js'
4
+ import type { ServerVersion, ShardInfo, WorldInfo } from '../types/game.js'
5
+ import type { HttpClient } from '../http/HttpClient.js'
6
+ import type { SocketClient } from '../socket/SocketClient.js'
7
+ import type { Cache } from '../cache/Cache.js'
8
+ import type { Subscription } from '../subscription/index.js'
9
+
10
+ export class ServerStore extends TypedStore<ServerStoreEvents> {
11
+ private readonly http: HttpClient
12
+ private readonly cache: Cache
13
+ private readonly socketSubs: Subscription[] = []
14
+ private _version: ServerVersion | null = null
15
+ get versionInfo(): ServerVersion | null { return this._version }
16
+ get isPrivateServer(): boolean | null {
17
+ if (!this._version) return null
18
+ return (this._version.serverData?.shards?.length ?? 0) === 0
19
+ }
20
+ private _shards: ShardInfo[] | null = null
21
+ get shardList(): ShardInfo[] | null { return this._shards }
22
+
23
+ constructor(http: HttpClient, socket: SocketClient, cache: Cache, logger?: Logger) {
24
+ super(logger)
25
+ this.http = http
26
+ this.cache = cache
27
+
28
+ this.socketSubs.push(socket.on('connected', () => {
29
+ this.emit('server:connected', {})
30
+ }))
31
+ this.socketSubs.push(socket.on('disconnected', (data) => {
32
+ const d = data as { willReconnect: boolean }
33
+ this.emit('server:disconnected', { willReconnect: d.willReconnect })
34
+ }))
35
+ this.socketSubs.push(socket.on('socket:error', (data) => {
36
+ this.emit('server:error', { error: data instanceof Error ? data : new Error(String(data)) })
37
+ }))
38
+ }
39
+
40
+ /** Release socket listeners owned by this store. Idempotent. */
41
+ destroy(): void {
42
+ for (const sub of this.socketSubs) sub.dispose()
43
+ this.socketSubs.length = 0
44
+ }
45
+
46
+ async version(): Promise<ServerVersion> {
47
+ const cached = this.cache.get<ServerVersion>('server/version')
48
+ if (cached) return cached
49
+ const res = await this.http.request<ServerVersion>('GET', '/api/version')
50
+ this._version = res
51
+ this.cache.set('server/version', res, 5 * 60_000)
52
+ this.emit('server:version', res)
53
+ return res
54
+ }
55
+
56
+ async refreshVersion(): Promise<ServerVersion> {
57
+ this.cache.delete('server/version')
58
+ return this.version()
59
+ }
60
+
61
+ async shards(): Promise<ShardInfo[]> {
62
+ const cached = this.cache.get<ShardInfo[]>('server/shards')
63
+ if (cached) return cached
64
+ const res = await this.http.request<{ ok: number; shards: ShardInfo[] }>('GET', '/api/game/shards/info')
65
+ this._shards = res.shards
66
+ this.cache.set('server/shards', res.shards, 5 * 60_000)
67
+ this.emit('server:shards', res.shards)
68
+ return res.shards
69
+ }
70
+
71
+ async refreshShards(): Promise<ShardInfo[]> {
72
+ this.cache.delete('server/shards')
73
+ return this.shards()
74
+ }
75
+
76
+ async worldInfo(shard?: string): Promise<WorldInfo> {
77
+ const shardKey = shard ?? 'default'
78
+ const cacheKey = `server/world/${shardKey}`
79
+ const cached = this.cache.get<WorldInfo>(cacheKey)
80
+ if (cached) return cached
81
+
82
+ const params: Record<string, string> = {}
83
+ if (shard) params.shard = shard
84
+
85
+ const size = await this.http.request<{ ok: number; width: number; height: number }>(
86
+ 'GET', '/api/game/world-size', params
87
+ )
88
+ const { width, height } = size
89
+ this.logger.log(`worldInfo shard=${shard ?? 'none'} — raw size: ${width}x${height}`)
90
+
91
+ // Start with W/N-only assumption (most common for private servers)
92
+ // width/height = total rooms in that axis; W-only → all rooms are west of E0
93
+ let minX = -width, maxX = -1, minY = -height, maxY = -1
94
+
95
+ // Probe the four quadrant-origin rooms to detect which quadrants actually exist.
96
+ // mapStats only returns entries for rooms that exist on the server.
97
+ try {
98
+ const probe = await this.http.request<{ ok: number; stats: Record<string, unknown> }>(
99
+ 'POST', '/api/game/map-stats',
100
+ { rooms: ['W0N0', 'E0N0', 'W0S0', 'E0S0'], statName: 'owner0', ...params }
101
+ )
102
+ const stats = probe.stats ?? {}
103
+ const found = (['W0N0', 'E0N0', 'W0S0', 'E0S0'] as const).filter(r => r in stats)
104
+ this.logger.log(`worldInfo corner probe — found: [${found.length ? found.join(', ') : 'none'}]`)
105
+ if ('E0N0' in stats || 'E0S0' in stats) {
106
+ // E quadrant exists: width spans both W and E sides, split evenly
107
+ minX = -Math.ceil(width / 2)
108
+ maxX = Math.floor(width / 2) - 1
109
+ }
110
+ if ('W0S0' in stats || 'E0S0' in stats) {
111
+ // S quadrant exists: height spans both N and S sides, split evenly
112
+ minY = -Math.ceil(height / 2)
113
+ maxY = Math.floor(height / 2) - 1
114
+ }
115
+ } catch (e) {
116
+ this.logger.log('worldInfo corner probe failed — keeping W/N defaults:', e)
117
+ }
118
+
119
+ this.logger.log(`worldInfo computed bounds — x: [${minX}, ${maxX}] y: [${minY}, ${maxY}]`)
120
+ const info: WorldInfo = { shard: shard ?? null, width, height, minX, maxX, minY, maxY }
121
+ this.cache.set(cacheKey, info, 10 * 60_000)
122
+ return info
123
+ }
124
+
125
+ invalidateWorldInfo(shard?: string): void {
126
+ this.cache.delete(`server/world/${shard ?? 'default'}`)
127
+ }
128
+ }
@@ -0,0 +1,31 @@
1
+ import { Logger } from '../logger.js'
2
+ import type { Subscription } from '../subscription/index.js'
3
+
4
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5
+ export class TypedStore<EventMap extends Record<string, any>> extends EventTarget {
6
+ protected readonly logger: Logger
7
+
8
+ constructor(logger?: Logger) {
9
+ super()
10
+ this.logger = logger ?? Logger.create()
11
+ }
12
+
13
+ emit<K extends string & keyof EventMap>(type: K, detail: EventMap[K]): void {
14
+ this.dispatchEvent(new CustomEvent(type, { detail }))
15
+ }
16
+
17
+ on<K extends string & keyof EventMap>(
18
+ type: K,
19
+ handler: (detail: EventMap[K]) => void,
20
+ ): Subscription {
21
+ this.logger.log(`on "${type}"`)
22
+ const listener = (e: Event) => handler((e as CustomEvent<EventMap[K]>).detail)
23
+ this.addEventListener(type, listener)
24
+ return {
25
+ dispose: () => {
26
+ this.logger.log(`off "${type}"`)
27
+ this.removeEventListener(type, listener)
28
+ },
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,189 @@
1
+ import { TypedStore } from './TypedStore.js'
2
+ import type { Logger } from '../logger.js'
3
+ import type { UserStoreEvents } from '../types/events.js'
4
+ import type { UserInfo, CpuStats, ConsoleMessage, WorldStatus } from '../types/game.js'
5
+ import type { HttpClient } from '../http/HttpClient.js'
6
+ import type { SocketClient } from '../socket/SocketClient.js'
7
+ import type { Cache } from '../cache/Cache.js'
8
+ import type { Subscription } from '../subscription/index.js'
9
+
10
+ export class UserStore extends TypedStore<UserStoreEvents> {
11
+ private readonly http: HttpClient
12
+ private readonly socket: SocketClient
13
+ private readonly cache: Cache
14
+ readonly console: ConsoleMessage[] = []
15
+ readonly maxConsoleSize: number
16
+ private _cpu: CpuStats | null = null
17
+ get cpu(): CpuStats | null { return this._cpu }
18
+ private _userInfo: UserInfo | null = null
19
+ get userInfo(): UserInfo | null { return this._userInfo }
20
+ private _userId: string | null = null
21
+ get userId(): string | null { return this._userId }
22
+ private _worldStatus: WorldStatus | null = null
23
+ get worldStatusValue(): WorldStatus | null { return this._worldStatus }
24
+ private _mePromise: Promise<UserInfo> | null = null
25
+
26
+ constructor(http: HttpClient, socket: SocketClient, cache: Cache, logger?: Logger, maxConsoleSize = 100) {
27
+ super(logger)
28
+ this.http = http
29
+ this.socket = socket
30
+ this.cache = cache
31
+ this.maxConsoleSize = maxConsoleSize
32
+ }
33
+
34
+ async me(): Promise<UserInfo> {
35
+ const cached = this.cache.get<UserInfo>('user/me')
36
+ if (cached) {
37
+ this._userId = cached._id
38
+ this._userInfo = cached
39
+ return cached
40
+ }
41
+ if (this._mePromise) {
42
+ return this._mePromise
43
+ }
44
+ this.logger.log('fetch me')
45
+ this._mePromise = this.http.auth.me().then((res) => {
46
+ const user = res as unknown as UserInfo
47
+ this._userId = user._id
48
+ this._userInfo = user
49
+ this.cache.set('user/me', user, 60_000)
50
+ this.emit('user:me', user)
51
+ return user
52
+ }).finally(() => {
53
+ this._mePromise = null
54
+ })
55
+ return this._mePromise
56
+ }
57
+
58
+ async refreshMe(): Promise<UserInfo> {
59
+ this.logger.log('refresh me')
60
+ this.cache.delete('user/me')
61
+ return this.me()
62
+ }
63
+
64
+ async worldStatus(): Promise<WorldStatus> {
65
+ const cached = this.cache.get<WorldStatus>('user/worldStatus')
66
+ if (cached) {
67
+ this._worldStatus = cached
68
+ return cached
69
+ }
70
+
71
+ this.logger.log('fetch world status')
72
+ const res = await this.http.user.worldStatus()
73
+ this._worldStatus = res.status
74
+ this.cache.set('user/worldStatus', res.status, 60_000)
75
+ this.emit('user:worldStatus', { status: res.status })
76
+ return res.status
77
+ }
78
+
79
+ async refreshWorldStatus(): Promise<WorldStatus> {
80
+ this.logger.log('refresh world status')
81
+ this.cache.delete('user/worldStatus')
82
+ return this.worldStatus()
83
+ }
84
+
85
+ subscribe(channel: 'console' | 'cpu' | 'code'): Subscription {
86
+ this.logger.log('subscribe', channel)
87
+ let socketSub: Subscription | null = null
88
+ let listenerSub: Subscription | null = null
89
+ let disposed = false
90
+
91
+ const setup = async () => {
92
+ try {
93
+ const uid = this._userId ?? (await this.me())._id
94
+ if (disposed) return
95
+ const fullChannel = `user:${uid}/${channel}`
96
+ socketSub = this.socket.subscribe(fullChannel)
97
+ listenerSub = this.socket.on(fullChannel, (data) => {
98
+ if (channel === 'cpu') {
99
+ this._cpu = data as CpuStats
100
+ this.emit('user:cpu', this._cpu)
101
+ } else if (channel === 'console') {
102
+ const raw = data as { messages?: ConsoleMessage, error?: string }
103
+ const msg: ConsoleMessage = {
104
+ log: raw.messages?.log ?? [],
105
+ results: raw.messages?.results ?? [],
106
+ error: raw.messages?.error ?? [],
107
+ }
108
+ if (raw.error) {
109
+ msg.error.push(raw.error)
110
+ }
111
+ this.console.push(msg)
112
+ if (this.console.length > this.maxConsoleSize) {
113
+ this.console.splice(0, this.console.length - this.maxConsoleSize)
114
+ }
115
+ this.emit('user:console', { messages: msg })
116
+ } else if (channel === 'code') {
117
+ this.emit('user:code', data as { branch: string; modules: Record<string, string> })
118
+ }
119
+ })
120
+ } catch (err) {
121
+ if (!disposed) {
122
+ this.dispatchEvent(new ErrorEvent('error', { error: err instanceof Error ? err : new Error(String(err)) }))
123
+ }
124
+ }
125
+ }
126
+
127
+ void setup()
128
+
129
+ return {
130
+ dispose: () => {
131
+ this.logger.log('unsubscribe', channel)
132
+ disposed = true
133
+ socketSub?.dispose()
134
+ listenerSub?.dispose()
135
+ },
136
+ }
137
+ }
138
+
139
+ /** Subscribe to the general user stream to receive global data like flags. */
140
+ subscribeUserStream(): Subscription {
141
+ this.logger.log('subscribe user stream')
142
+ let socketSub: Subscription | null = null
143
+ let listenerSub: Subscription | null = null
144
+ let disposed = false
145
+
146
+ const setup = async () => {
147
+ try {
148
+ const uid = this._userId ?? (await this.me())._id
149
+ if (disposed) return
150
+ const fullChannel = `user:${uid}`
151
+ socketSub = this.socket.subscribe(fullChannel)
152
+ listenerSub = this.socket.on(fullChannel, (data) => {
153
+ const payload = data as Record<string, unknown>
154
+ // Log flags received via user stream
155
+ if (payload && typeof payload === 'object' && 'flags' in payload) {
156
+ const flags = payload.flags as Record<string, unknown> | undefined
157
+ if (flags && typeof flags === 'object') {
158
+ for (const [name, flagData] of Object.entries(flags)) {
159
+ const fd = flagData as Record<string, unknown> | null
160
+ if (fd && typeof fd === 'object') {
161
+ const room = fd.room ?? 'unknown'
162
+ const x = fd.x ?? '?'
163
+ const y = fd.y ?? '?'
164
+ this.logger.log(`[flag:user] ${name} @ ${room} (${x},${y})`)
165
+ }
166
+ }
167
+ }
168
+ }
169
+ this.emit('user:stream', payload)
170
+ })
171
+ } catch (err) {
172
+ if (!disposed) {
173
+ this.dispatchEvent(new ErrorEvent('error', { error: err instanceof Error ? err : new Error(String(err)) }))
174
+ }
175
+ }
176
+ }
177
+
178
+ void setup()
179
+
180
+ return {
181
+ dispose: () => {
182
+ this.logger.log('unsubscribe user stream')
183
+ disposed = true
184
+ socketSub?.dispose()
185
+ listenerSub?.dispose()
186
+ },
187
+ }
188
+ }
189
+ }
@@ -0,0 +1,18 @@
1
+ export interface Subscription {
2
+ dispose(): void
3
+ }
4
+
5
+ export class SubscriptionGroup implements Subscription {
6
+ private readonly subs: Subscription[] = []
7
+
8
+ add(sub: Subscription): void {
9
+ this.subs.push(sub)
10
+ }
11
+
12
+ dispose(): void {
13
+ for (const sub of this.subs) {
14
+ sub.dispose()
15
+ }
16
+ this.subs.length = 0
17
+ }
18
+ }
@@ -0,0 +1,252 @@
1
+ export interface ApiOkResponse {
2
+ ok: number
3
+ }
4
+
5
+ export interface ApiAuthSigninResponse {
6
+ ok: number
7
+ token: string
8
+ }
9
+
10
+ export interface ApiAuthMeResponse {
11
+ ok: number
12
+ _id: string
13
+ email: string
14
+ username: string
15
+ cpu: number
16
+ gcl: number
17
+ credits: number
18
+ badge: import('./game.js').Badge
19
+ password: boolean
20
+ }
21
+
22
+ export interface ApiAuthQueryTokenResponse {
23
+ ok: number
24
+ token: { full: boolean }
25
+ }
26
+
27
+ export interface ApiAuthSteamTicketResponse {
28
+ ok: number
29
+ token: string
30
+ steamid: string
31
+ }
32
+
33
+ export interface ApiAuthModInfoResponse {
34
+ ok: number
35
+ name: string
36
+ version: string
37
+ allowRegistration: boolean
38
+ steam: boolean
39
+ github: boolean
40
+ gitlab: boolean
41
+ }
42
+
43
+ export interface ApiRegisterCheckResponse {
44
+ ok: number
45
+ error?: string
46
+ }
47
+
48
+ export interface ApiRoomTerrainResponse {
49
+ ok: number
50
+ terrain: Array<{
51
+ _id: string
52
+ room: string
53
+ terrain: string
54
+ type: string
55
+ }>
56
+ }
57
+
58
+ export interface ApiRoomObjectsResponse {
59
+ ok: number
60
+ objects: unknown[]
61
+ users: Record<string, unknown>
62
+ }
63
+
64
+ export interface ApiVersionResponse {
65
+ ok: number
66
+ package: number
67
+ protocol: number
68
+ users: number
69
+ serverData: {
70
+ historyChunkSize: number
71
+ features: Array<{ name: string }>
72
+ shards: string[]
73
+ customObjectTypes?: unknown
74
+ }
75
+ }
76
+
77
+ export interface ApiShardsInfoResponse {
78
+ ok: number
79
+ shards: Array<{
80
+ name: string
81
+ lastTicks: number[]
82
+ cpuLimit: number
83
+ rooms: number
84
+ users: number
85
+ tick: number
86
+ }>
87
+ }
88
+
89
+ export interface ApiUserBranchesResponse {
90
+ ok: number
91
+ list: Array<{
92
+ _id: string
93
+ branch: string
94
+ activeWorld: boolean
95
+ activeSim: boolean
96
+ }>
97
+ }
98
+
99
+ export interface ApiLeaderboardListResponse {
100
+ ok: number
101
+ list: Array<{ _id: string; season: string; user: string; score: number; rank: number }>
102
+ count: number
103
+ users: Record<string, { _id: string; username: string; badge: import('./game.js').Badge; gcl: number }>
104
+ }
105
+
106
+ export interface ApiLeaderboardSeasonsResponse {
107
+ ok: number
108
+ seasons: Array<{ _id: string; name: string; date: string }>
109
+ }
110
+
111
+ export interface ApiMapStatsRoomStat {
112
+ status: string
113
+ novice: number | null
114
+ respawnArea: number | null
115
+ openTime: number | null
116
+ own?: { user: string; level: number }
117
+ safeMode?: boolean
118
+ [mineral: `minerals${number}`]: { type: string; density: number } | undefined
119
+ }
120
+
121
+ export interface ApiMapStatsBadge {
122
+ type: number | { path1: string; path2: string }
123
+ color1: string
124
+ color2: string
125
+ color3: string
126
+ param?: number
127
+ flip: boolean
128
+ }
129
+
130
+ export interface ApiGameRoomsResponse {
131
+ ok: number
132
+ rooms: Array<{
133
+ _id: string
134
+ room: string
135
+ terrain: string
136
+ }>
137
+ }
138
+
139
+ export interface ApiMapStatsResponse {
140
+ ok: number
141
+ gameTime: number
142
+ stats: Record<string, ApiMapStatsRoomStat>
143
+ statsMax: Record<string, unknown>
144
+ users: Record<string, { _id: string; username: string; badge: ApiMapStatsBadge }>
145
+ }
146
+
147
+ export interface ApiCreateFlagResponse {
148
+ ok: number
149
+ name?: string
150
+ error?: string
151
+ }
152
+
153
+ export interface ApiGenUniqueFlagNameResponse {
154
+ ok: number
155
+ name: string
156
+ }
157
+
158
+ export interface ApiCheckUniqueFlagNameResponse {
159
+ ok: number
160
+ error?: string
161
+ }
162
+
163
+ export interface ApiChangeFlagColorResponse {
164
+ ok: number
165
+ }
166
+
167
+ export interface ApiRemoveFlagResponse {
168
+ ok: number
169
+ }
170
+
171
+ export interface ApiGenUniqueObjectNameResponse {
172
+ ok: number
173
+ name: string
174
+ }
175
+
176
+ export interface ApiCheckUniqueObjectNameResponse {
177
+ ok: number
178
+ error?: string
179
+ }
180
+
181
+ export interface ApiGameTickResponse {
182
+ ok: number
183
+ tick: number
184
+ }
185
+
186
+ export interface ApiPowerCreep {
187
+ _id: string
188
+ name: string
189
+ className: string
190
+ level: number
191
+ powers: Record<string, { level: number; cooldownTime?: number }>
192
+ deleteTime?: number
193
+ }
194
+
195
+ export interface ApiPowerCreepsListResponse {
196
+ ok: number
197
+ list: ApiPowerCreep[]
198
+ }
199
+
200
+ export interface ApiUserFindResponse {
201
+ ok: number
202
+ user: {
203
+ _id: string
204
+ username: string
205
+ badge: import('./game.js').Badge
206
+ gcl: number
207
+ }
208
+ }
209
+
210
+ export interface ApiUserMoneyHistoryResponse {
211
+ ok: number
212
+ page: number
213
+ list: Array<{
214
+ _id: string
215
+ date: string
216
+ tick: number
217
+ type: string
218
+ balance: number
219
+ change: number
220
+ market?: unknown
221
+ }>
222
+ }
223
+
224
+ export interface ApiUserMessage {
225
+ _id: string
226
+ date: string
227
+ respondent: string
228
+ user: string
229
+ text: string
230
+ unread: boolean
231
+ }
232
+
233
+ export interface ApiUserMessagesListResponse {
234
+ ok: number
235
+ messages: ApiUserMessage[]
236
+ }
237
+
238
+ export interface ApiUserMessagesIndexEntry {
239
+ _id: string
240
+ message: ApiUserMessage
241
+ user: { _id: string; username: string; badge: import('./game.js').Badge }
242
+ }
243
+
244
+ export interface ApiUserMessagesIndexResponse {
245
+ ok: number
246
+ list: ApiUserMessagesIndexEntry[]
247
+ }
248
+
249
+ export interface ApiUserMessagesUnreadCountResponse {
250
+ ok: number
251
+ count: number
252
+ }
@@ -0,0 +1,72 @@
1
+ import type {
2
+ ConsoleMessage,
3
+ CpuStats,
4
+ RoomMap2Data,
5
+ RoomObjectDiff,
6
+ RoomObjectMap,
7
+ RoomTerrain,
8
+ ServerVersion,
9
+ ShardInfo,
10
+ UserInfo,
11
+ WorldStatus
12
+ } from './game.js'
13
+ import type {MapStatsRoomData} from '../stores/MapStatsStore.js'
14
+
15
+ export interface RoomStoreEvents {
16
+ 'room:update': {
17
+ room: string;
18
+ shard: string | null;
19
+ gameTime: number | undefined;
20
+ objects: RoomObjectMap;
21
+ diff: RoomObjectDiff;
22
+ visual: string;
23
+ users?: Record<string, { _id: string; username: string }>
24
+ }
25
+ 'room:terrainavailable': { room: string; shard: string | null; terrain: RoomTerrain }
26
+ 'room:error': { room: string; shard: string | null; message: string }
27
+ }
28
+
29
+ export type Map2SubscriptionStatus = 'pending' | 'active'
30
+
31
+ export interface MapStoreEvents {
32
+ 'room:map2update': { room: string; shard: string | null; data: RoomMap2Data; source: 'cache' | 'live' }
33
+ 'room:map2state': { room: string; shard: string | null; status: Map2SubscriptionStatus }
34
+ }
35
+
36
+ export interface UserStoreEvents {
37
+ 'user:me': UserInfo
38
+ 'user:worldStatus': { status: WorldStatus }
39
+ 'user:cpu': CpuStats
40
+ 'user:console': { messages: ConsoleMessage }
41
+ 'user:code': { branch: string; modules: Record<string, string> }
42
+ 'user:stream': Record<string, unknown>
43
+ }
44
+
45
+ export interface ServerStoreEvents {
46
+ 'server:connected': Record<string, never>
47
+ 'server:disconnected': { willReconnect: boolean }
48
+ 'server:error': { error: Error }
49
+ 'server:version': ServerVersion
50
+ 'server:shards': ShardInfo[]
51
+ }
52
+
53
+ export interface MapStatsStoreEvents {
54
+ 'mapStats:room': { room: string; shard: string | null; stat: MapStatsRoomData }
55
+ }
56
+
57
+ export interface HttpClientEvents {
58
+ 'http:success': {
59
+ method: string
60
+ path: string
61
+ status: number
62
+ }
63
+ 'http:error': {
64
+ method: string
65
+ path: string
66
+ status: number
67
+ error: Error
68
+ }
69
+ 'http:tokenRefresh': {
70
+ token: string
71
+ }
72
+ }