screeps-connectivity 0.13.0 → 0.14.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 +63 -0
- package/package.json +1 -1
- package/src/http/HttpClient.ts +15 -1
- package/src/http/endpoints/game.ts +10 -7
- package/src/http/fetchServerVersion.ts +13 -0
- package/src/index.ts +2 -2
- package/src/socket/SocketClient.ts +1 -1
- package/src/stores/ServerStore.ts +2 -2
- package/src/types/api.ts +22 -2
- package/src/types/events.ts +1 -1
- package/src/types/game.ts +8 -0
- package/tests/http/HttpClient.test.ts +10 -0
- package/tests/socket/SocketClient.test.ts +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 57a7e1d: Hide the connection ("server") password field for xxscreeps servers.
|
|
8
|
+
|
|
9
|
+
`screeps-connectivity` adds a `hasOfficialLike(version)` capability helper that
|
|
10
|
+
detects the `official-like` feature advertised at `/api/version`. The login
|
|
11
|
+
screens (web and desktop) now use it to hide the server-password field on
|
|
12
|
+
xxscreeps servers, where the screepsmod-auth connection password does not apply.
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 5d46b8e: Fix flag removal and color changes failing with "invalid shard" on multi-shard servers. `removeFlag` and `changeFlagColor` now accept and forward the current shard, matching the other room-scoped game endpoints.
|
|
17
|
+
- 3b9b951: Revamp the public profile page (`/profile/<username>`):
|
|
18
|
+
|
|
19
|
+
- Header now mirrors the self Overview chrome — small badge, the player's name
|
|
20
|
+
as the title, and a compact GCL/GPL readout rendered as rounded chips bordered
|
|
21
|
+
in the rank color (teal / red).
|
|
22
|
+
- The "Last 7 days" stat block is now a dropdown (1 hour / 24 hours / 7 days);
|
|
23
|
+
the tiles refetch the user's public stats for the selected window.
|
|
24
|
+
- Stat tiles now render correctly on servers that return `/api/user/stats`
|
|
25
|
+
metrics as a single pre-summed total per interval, not just per-tick buckets
|
|
26
|
+
(`ApiUserStatsResponse.stats` widened to `number | bucket[]`).
|
|
27
|
+
- Cross-links between the two account views: the username on the self Overview
|
|
28
|
+
links to the public profile, and your own public profile links back to your
|
|
29
|
+
private Overview.
|
|
30
|
+
- The top-bar Overview button no longer appears active while a profile is open —
|
|
31
|
+
a profile is a separate view.
|
|
32
|
+
|
|
33
|
+
- 79bd3d0: Add a read-only per-room overview page at `/room-overview/<shard>/<room>` (or
|
|
34
|
+
`/room-overview/<room>` on single-shard servers):
|
|
35
|
+
|
|
36
|
+
- Header with the room name, owner (badge + profile link, or "Unclaimed room"),
|
|
37
|
+
and a room minimap thumbnail that opens the live room view.
|
|
38
|
+
- The same seven stat tiles as the account Overview, summed over a selectable
|
|
39
|
+
interval (1 hour / 24 hours / 7 days).
|
|
40
|
+
- A history graph rendering the six per-bucket metrics (energy harvested,
|
|
41
|
+
construction, control, energy on creeps, creeps produced/lost) as
|
|
42
|
+
opacity-scaled dot strips.
|
|
43
|
+
- Entry points: a chart button next to each room name on the self Overview and
|
|
44
|
+
public Profile pages, plus a button in the in-game room view's left toolbar
|
|
45
|
+
(between the World Map and History buttons).
|
|
46
|
+
- `screeps-connectivity`: the existing `game.roomOverview` endpoint is now typed
|
|
47
|
+
with the new exported `ApiRoomOverviewResponse` (was `unknown`).
|
|
48
|
+
|
|
49
|
+
## 0.13.1
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- 661a53f: Don't show the "Connection lost" modal after an intentional disconnect. A guest hitting Login (or any user logging out) tore the session down synchronously, but the socket's async `onclose` still fired `server:disconnected` with `willReconnect: false`, which re-raised a fatal session error over the login screen. The disconnected event now carries an `intentional` flag so the client can distinguish a user-initiated close from a genuinely lost connection.
|
|
54
|
+
- 9bc39f8: History viewer fixes and a couple of supporting connectivity additions:
|
|
55
|
+
|
|
56
|
+
- Render terrain when reloading directly into history mode. Terrain loading was gated behind the room-subscription effect, which is skipped in history mode, so a fresh reload into a `#tick=` URL showed no terrain. Terrain/decoration loading now runs independently of history mode.
|
|
57
|
+
- Open the history view at the start of the previous, fully-written chunk instead of the current tick (whose chunk isn't flushed yet), avoiding an immediate 404 + fallback round-trip.
|
|
58
|
+
- Suppress the red failure toast when a history chunk is missing (404). `roomHistory` requests are now `silent`, and the viewer shows an in-room "No data available for this tick" hint instead, prompting the user to pick another tick from the timeline.
|
|
59
|
+
- During playback, skip to the start of the next chunk when the current chunk is missing, instead of re-fetching the same non-existent file on every tick.
|
|
60
|
+
- Expose `serverData.historyKeepTicks` (non-official field from the xxscreeps history mod) on the version types, used to size the history timeline's replayable range (falls back to a default window when absent).
|
|
61
|
+
- HTTP errors thrown by `HttpClient` now carry a `status` property so callers can distinguish a 404 from other failures.
|
|
62
|
+
- Dev-only: proxy `/room-history` to `VITE_PROXY_TARGET` in the Vite dev server.
|
|
63
|
+
|
|
64
|
+
- 1e85697: Accept the xxscreeps `{ error: "actually, it was fine" }` sentinel (returned with status 200 by the `create-construction` route to signal success) as a successful response instead of throwing.
|
|
65
|
+
|
|
3
66
|
## 0.13.0
|
|
4
67
|
|
|
5
68
|
### Minor Changes
|
package/package.json
CHANGED
package/src/http/HttpClient.ts
CHANGED
|
@@ -11,6 +11,12 @@ import { createRegisterEndpoints, type RegisterEndpoints } from './endpoints/reg
|
|
|
11
11
|
import type { HttpClientEvents } from '../types/events.js'
|
|
12
12
|
import type { Subscription } from '../subscription/index.js'
|
|
13
13
|
|
|
14
|
+
/** xxscreeps' /api/game/create-construction route returns this in an { error }
|
|
15
|
+
* field with status 200 to signal success. Official Screeps inserts the site
|
|
16
|
+
* directly and returns an id, but xxscreeps returns this fake error and relies
|
|
17
|
+
* on the room socket to show the site next tick. It is not a real error. */
|
|
18
|
+
const XXSCREEPS_FALSE_ERROR = 'actually, it was fine'
|
|
19
|
+
|
|
14
20
|
export interface RateLimitInfo {
|
|
15
21
|
limit: number
|
|
16
22
|
remaining: number
|
|
@@ -133,7 +139,8 @@ export class HttpClient extends EventTarget {
|
|
|
133
139
|
if (!res.ok && res.status !== 304) {
|
|
134
140
|
let body = ''
|
|
135
141
|
try { body = await res.text() } catch { /* ignore */ }
|
|
136
|
-
const error = new Error(`HTTP ${res.status}: ${body}`)
|
|
142
|
+
const error = new Error(`HTTP ${res.status}: ${body}`) as Error & { status?: number }
|
|
143
|
+
error.status = res.status
|
|
137
144
|
this.emit('http:error', { method, path, status: res.status, error, silent: opts.silent })
|
|
138
145
|
throw error
|
|
139
146
|
}
|
|
@@ -142,6 +149,13 @@ export class HttpClient extends EventTarget {
|
|
|
142
149
|
|
|
143
150
|
const data = await res.json() as Record<string, unknown>
|
|
144
151
|
|
|
152
|
+
// xxscreeps' create-construction route returns { error: 'actually, it was
|
|
153
|
+
// fine' } with status 200 to signal success (see XXSCREEPS_FALSE_ERROR).
|
|
154
|
+
// Treat this sentinel as success, not an error.
|
|
155
|
+
if (data['error'] === XXSCREEPS_FALSE_ERROR) {
|
|
156
|
+
delete data['error']
|
|
157
|
+
}
|
|
158
|
+
|
|
145
159
|
if (typeof data['error'] === 'string') {
|
|
146
160
|
const error = new Error(`Screeps API error: ${data['error']}`)
|
|
147
161
|
this.emit('http:error', { method, path, status: res.status, error, silent: opts.silent })
|
|
@@ -19,6 +19,7 @@ import type {
|
|
|
19
19
|
ApiMarketOrdersResponse,
|
|
20
20
|
ApiMarketMyOrdersResponse,
|
|
21
21
|
ApiMarketStatsResponse,
|
|
22
|
+
ApiRoomOverviewResponse,
|
|
22
23
|
} from '../../types/api.js'
|
|
23
24
|
import { createPowerCreepsEndpoints, type PowerCreepsEndpoints } from './power-creeps.js'
|
|
24
25
|
|
|
@@ -28,7 +29,7 @@ export interface GameEndpoints {
|
|
|
28
29
|
roomObjects(room: string, shard?: string | null): Promise<ApiRoomObjectsResponse>
|
|
29
30
|
roomDecorations(room: string, shard?: string | null): Promise<ApiRoomDecorationsResponse>
|
|
30
31
|
roomStatus(room: string, shard?: string | null): Promise<{ ok: number; status: string; novice?: string }>
|
|
31
|
-
roomOverview(room: string, interval?: number, shard?: string | null): Promise<
|
|
32
|
+
roomOverview(room: string, interval?: number, shard?: string | null): Promise<ApiRoomOverviewResponse>
|
|
32
33
|
time(shard?: string | null): Promise<{ ok: number; time: number }>
|
|
33
34
|
tick(): Promise<ApiGameTickResponse>
|
|
34
35
|
worldSize(shard?: string | null): Promise<unknown>
|
|
@@ -37,8 +38,8 @@ export interface GameEndpoints {
|
|
|
37
38
|
createFlag(room: string, x: number, y: number, name: string, color: number, secondaryColor: number, shard?: string | null): Promise<ApiCreateFlagResponse>
|
|
38
39
|
genUniqueFlagName(): Promise<ApiGenUniqueFlagNameResponse>
|
|
39
40
|
checkUniqueFlagName(name: string): Promise<ApiCheckUniqueFlagNameResponse>
|
|
40
|
-
changeFlagColor(room: string, name: string, color: number, secondaryColor: number): Promise<ApiChangeFlagColorResponse>
|
|
41
|
-
removeFlag(room: string, name: string): Promise<ApiRemoveFlagResponse>
|
|
41
|
+
changeFlagColor(room: string, name: string, color: number, secondaryColor: number, shard?: string | null): Promise<ApiChangeFlagColorResponse>
|
|
42
|
+
removeFlag(room: string, name: string, shard?: string | null): Promise<ApiRemoveFlagResponse>
|
|
42
43
|
genUniqueObjectName(type: string, shard?: string | null): Promise<ApiGenUniqueObjectNameResponse>
|
|
43
44
|
checkUniqueObjectName(type: string, name: string, shard?: string | null): Promise<ApiCheckUniqueObjectNameResponse>
|
|
44
45
|
placeSpawn(room: string, x: number, y: number, name?: string, shard?: string | null): Promise<{ ok: number }>
|
|
@@ -88,8 +89,8 @@ export function createGameEndpoints(http: HttpClient, decorationsMock?: ApiRoomD
|
|
|
88
89
|
createFlag: (room, x, y, name, color, secondaryColor, shard) => http.request('POST', '/api/game/create-flag', withShard({ room, x, y, name, color, secondaryColor }, shard)),
|
|
89
90
|
genUniqueFlagName: () => http.request('POST', '/api/game/gen-unique-flag-name'),
|
|
90
91
|
checkUniqueFlagName: (name) => http.request('POST', '/api/game/check-unique-flag-name', { name }),
|
|
91
|
-
changeFlagColor: (room, name, color, secondaryColor) => http.request('POST', '/api/game/change-flag-color', { room, name, color, secondaryColor }),
|
|
92
|
-
removeFlag: (room, name) => http.request('POST', '/api/game/remove-flag', { room, name }),
|
|
92
|
+
changeFlagColor: (room, name, color, secondaryColor, shard) => http.request('POST', '/api/game/change-flag-color', withShard({ room, name, color, secondaryColor }, shard)),
|
|
93
|
+
removeFlag: (room, name, shard) => http.request('POST', '/api/game/remove-flag', withShard({ room, name }, shard)),
|
|
93
94
|
genUniqueObjectName: (type, shard) => http.request('POST', '/api/game/gen-unique-object-name', withShard({ type }, shard)),
|
|
94
95
|
checkUniqueObjectName: (type, name, shard) => http.request('POST', '/api/game/check-unique-object-name', withShard({ type, name }, shard)),
|
|
95
96
|
placeSpawn: (room, x, y, name, shard) => http.request('POST', '/api/game/place-spawn', withShard({ room, x, y, ...(name ? { name } : {}) }, shard)),
|
|
@@ -98,9 +99,11 @@ export function createGameEndpoints(http: HttpClient, decorationsMock?: ApiRoomD
|
|
|
98
99
|
addObjectIntent: (id, room, name, intent, shard) => http.request('POST', '/api/game/add-object-intent', withShard({ _id: id, room, name, intent }, shard)),
|
|
99
100
|
addGlobalIntent: (name, intent) => http.request('POST', '/api/game/add-global-intent', { name, intent }),
|
|
100
101
|
roomHistory: (room, time, shard) =>
|
|
102
|
+
// silent: a missing chunk 404s while history is still being written; the caller
|
|
103
|
+
// handles that gracefully, so don't surface a global "request failed" toast.
|
|
101
104
|
shard
|
|
102
|
-
? http.request('GET', `/room-history/${encodeURIComponent(shard)}/${encodeURIComponent(room)}/${time}.json
|
|
103
|
-
: http.request('GET', '/room-history', { room, time }),
|
|
105
|
+
? http.request('GET', `/room-history/${encodeURIComponent(shard)}/${encodeURIComponent(room)}/${time}.json`, undefined, { silent: true })
|
|
106
|
+
: http.request('GET', '/room-history', { room, time }, { silent: true }),
|
|
104
107
|
setNotifyWhenAttacked: (id, enabled) => http.request('POST', '/api/game/set-notify-when-attacked', { _id: id, enabled }),
|
|
105
108
|
createInvader: (room, x, y, size, type, boosted) => http.request('POST', '/api/game/create-invader', { room, x, y, size, type, ...(boosted != null ? { boosted } : {}) }),
|
|
106
109
|
removeInvader: (id) => http.request('POST', '/api/game/remove-invader', { _id: id }),
|
|
@@ -206,3 +206,16 @@ export function getXxscreepsModClientFeature(version: ServerVersion): XxscreepsM
|
|
|
206
206
|
export function getDiscordFeature(version: ServerVersion): DiscordFeature | undefined {
|
|
207
207
|
return getServerFeature<DiscordFeature>(version, 'discord')
|
|
208
208
|
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Returns `true` when the server advertises the `official-like` feature — the
|
|
212
|
+
* marker xxscreeps servers publish at `/api/version`. Such servers do not
|
|
213
|
+
* implement the screepsmod-auth connection ("server") password, so the client
|
|
214
|
+
* can hide that field entirely.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* if (hasOfficialLike(version)) hideServerPasswordField()
|
|
218
|
+
*/
|
|
219
|
+
export function hasOfficialLike(version: ServerVersion): boolean {
|
|
220
|
+
return getServerFeature(version, 'official-like') !== undefined
|
|
221
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -42,9 +42,9 @@ export type {
|
|
|
42
42
|
MapVisualEntry,
|
|
43
43
|
} from './types/game.js'
|
|
44
44
|
|
|
45
|
-
export { fetchServerVersion, fetchAuthModInfo, checkUsername, checkEmail, registerUser, fetchAuthMeWithToken, completeProviderRegistration, getServerFeature, getScreepsmodAuth, getXxscreepsModClientFeature, getDiscordFeature } from './http/fetchServerVersion.js'
|
|
45
|
+
export { fetchServerVersion, fetchAuthModInfo, checkUsername, checkEmail, registerUser, fetchAuthMeWithToken, completeProviderRegistration, getServerFeature, getScreepsmodAuth, getXxscreepsModClientFeature, getDiscordFeature, hasOfficialLike } from './http/fetchServerVersion.js'
|
|
46
46
|
export type { ApiAuthModInfoResponse } from './http/fetchServerVersion.js'
|
|
47
|
-
export type { ApiAuthMeResponse, RoomHistoryChunk, ApiRoomDecorationsResponse, ApiRoomDecorationItem, ApiRoomDecorationDef, ApiRoomDecorationGraphic, ApiRoomDecorationActive, ApiUserOverviewResponse, ApiUserOverviewTotals, ApiUserRoomsResponse, ApiUserStatsResponse, ApiLeaderboardFindResponse, ApiUserMoneyHistoryResponse, ApiMarketOrder, ApiMarketOrdersIndexResponse, ApiMarketOrdersResponse, ApiMarketMyOrdersResponse, ApiMarketStat, ApiMarketStatsResponse, ApiPowerCreep, ApiPowerCreepsListResponse, ApiUserMessage, ApiUserMessagesListResponse, ApiUserMessagesIndexEntry, ApiUserMessagesIndexResponse, ApiUserMessagesUnreadCountResponse, ApiUserFindResponse } from './types/api.js'
|
|
47
|
+
export type { ApiAuthMeResponse, RoomHistoryChunk, ApiRoomDecorationsResponse, ApiRoomDecorationItem, ApiRoomDecorationDef, ApiRoomDecorationGraphic, ApiRoomDecorationActive, ApiUserOverviewResponse, ApiUserOverviewTotals, ApiRoomOverviewResponse, ApiUserRoomsResponse, ApiUserStatsResponse, ApiLeaderboardFindResponse, ApiUserMoneyHistoryResponse, ApiMarketOrder, ApiMarketOrdersIndexResponse, ApiMarketOrdersResponse, ApiMarketMyOrdersResponse, ApiMarketStat, ApiMarketStatsResponse, ApiPowerCreep, ApiPowerCreepsListResponse, ApiUserMessage, ApiUserMessagesListResponse, ApiUserMessagesIndexEntry, ApiUserMessagesIndexResponse, ApiUserMessagesUnreadCountResponse, ApiUserFindResponse } from './types/api.js'
|
|
48
48
|
export { ROOM_DECORATIONS_MOCK } from './mocks/roomDecorations.js'
|
|
49
49
|
|
|
50
50
|
export { badgeToSvg } from './badge/index.js'
|
|
@@ -79,7 +79,7 @@ export class SocketClient {
|
|
|
79
79
|
this.authed = false
|
|
80
80
|
this.authSub?.dispose()
|
|
81
81
|
this.authSub = null
|
|
82
|
-
this.emit('disconnected', { willReconnect: this.reconnecting })
|
|
82
|
+
this.emit('disconnected', { willReconnect: this.reconnecting, intentional: this._intentionalClose })
|
|
83
83
|
void this.scheduleReconnect()
|
|
84
84
|
}
|
|
85
85
|
this.ws.onerror = (err) => {
|
|
@@ -29,8 +29,8 @@ export class ServerStore extends TypedStore<ServerStoreEvents> {
|
|
|
29
29
|
this.emit('server:connected', {})
|
|
30
30
|
}))
|
|
31
31
|
this.socketSubs.push(socket.on('disconnected', (data) => {
|
|
32
|
-
const d = data as { willReconnect: boolean }
|
|
33
|
-
this.emit('server:disconnected', { willReconnect: d.willReconnect })
|
|
32
|
+
const d = data as { willReconnect: boolean; intentional?: boolean }
|
|
33
|
+
this.emit('server:disconnected', { willReconnect: d.willReconnect, intentional: d.intentional ?? false })
|
|
34
34
|
}))
|
|
35
35
|
this.socketSubs.push(socket.on('socket:error', (data) => {
|
|
36
36
|
this.emit('server:error', { error: data instanceof Error ? data : new Error(String(data)) })
|
package/src/types/api.ts
CHANGED
|
@@ -46,6 +46,15 @@ export interface ApiUserOverviewResponse {
|
|
|
46
46
|
statsMax?: number
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/** Response of GET /api/game/room-overview?room=&interval=&shard=. `stats` holds per-stat time-series buckets over the requested interval (same metric keys as the account overview tiles); `owner` is absent for unowned rooms. */
|
|
50
|
+
export interface ApiRoomOverviewResponse {
|
|
51
|
+
ok: number
|
|
52
|
+
owner?: { username: string; badge: import('./game.js').Badge } | null
|
|
53
|
+
stats?: Record<string, Array<{ value: number; endTime: number }>>
|
|
54
|
+
statsMax?: Record<string, number>
|
|
55
|
+
totals?: ApiUserOverviewTotals
|
|
56
|
+
}
|
|
57
|
+
|
|
49
58
|
/** Response of GET /api/user/rooms?id=<userId> — the rooms a user owns. Multishard servers key by shard; single-shard servers may return a flat list. */
|
|
50
59
|
export interface ApiUserRoomsResponse {
|
|
51
60
|
ok: number
|
|
@@ -102,6 +111,13 @@ export interface ApiVersionResponse {
|
|
|
102
111
|
users: number
|
|
103
112
|
serverData: {
|
|
104
113
|
historyChunkSize: number
|
|
114
|
+
/**
|
|
115
|
+
* Retention window for room history, in ticks. Non-official field added by the
|
|
116
|
+
* xxscreeps history mod. When present, the earliest replayable tick is roughly
|
|
117
|
+
* `currentTick - historyKeepTicks`; a value of `0` means history is kept forever
|
|
118
|
+
* (unbounded). Absent on servers without the mod.
|
|
119
|
+
*/
|
|
120
|
+
historyKeepTicks?: number
|
|
105
121
|
features: Array<{ name: string }>
|
|
106
122
|
shards: string[]
|
|
107
123
|
customObjectTypes?: unknown
|
|
@@ -264,10 +280,14 @@ export interface ApiUserFindResponse {
|
|
|
264
280
|
}
|
|
265
281
|
}
|
|
266
282
|
|
|
267
|
-
/**
|
|
283
|
+
/**
|
|
284
|
+
* Response of GET /api/user/stats?id=&interval=. `stats` maps a metric to
|
|
285
|
+
* either per-tick buckets (consumers sum the bucket values over the interval)
|
|
286
|
+
* or a single pre-summed total for the interval — servers differ on which.
|
|
287
|
+
*/
|
|
268
288
|
export interface ApiUserStatsResponse {
|
|
269
289
|
ok: number
|
|
270
|
-
stats?: Record<string, Array<{ value: number; endTime?: number }>>
|
|
290
|
+
stats?: Record<string, number | Array<{ value: number; endTime?: number }>>
|
|
271
291
|
statsMax?: Record<string, number>
|
|
272
292
|
}
|
|
273
293
|
|
package/src/types/events.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface UserStoreEvents {
|
|
|
47
47
|
|
|
48
48
|
export interface ServerStoreEvents {
|
|
49
49
|
'server:connected': Record<string, never>
|
|
50
|
-
'server:disconnected': { willReconnect: boolean }
|
|
50
|
+
'server:disconnected': { willReconnect: boolean; intentional: boolean }
|
|
51
51
|
'server:error': { error: Error }
|
|
52
52
|
'server:version': ServerVersion
|
|
53
53
|
'server:shards': ShardInfo[]
|
package/src/types/game.ts
CHANGED
|
@@ -145,6 +145,14 @@ export interface ServerVersion {
|
|
|
145
145
|
users: number
|
|
146
146
|
serverData: {
|
|
147
147
|
historyChunkSize: number
|
|
148
|
+
/**
|
|
149
|
+
* Retention window for room history, in ticks. Non-official field added by the
|
|
150
|
+
* xxscreeps history mod. When present, the earliest replayable tick is roughly
|
|
151
|
+
* `currentTick - historyKeepTicks`; a value of `0` means history is kept forever
|
|
152
|
+
* (unbounded). Absent on servers without the mod — callers should fall back to a
|
|
153
|
+
* sensible default in that case.
|
|
154
|
+
*/
|
|
155
|
+
historyKeepTicks?: number
|
|
148
156
|
features: ServerFeature[]
|
|
149
157
|
shards: Array<string | null>
|
|
150
158
|
welcomeText?: string
|
|
@@ -205,6 +205,16 @@ describe('HttpClient', () => {
|
|
|
205
205
|
await expect(http.request('GET', '/api/game/create-flag')).rejects.toThrow('Screeps API error: invalid params')
|
|
206
206
|
})
|
|
207
207
|
|
|
208
|
+
it('treats the xxscreeps "actually, it was fine" sentinel as success', async () => {
|
|
209
|
+
fetchMock.mockResolvedValue(mockResponse({ error: 'actually, it was fine' }))
|
|
210
|
+
const http = new HttpClient({ url: 'http://test.local', auth: new TokenAuth({ token: 't' }) })
|
|
211
|
+
const errorHandler = vi.fn()
|
|
212
|
+
http.on('http:error', errorHandler)
|
|
213
|
+
const res = await http.request<{ error?: string }>('POST', '/api/game/create-construction')
|
|
214
|
+
expect(res.error).toBeUndefined()
|
|
215
|
+
expect(errorHandler).not.toHaveBeenCalled()
|
|
216
|
+
})
|
|
217
|
+
|
|
208
218
|
it('emits http:error on 200 with error in response body', async () => {
|
|
209
219
|
fetchMock.mockResolvedValue(mockResponse({ ok: 0, error: 'flags limit exceeded' }))
|
|
210
220
|
const http = new HttpClient({ url: 'http://test.local', auth: new TokenAuth({ token: 't' }) })
|
|
@@ -141,4 +141,15 @@ describe('SocketClient', () => {
|
|
|
141
141
|
client.disconnect()
|
|
142
142
|
expect(willReconnect).toBe(false)
|
|
143
143
|
})
|
|
144
|
+
|
|
145
|
+
it('disconnected event is flagged intentional when disconnect() is called', async () => {
|
|
146
|
+
const client = makeClient()
|
|
147
|
+
const _ws = await connectClient(client)
|
|
148
|
+
let intentional: boolean | undefined
|
|
149
|
+
client.on('disconnected', (data) => {
|
|
150
|
+
intentional = (data as { intentional: boolean }).intentional
|
|
151
|
+
})
|
|
152
|
+
client.disconnect()
|
|
153
|
+
expect(intentional).toBe(true)
|
|
154
|
+
})
|
|
144
155
|
})
|