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.
- package/CHANGELOG.md +40 -0
- package/eslint.config.js +54 -0
- package/package.json +45 -0
- package/src/ScreepsClient.ts +172 -0
- package/src/badge/colors.ts +83 -0
- package/src/badge/generateSvg.ts +70 -0
- package/src/badge/index.ts +1 -0
- package/src/badge/paths.ts +385 -0
- package/src/cache/Cache.ts +71 -0
- package/src/cache/Map2Storage.ts +112 -0
- package/src/file-storage.ts +2 -0
- package/src/http/HttpClient.ts +160 -0
- package/src/http/auth/AuthStrategy.ts +5 -0
- package/src/http/auth/GuestAuth.ts +13 -0
- package/src/http/auth/PasswordAuth.ts +17 -0
- package/src/http/auth/SteamTicketAuth.ts +17 -0
- package/src/http/auth/TokenAuth.ts +14 -0
- package/src/http/decompress.ts +37 -0
- package/src/http/endpoints/auth.ts +23 -0
- package/src/http/endpoints/experimental.ts +13 -0
- package/src/http/endpoints/game.ts +103 -0
- package/src/http/endpoints/leaderboard.ts +16 -0
- package/src/http/endpoints/power-creeps.ts +24 -0
- package/src/http/endpoints/register.ts +19 -0
- package/src/http/endpoints/user-messages.ts +20 -0
- package/src/http/endpoints/user.ts +95 -0
- package/src/http/fetchServerVersion.ts +151 -0
- package/src/index.ts +55 -0
- package/src/logger.ts +25 -0
- package/src/socket/MessageParser.ts +44 -0
- package/src/socket/SocketClient.ts +203 -0
- package/src/storage/FileStorage.ts +44 -0
- package/src/storage/IndexedDBStorage.ts +77 -0
- package/src/storage/NullStorage.ts +8 -0
- package/src/storage/StorageAdapter.ts +6 -0
- package/src/stores/MapStatsStore.ts +115 -0
- package/src/stores/MapStore.ts +254 -0
- package/src/stores/NavigationStore.ts +61 -0
- package/src/stores/RoomStore.ts +264 -0
- package/src/stores/ServerStore.ts +128 -0
- package/src/stores/TypedStore.ts +31 -0
- package/src/stores/UserStore.ts +189 -0
- package/src/subscription/index.ts +18 -0
- package/src/types/api.ts +252 -0
- package/src/types/events.ts +72 -0
- package/src/types/game.ts +160 -0
- package/tests/.gitkeep +0 -0
- package/tests/ScreepsClient.test.ts +229 -0
- package/tests/badge/generateSvg.test.ts +174 -0
- package/tests/cache/Cache.test.ts +99 -0
- package/tests/cache/Map2Storage.test.ts +130 -0
- package/tests/http/HttpClient.test.ts +188 -0
- package/tests/http/decompress.test.ts +52 -0
- package/tests/http/endpoints/auth.test.ts +126 -0
- package/tests/http/endpoints/game.test.ts +210 -0
- package/tests/http/endpoints/power-creeps.test.ts +81 -0
- package/tests/http/endpoints/user-messages.test.ts +68 -0
- package/tests/http/endpoints/user.test.ts +139 -0
- package/tests/socket/MessageParser.test.ts +55 -0
- package/tests/socket/SocketClient.test.ts +144 -0
- package/tests/storage/FileStorage.test.ts +64 -0
- package/tests/storage/IndexedDBStorage.test.ts +36 -0
- package/tests/storage/NullStorage.test.ts +24 -0
- package/tests/stores/MapStatsStore.test.ts +234 -0
- package/tests/stores/MapStore.test.ts +537 -0
- package/tests/stores/NavigationStore.test.ts +166 -0
- package/tests/stores/RoomStore.test.ts +130 -0
- package/tests/stores/ServerStore.test.ts +48 -0
- package/tests/stores/TypedStore.test.ts +54 -0
- package/tests/stores/UserStore.test.ts +136 -0
- package/tests/subscription/SubscriptionGroup.test.ts +34 -0
- package/tests/types/game.test.ts +42 -0
- package/tsconfig.json +17 -0
- package/tsup.config.ts +9 -0
- 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
|
+
}
|
package/src/types/api.ts
ADDED
|
@@ -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
|
+
}
|