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,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,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
|
+
}
|