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,160 @@
1
+ import { decompressGzip } from './decompress.js'
2
+ import { Logger } from '../logger.js'
3
+ import type { AuthStrategy } from './auth/AuthStrategy.js'
4
+ import { createAuthEndpoints, type AuthEndpoints } from './endpoints/auth.js'
5
+ import { createGameEndpoints, type GameEndpoints } from './endpoints/game.js'
6
+ import { createUserEndpoints, type UserEndpoints } from './endpoints/user.js'
7
+ import { createLeaderboardEndpoints, type LeaderboardEndpoints } from './endpoints/leaderboard.js'
8
+ import { createExperimentalEndpoints, type ExperimentalEndpoints } from './endpoints/experimental.js'
9
+ import { createRegisterEndpoints, type RegisterEndpoints } from './endpoints/register.js'
10
+ import type { HttpClientEvents } from '../types/events.js'
11
+ import type { Subscription } from '../subscription/index.js'
12
+
13
+ export interface RateLimitInfo {
14
+ limit: number
15
+ remaining: number
16
+ reset: number
17
+ }
18
+
19
+ export class HttpClient extends EventTarget {
20
+ readonly baseUrl: string
21
+ private readonly authStrategy: AuthStrategy
22
+ private readonly logger: Logger
23
+ private readonly serverPassword: string | null
24
+ token: string | null = null
25
+ readonly rateLimits = new Map<string, RateLimitInfo>()
26
+ private authenticating = false
27
+
28
+ readonly auth: AuthEndpoints
29
+ readonly register: RegisterEndpoints
30
+ readonly game: GameEndpoints
31
+ readonly user: UserEndpoints
32
+ readonly leaderboard: LeaderboardEndpoints
33
+ readonly experimental: ExperimentalEndpoints
34
+
35
+ constructor(opts: { url: string; auth: AuthStrategy; logger?: Logger; serverPassword?: string }) {
36
+ super()
37
+ this.baseUrl = opts.url.endsWith('/') ? opts.url : `${opts.url}/`
38
+ this.authStrategy = opts.auth
39
+ this.logger = opts.logger ?? Logger.create()
40
+ this.serverPassword = opts.serverPassword ?? null
41
+ this.auth = createAuthEndpoints(this)
42
+ this.register = createRegisterEndpoints(this)
43
+ this.game = createGameEndpoints(this)
44
+ this.user = createUserEndpoints(this)
45
+ this.leaderboard = createLeaderboardEndpoints(this)
46
+ this.experimental = createExperimentalEndpoints(this)
47
+ }
48
+
49
+ emit<K extends string & keyof HttpClientEvents>(type: K, detail: HttpClientEvents[K]): void {
50
+ this.dispatchEvent(new CustomEvent(type, { detail }))
51
+ }
52
+
53
+ on<K extends string & keyof HttpClientEvents>(
54
+ type: K,
55
+ handler: (detail: HttpClientEvents[K]) => void,
56
+ ): Subscription {
57
+ const listener = (e: Event) => handler((e as CustomEvent<HttpClientEvents[K]>).detail)
58
+ this.addEventListener(type, listener)
59
+ return {
60
+ dispose: () => {
61
+ this.removeEventListener(type, listener)
62
+ },
63
+ }
64
+ }
65
+
66
+ async authenticate(): Promise<void> {
67
+ this.logger.log('authenticate')
68
+ this.authenticating = true
69
+ try {
70
+ this.token = await this.authStrategy.authenticate(this)
71
+ this.logger.log('authenticated')
72
+ } finally {
73
+ this.authenticating = false
74
+ }
75
+ }
76
+
77
+ /** Update the stored token. Used to keep HTTP and WS token in sync after a WS auth rotation. */
78
+ setToken(token: string): void {
79
+ this.token = token
80
+ }
81
+
82
+ async request<T>(method: string, path: string, body?: Record<string, unknown>, isRetry = false): Promise<T> {
83
+ this.logger.log(method, path)
84
+ const url = new URL(path.startsWith('/') ? path.slice(1) : path, this.baseUrl)
85
+ const headers: Record<string, string> = {}
86
+
87
+ if (this.token) {
88
+ headers['X-Token'] = this.token
89
+ // passport-token strategy requires both x-token and x-username to be present.
90
+ // The server ignores the username value but fails auth if the header is missing.
91
+ headers['X-Username'] = this.token
92
+ }
93
+ if (this.serverPassword) {
94
+ headers['X-Server-Password'] = this.serverPassword
95
+ }
96
+
97
+ const init: RequestInit = { method, headers }
98
+
99
+ if (method === 'GET' && body) {
100
+ for (const [k, v] of Object.entries(body)) {
101
+ if (v != null) url.searchParams.set(k, String(v))
102
+ }
103
+ } else if (body) {
104
+ headers['Content-Type'] = 'application/json'
105
+ init.body = JSON.stringify(body)
106
+ }
107
+
108
+ const res = await fetch(url.toString(), init)
109
+
110
+ const newToken = res.headers.get('x-token')
111
+ if (newToken) {
112
+ this.token = newToken
113
+ this.emit('http:tokenRefresh', { token: newToken })
114
+ }
115
+
116
+ this.updateRateLimit(path, res)
117
+
118
+ if (res.status === 401 && !isRetry && !this.authenticating) {
119
+ await this.authenticate()
120
+ return this.request<T>(method, path, body, true)
121
+ }
122
+
123
+ if (!res.ok) {
124
+ let body = ''
125
+ try { body = await res.text() } catch { /* ignore */ }
126
+ const error = new Error(`HTTP ${res.status}: ${body}`)
127
+ this.emit('http:error', { method, path, status: res.status, error })
128
+ throw error
129
+ }
130
+
131
+ this.emit('http:success', { method, path, status: res.status })
132
+
133
+ const data = await res.json() as Record<string, unknown>
134
+
135
+ if (typeof data['error'] === 'string') {
136
+ const error = new Error(`Screeps API error: ${data['error']}`)
137
+ this.emit('http:error', { method, path, status: res.status, error })
138
+ throw error
139
+ }
140
+
141
+ if (typeof data['data'] === 'string' && (data['data'] as string).startsWith('gz:')) {
142
+ data['data'] = await decompressGzip(data['data'] as string)
143
+ }
144
+
145
+ return data as T
146
+ }
147
+
148
+ private updateRateLimit(path: string, res: Response): void {
149
+ const limit = res.headers.get('x-ratelimit-limit')
150
+ const remaining = res.headers.get('x-ratelimit-remaining')
151
+ const reset = res.headers.get('x-ratelimit-reset')
152
+ if (limit && remaining && reset) {
153
+ this.rateLimits.set(path, {
154
+ limit: parseInt(limit, 10),
155
+ remaining: parseInt(remaining, 10),
156
+ reset: parseInt(reset, 10),
157
+ })
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,5 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+
3
+ export interface AuthStrategy {
4
+ authenticate(http: HttpClient): Promise<string>
5
+ }
@@ -0,0 +1,13 @@
1
+ import type { AuthStrategy } from './AuthStrategy.js'
2
+ import type { HttpClient } from '../HttpClient.js'
3
+
4
+ /**
5
+ * Guest authentication strategy for xxscreeps-compatible private servers.
6
+ * Bypasses HTTP sign-in and sends the literal token "guest" to the WebSocket,
7
+ * granting read-only observer access without an account.
8
+ */
9
+ export class GuestAuth implements AuthStrategy {
10
+ async authenticate(_http: HttpClient): Promise<string> {
11
+ return 'guest'
12
+ }
13
+ }
@@ -0,0 +1,17 @@
1
+ import type { AuthStrategy } from './AuthStrategy.js'
2
+ import type { HttpClient } from '../HttpClient.js'
3
+
4
+ export class PasswordAuth implements AuthStrategy {
5
+ private readonly email: string
6
+ private readonly password: string
7
+
8
+ constructor(opts: { email: string; password: string }) {
9
+ this.email = opts.email
10
+ this.password = opts.password
11
+ }
12
+
13
+ async authenticate(http: HttpClient): Promise<string> {
14
+ const res = await http.auth.signin(this.email, this.password)
15
+ return res.token
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ import type { AuthStrategy } from './AuthStrategy.js'
2
+ import type { HttpClient } from '../HttpClient.js'
3
+
4
+ export class SteamTicketAuth implements AuthStrategy {
5
+ private readonly ticket: string
6
+ private readonly useNativeAuth: boolean | undefined
7
+
8
+ constructor(opts: { ticket: string; useNativeAuth?: boolean }) {
9
+ this.ticket = opts.ticket
10
+ this.useNativeAuth = opts.useNativeAuth
11
+ }
12
+
13
+ async authenticate(http: HttpClient): Promise<string> {
14
+ const res = await http.auth.steamTicket(this.ticket, this.useNativeAuth)
15
+ return res.token
16
+ }
17
+ }
@@ -0,0 +1,14 @@
1
+ import type { AuthStrategy } from './AuthStrategy.js'
2
+ import type { HttpClient } from '../HttpClient.js'
3
+
4
+ export class TokenAuth implements AuthStrategy {
5
+ private readonly token: string
6
+
7
+ constructor(opts: { token: string }) {
8
+ this.token = opts.token
9
+ }
10
+
11
+ async authenticate(_http: HttpClient): Promise<string> {
12
+ return this.token
13
+ }
14
+ }
@@ -0,0 +1,37 @@
1
+ async function decompress(data: string, format: 'gzip' | 'deflate'): Promise<unknown> {
2
+ const b64 = data.slice(3) // strip 'gz:' prefix
3
+ const binary = Uint8Array.from(atob(b64), c => c.charCodeAt(0))
4
+ const ds = new DecompressionStream(format)
5
+ const writer = ds.writable.getWriter()
6
+ await writer.write(binary)
7
+ await writer.close()
8
+ const reader = ds.readable.getReader()
9
+ const chunks: Uint8Array[] = []
10
+ try {
11
+ while (true) {
12
+ const { done, value } = await reader.read()
13
+ if (done) break
14
+ chunks.push(value)
15
+ }
16
+ } catch (e) {
17
+ reader.cancel()
18
+ throw e
19
+ }
20
+ let totalLength = 0
21
+ for (const chunk of chunks) totalLength += chunk.length
22
+ const result = new Uint8Array(totalLength)
23
+ let offset = 0
24
+ for (const chunk of chunks) {
25
+ result.set(chunk, offset)
26
+ offset += chunk.length
27
+ }
28
+ return JSON.parse(new TextDecoder().decode(result))
29
+ }
30
+
31
+ export function decompressGzip(data: string): Promise<unknown> {
32
+ return decompress(data, 'gzip')
33
+ }
34
+
35
+ export function decompressZlib(data: string): Promise<unknown> {
36
+ return decompress(data, 'deflate')
37
+ }
@@ -0,0 +1,23 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type { ApiAuthSigninResponse, ApiAuthMeResponse, ApiAuthQueryTokenResponse, ApiAuthSteamTicketResponse, ApiAuthModInfoResponse } from '../../types/api.js'
3
+
4
+ export interface AuthEndpoints {
5
+ /** @mmonly Not available on private servers (backend-local). Use steamTicket() instead. */
6
+ signin(email: string, password: string): Promise<ApiAuthSigninResponse>
7
+ me(): Promise<ApiAuthMeResponse>
8
+ /** @mmonly Not available on private servers (backend-local). */
9
+ queryToken(token: string): Promise<ApiAuthQueryTokenResponse>
10
+ steamTicket(ticket: string, useNativeAuth?: boolean): Promise<ApiAuthSteamTicketResponse>
11
+ /** Query screepsmod-auth capabilities (allowRegistration, available OAuth providers). Only available on servers running screepsmod-auth. */
12
+ modInfo(): Promise<ApiAuthModInfoResponse>
13
+ }
14
+
15
+ export function createAuthEndpoints(http: HttpClient): AuthEndpoints {
16
+ return {
17
+ signin: (email, password) => http.request('POST', '/api/auth/signin', { email, password }),
18
+ me: () => http.request('GET', '/api/auth/me'),
19
+ queryToken: (token) => http.request('GET', '/api/auth/query-token', { token }),
20
+ steamTicket: (ticket, useNativeAuth) => http.request('POST', '/api/auth/steam-ticket', { ticket, ...(useNativeAuth != null ? { useNativeAuth } : {}) }),
21
+ modInfo: () => http.request('GET', '/api/authmod'),
22
+ }
23
+ }
@@ -0,0 +1,13 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+
3
+ export interface ExperimentalEndpoints {
4
+ pvp(interval?: number): Promise<unknown>
5
+ nukes(): Promise<unknown>
6
+ }
7
+
8
+ export function createExperimentalEndpoints(http: HttpClient): ExperimentalEndpoints {
9
+ return {
10
+ pvp: (interval = 100) => http.request('GET', '/api/experimental/pvp', { interval }),
11
+ nukes: () => http.request('GET', '/api/experimental/nukes'),
12
+ }
13
+ }
@@ -0,0 +1,103 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type {
3
+ ApiRoomTerrainResponse,
4
+ ApiRoomObjectsResponse,
5
+ ApiShardsInfoResponse,
6
+ ApiMapStatsResponse,
7
+ ApiGameRoomsResponse,
8
+ ApiCreateFlagResponse,
9
+ ApiGenUniqueFlagNameResponse,
10
+ ApiCheckUniqueFlagNameResponse,
11
+ ApiChangeFlagColorResponse,
12
+ ApiRemoveFlagResponse,
13
+ ApiGenUniqueObjectNameResponse,
14
+ ApiCheckUniqueObjectNameResponse,
15
+ ApiGameTickResponse,
16
+ } from '../../types/api.js'
17
+ import { createPowerCreepsEndpoints, type PowerCreepsEndpoints } from './power-creeps.js'
18
+
19
+ export interface GameEndpoints {
20
+ roomTerrain(room: string, shard?: string | null): Promise<ApiRoomTerrainResponse>
21
+ /** @deprecated Not available on private servers (backend-local). Room objects are delivered via the `room:<name>` WebSocket channel. */
22
+ roomObjects(room: string, shard?: string | null): Promise<ApiRoomObjectsResponse>
23
+ roomStatus(room: string, shard?: string | null): Promise<{ ok: number; status: string; novice?: string }>
24
+ roomOverview(room: string, interval?: number, shard?: string | null): Promise<unknown>
25
+ time(shard?: string | null): Promise<{ ok: number; time: number }>
26
+ tick(): Promise<ApiGameTickResponse>
27
+ worldSize(shard?: string | null): Promise<unknown>
28
+ mapStats(rooms: string[], statName: string, shard?: string | null): Promise<ApiMapStatsResponse>
29
+ roomsTerrain(rooms: string[], shard?: string | null): Promise<ApiGameRoomsResponse>
30
+ createFlag(room: string, x: number, y: number, name: string, color: number, secondaryColor: number, shard?: string | null): Promise<ApiCreateFlagResponse>
31
+ genUniqueFlagName(): Promise<ApiGenUniqueFlagNameResponse>
32
+ checkUniqueFlagName(name: string): Promise<ApiCheckUniqueFlagNameResponse>
33
+ changeFlagColor(room: string, name: string, color: number, secondaryColor: number): Promise<ApiChangeFlagColorResponse>
34
+ removeFlag(room: string, name: string): Promise<ApiRemoveFlagResponse>
35
+ genUniqueObjectName(type: string): Promise<ApiGenUniqueObjectNameResponse>
36
+ checkUniqueObjectName(type: string, name: string): Promise<ApiCheckUniqueObjectNameResponse>
37
+ placeSpawn(room: string, x: number, y: number, name?: string, shard?: string | null): Promise<{ ok: number }>
38
+ createConstruction(room: string, x: number, y: number, structureType: string, name?: string, shard?: string | null): Promise<{ ok: number }>
39
+ removeConstructionSite(room: string, ids: string[], shard?: string | null): Promise<{ ok: number }>
40
+ addObjectIntent(id: string, room: string, name: string, intent: unknown): Promise<{ ok: number }>
41
+ addGlobalIntent(name: string, intent: unknown): Promise<{ ok: number }>
42
+ setNotifyWhenAttacked(id: string, enabled: boolean): Promise<{ ok: number }>
43
+ createInvader(room: string, x: number, y: number, size: number, type: string, boosted?: boolean): Promise<{ ok: number }>
44
+ removeInvader(id: string): Promise<{ ok: number }>
45
+ powerCreeps: PowerCreepsEndpoints
46
+ market: {
47
+ ordersIndex(shard?: string | null): Promise<unknown>
48
+ myOrders(): Promise<unknown>
49
+ orders(resourceType: string, shard?: string | null): Promise<unknown>
50
+ stats(resourceType: string, shard?: string | null): Promise<unknown>
51
+ }
52
+ shards: {
53
+ info(): Promise<ApiShardsInfoResponse>
54
+ }
55
+ }
56
+
57
+ function withShard(params: Record<string, unknown>, shard?: string | null): Record<string, unknown> {
58
+ if (shard) params.shard = shard
59
+ return params
60
+ }
61
+
62
+ export function createGameEndpoints(http: HttpClient): GameEndpoints {
63
+ return {
64
+ roomTerrain: (room, shard) => http.request('GET', '/api/game/room-terrain', withShard({ room, encoded: 1 }, shard)),
65
+ roomObjects: (room, shard) => http.request('GET', '/api/game/room-objects', withShard({ room }, shard)),
66
+ roomStatus: (room, shard) => http.request('GET', '/api/game/room-status', withShard({ room }, shard)),
67
+ roomOverview: (room, interval = 8, shard) => http.request('GET', '/api/game/room-overview', withShard({ room, interval }, shard)),
68
+ time: (shard) => http.request('GET', '/api/game/time', withShard({}, shard)),
69
+ worldSize: (shard) => http.request('GET', '/api/game/world-size', withShard({}, shard)),
70
+ mapStats: (rooms, statName, shard) => http.request('POST', '/api/game/map-stats', withShard({ rooms, statName }, shard)),
71
+ roomsTerrain: (rooms, shard) => {
72
+ const params = new URLSearchParams({ encoded: 'true' })
73
+ if (shard) params.set('shard', shard)
74
+ return http.request('POST', `/api/game/rooms?${params}`, { rooms })
75
+ },
76
+ createFlag: (room, x, y, name, color, secondaryColor, shard) => http.request('POST', '/api/game/create-flag', withShard({ room, x, y, name, color, secondaryColor }, shard)),
77
+ genUniqueFlagName: () => http.request('POST', '/api/game/gen-unique-flag-name'),
78
+ checkUniqueFlagName: (name) => http.request('POST', '/api/game/check-unique-flag-name', { name }),
79
+ changeFlagColor: (room, name, color, secondaryColor) => http.request('POST', '/api/game/change-flag-color', { room, name, color, secondaryColor }),
80
+ removeFlag: (room, name) => http.request('POST', '/api/game/remove-flag', { room, name }),
81
+ genUniqueObjectName: (type) => http.request('POST', '/api/game/gen-unique-object-name', { type }),
82
+ checkUniqueObjectName: (type, name) => http.request('POST', '/api/game/check-unique-object-name', { type, name }),
83
+ placeSpawn: (room, x, y, name, shard) => http.request('POST', '/api/game/place-spawn', withShard({ room, x, y, ...(name ? { name } : {}) }, shard)),
84
+ createConstruction: (room, x, y, structureType, name, shard) => http.request('POST', '/api/game/create-construction', withShard({ room, x, y, structureType, ...(name ? { name } : {}) }, shard)),
85
+ removeConstructionSite: (room, ids, shard) => http.request('POST', '/api/game/add-object-intent', withShard({ _id: 'room', room, name: 'removeConstructionSite', intent: ids.map(id => ({ id, roomName: room })) }, shard)),
86
+ addObjectIntent: (id, room, name, intent) => http.request('POST', '/api/game/add-object-intent', { _id: id, room, name, intent }),
87
+ addGlobalIntent: (name, intent) => http.request('POST', '/api/game/add-global-intent', { name, intent }),
88
+ setNotifyWhenAttacked: (id, enabled) => http.request('POST', '/api/game/set-notify-when-attacked', { _id: id, enabled }),
89
+ createInvader: (room, x, y, size, type, boosted) => http.request('POST', '/api/game/create-invader', { room, x, y, size, type, ...(boosted != null ? { boosted } : {}) }),
90
+ removeInvader: (id) => http.request('POST', '/api/game/remove-invader', { _id: id }),
91
+ tick: () => http.request('GET', '/api/game/tick'),
92
+ powerCreeps: createPowerCreepsEndpoints(http),
93
+ market: {
94
+ ordersIndex: (shard) => http.request('GET', '/api/game/market/orders-index', withShard({}, shard)),
95
+ myOrders: () => http.request('GET', '/api/game/market/my-orders'),
96
+ orders: (resourceType, shard) => http.request('GET', '/api/game/market/orders', withShard({ resourceType }, shard)),
97
+ stats: (resourceType, shard) => http.request('GET', '/api/game/market/stats', withShard({ resourceType }, shard)),
98
+ },
99
+ shards: {
100
+ info: () => http.request('GET', '/api/game/shards/info'),
101
+ },
102
+ }
103
+ }
@@ -0,0 +1,16 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type { ApiLeaderboardListResponse, ApiLeaderboardSeasonsResponse } from '../../types/api.js'
3
+
4
+ export interface LeaderboardEndpoints {
5
+ list(limit?: number, mode?: 'world' | 'power', offset?: number, season?: string): Promise<ApiLeaderboardListResponse>
6
+ find(username: string, mode?: string, season?: string): Promise<unknown>
7
+ seasons(): Promise<ApiLeaderboardSeasonsResponse>
8
+ }
9
+
10
+ export function createLeaderboardEndpoints(http: HttpClient): LeaderboardEndpoints {
11
+ return {
12
+ list: (limit = 10, mode = 'world', offset = 0, season) => http.request('GET', '/api/leaderboard/list', { limit, mode, offset, season }),
13
+ find: (username, mode = 'world', season = '') => http.request('GET', '/api/leaderboard/find', { username, mode, season }),
14
+ seasons: () => http.request('GET', '/api/leaderboard/seasons'),
15
+ }
16
+ }
@@ -0,0 +1,24 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type { ApiPowerCreepsListResponse } from '../../types/api.js'
3
+
4
+ export interface PowerCreepsEndpoints {
5
+ list(): Promise<ApiPowerCreepsListResponse>
6
+ create(name: string, className: string): Promise<{ ok: number }>
7
+ delete(id: string): Promise<{ ok: number }>
8
+ cancelDelete(id: string): Promise<{ ok: number }>
9
+ upgrade(id: string, powers: Record<string, number>): Promise<{ ok: number }>
10
+ rename(id: string, name: string): Promise<{ ok: number }>
11
+ experimentation(): Promise<{ ok: number }>
12
+ }
13
+
14
+ export function createPowerCreepsEndpoints(http: HttpClient): PowerCreepsEndpoints {
15
+ return {
16
+ list: () => http.request('GET', '/api/game/power-creeps/list'),
17
+ create: (name, className) => http.request('POST', '/api/game/power-creeps/create', { name, className }),
18
+ delete: (id) => http.request('POST', '/api/game/power-creeps/delete', { id }),
19
+ cancelDelete: (id) => http.request('POST', '/api/game/power-creeps/cancel-delete', { id }),
20
+ upgrade: (id, powers) => http.request('POST', '/api/game/power-creeps/upgrade', { id, powers }),
21
+ rename: (id, name) => http.request('POST', '/api/game/power-creeps/rename', { id, name }),
22
+ experimentation: () => http.request('POST', '/api/game/power-creeps/experimentation'),
23
+ }
24
+ }
@@ -0,0 +1,19 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type { ApiRegisterCheckResponse } from '../../types/api.js'
3
+
4
+ export interface RegisterEndpoints {
5
+ checkEmail(email: string): Promise<ApiRegisterCheckResponse>
6
+ checkUsername(username: string): Promise<ApiRegisterCheckResponse>
7
+ setUsername(username: string, email?: string): Promise<{ ok: number }>
8
+ /** Register a new user account (screepsmod-auth private servers only). */
9
+ submit(username: string, email: string, password: string, modules?: Record<string, string>): Promise<{ ok: number }>
10
+ }
11
+
12
+ export function createRegisterEndpoints(http: HttpClient): RegisterEndpoints {
13
+ return {
14
+ checkEmail: (email) => http.request('GET', '/api/register/check-email', { email }),
15
+ checkUsername: (username) => http.request('GET', '/api/register/check-username', { username }),
16
+ setUsername: (username, email) => http.request('POST', '/api/register/set-username', { username, ...(email != null ? { email } : {}) }),
17
+ submit: (username, email, password, modules) => http.request('POST', '/api/register/submit', { username, email, password, ...(modules != null ? { modules } : {}) }),
18
+ }
19
+ }
@@ -0,0 +1,20 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type { ApiUserMessagesListResponse, ApiUserMessagesIndexResponse, ApiUserMessagesUnreadCountResponse } from '../../types/api.js'
3
+
4
+ export interface UserMessagesEndpoints {
5
+ send(respondent: string, text: string): Promise<{ ok: number }>
6
+ list(respondent: string): Promise<ApiUserMessagesListResponse>
7
+ index(): Promise<ApiUserMessagesIndexResponse>
8
+ markRead(id: string): Promise<{ ok: number }>
9
+ unreadCount(): Promise<ApiUserMessagesUnreadCountResponse>
10
+ }
11
+
12
+ export function createUserMessagesEndpoints(http: HttpClient): UserMessagesEndpoints {
13
+ return {
14
+ send: (respondent, text) => http.request('POST', '/api/user/messages/send', { respondent, text }),
15
+ list: (respondent) => http.request('GET', '/api/user/messages/list', { respondent }),
16
+ index: () => http.request('GET', '/api/user/messages/index'),
17
+ markRead: (id) => http.request('POST', '/api/user/messages/mark-read', { id }),
18
+ unreadCount: () => http.request('GET', '/api/user/messages/unread-count'),
19
+ }
20
+ }
@@ -0,0 +1,95 @@
1
+ import type { HttpClient } from '../HttpClient.js'
2
+ import type {
3
+ ApiUserBranchesResponse,
4
+ ApiUserFindResponse,
5
+ ApiUserMoneyHistoryResponse,
6
+ } from '../../types/api.js'
7
+ import { createUserMessagesEndpoints, type UserMessagesEndpoints } from './user-messages.js'
8
+
9
+ export interface NotifyPrefs {
10
+ disabled: boolean
11
+ disabledOnMessages: boolean
12
+ sendOnline: boolean
13
+ interval: number
14
+ errorsInterval: number
15
+ }
16
+
17
+ export interface UserEndpoints {
18
+ branches(): Promise<ApiUserBranchesResponse>
19
+ code: {
20
+ get(branch?: string): Promise<unknown>
21
+ set(branch: string, modules: Record<string, string>): Promise<unknown>
22
+ }
23
+ memory: {
24
+ get(path: string, shard?: string | null): Promise<{ ok: number; data: unknown }>
25
+ set(path: string, value: unknown, shard?: string | null): Promise<unknown>
26
+ segment: {
27
+ get(segment: number, shard?: string | null): Promise<{ ok: number; data: string }>
28
+ set(segment: number, data: string, shard?: string | null): Promise<unknown>
29
+ }
30
+ }
31
+ console(expression: string, shard?: string | null): Promise<unknown>
32
+ stats(interval: number): Promise<unknown>
33
+ rooms(id: string): Promise<unknown>
34
+ overview(interval: number, statName: string): Promise<unknown>
35
+ worldStatus(): Promise<{ ok: number; status: 'normal' | 'lost' | 'empty' }>
36
+ worldStartRoom(shard?: string | null): Promise<unknown>
37
+ find(query: { username: string } | { id: string }): Promise<ApiUserFindResponse>
38
+ moneyHistory(page?: number): Promise<ApiUserMoneyHistoryResponse>
39
+ respawn(): Promise<{ ok: number }>
40
+ respawnProhibitedRooms(): Promise<{ ok: number; rooms: string[] }>
41
+ badge(badge: unknown): Promise<{ ok: number }>
42
+ setActiveBranch(activeName: 'activeWorld' | 'activeSim', branch: string): Promise<{ ok: number }>
43
+ cloneBranch(newName: string, branch?: string, defaultModules?: boolean): Promise<{ ok: number }>
44
+ deleteBranch(branch: string): Promise<{ ok: number }>
45
+ notifyPrefs(prefs: Partial<NotifyPrefs>): Promise<{ ok: number }>
46
+ tutorialDone(): Promise<{ ok: number }>
47
+ email(email: string): Promise<{ ok: number }>
48
+ setSteamVisible(visible: boolean): Promise<{ ok: number }>
49
+ /** Change the current user's password (screepsmod-auth private servers only). Pass oldPassword when the account already has a password set. */
50
+ password(newPassword: string, oldPassword?: string): Promise<{ ok: number }>
51
+ messages: UserMessagesEndpoints
52
+ }
53
+
54
+ function withShard(params: Record<string, unknown>, shard?: string | null): Record<string, unknown> {
55
+ if (shard) params.shard = shard
56
+ return params
57
+ }
58
+
59
+ export function createUserEndpoints(http: HttpClient): UserEndpoints {
60
+ return {
61
+ branches: () => http.request('GET', '/api/user/branches'),
62
+ code: {
63
+ get: (branch) => http.request('GET', '/api/user/code', branch ? { branch } : {}),
64
+ set: (branch, modules) => http.request('POST', '/api/user/code', { branch, modules, _hash: Date.now() }),
65
+ },
66
+ memory: {
67
+ get: (path, shard) => http.request('GET', '/api/user/memory', withShard({ path }, shard)),
68
+ set: (path, value, shard) => http.request('POST', '/api/user/memory', withShard({ path, value }, shard)),
69
+ segment: {
70
+ get: (segment, shard) => http.request('GET', '/api/user/memory-segment', withShard({ segment }, shard)),
71
+ set: (segment, data, shard) => http.request('POST', '/api/user/memory-segment', withShard({ segment, data }, shard)),
72
+ },
73
+ },
74
+ console: (expression, shard) => http.request('POST', '/api/user/console', withShard({ expression }, shard)),
75
+ stats: (interval) => http.request('GET', '/api/user/stats', { interval }),
76
+ rooms: (id) => http.request('GET', '/api/user/rooms', { id }),
77
+ overview: (interval, statName) => http.request('GET', '/api/user/overview', { interval, statName }),
78
+ worldStatus: () => http.request('GET', '/api/user/world-status'),
79
+ worldStartRoom: (shard) => http.request('GET', '/api/user/world-start-room', withShard({}, shard)),
80
+ find: (query) => http.request('GET', '/api/user/find', query as Record<string, unknown>),
81
+ moneyHistory: (page) => http.request('GET', '/api/user/money-history', page != null ? { page } : {}),
82
+ respawn: () => http.request('POST', '/api/user/respawn'),
83
+ respawnProhibitedRooms: () => http.request('GET', '/api/user/respawn-prohibited-rooms'),
84
+ badge: (badge) => http.request('POST', '/api/user/badge', { badge }),
85
+ setActiveBranch: (activeName, branch) => http.request('POST', '/api/user/set-active-branch', { activeName, branch }),
86
+ cloneBranch: (newName, branch, defaultModules) => http.request('POST', '/api/user/clone-branch', { newName, ...(branch ? { branch } : {}), ...(defaultModules != null ? { defaultModules } : {}) }),
87
+ deleteBranch: (branch) => http.request('POST', '/api/user/delete-branch', { branch }),
88
+ notifyPrefs: (prefs) => http.request('POST', '/api/user/notify-prefs', prefs as Record<string, unknown>),
89
+ tutorialDone: () => http.request('POST', '/api/user/tutorial-done'),
90
+ email: (email) => http.request('POST', '/api/user/email', { email }),
91
+ setSteamVisible: (visible) => http.request('POST', '/api/user/set-steam-visible', { visible }),
92
+ password: (newPassword, oldPassword) => http.request('POST', '/api/user/password', { password: newPassword, ...(oldPassword != null ? { oldPassword } : {}) }),
93
+ messages: createUserMessagesEndpoints(http),
94
+ }
95
+ }