screeps-connectivity 0.13.1 → 0.15.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 +58 -0
- package/LICENSE +15 -0
- package/package.json +14 -14
- package/src/ScreepsClient.ts +9 -1
- package/src/http/endpoints/game.ts +6 -5
- package/src/http/fetchServerVersion.ts +13 -0
- package/src/index.ts +2 -2
- package/src/socket/SocketClient.ts +10 -1
- package/src/stores/UserStore.ts +3 -1
- package/src/types/api.ts +15 -2
- package/src/types/events.ts +1 -0
- package/tests/socket/SocketClient.test.ts +25 -0
- package/tsconfig.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 66f88f8: Code panel branch management: create a new branch (clones the selected branch, with an inline name input) and set the selected branch to run on the server. The active-branch indicator now stays live via a new `set-active-branch` WebSocket subscription — `UserStore.subscribe('set-active-branch')` emits a `user:setActiveBranch` event whenever the active branch changes, including from another client or session.
|
|
8
|
+
|
|
9
|
+
The code panel can now add and delete modules: an add button in the module list opens an inline name input, and hovering a module reveals a delete button (the `main` entry module is protected). Both changes are staged locally and persisted on the next Save.
|
|
10
|
+
|
|
11
|
+
Also fixes a stale-response race in the code panel where switching branches while a previous branch's code fetch was still in flight could leave the editor showing the wrong branch's files.
|
|
12
|
+
|
|
13
|
+
- 66f88f8: WebSocket compression support (opt-in): setting the new `ScreepsClientOptions.gzip` to `true` sends `gzip on` after auth, so the server deflates event frames (room updates, map stats, memory) and only transmits the compressed `gz:` form when it's actually smaller. Defaults to `false` to match the official client, which never enables it. The `gz:` decode path is always active regardless, so this is a pure opt-in bandwidth trade with no downside on small control frames.
|
|
14
|
+
|
|
15
|
+
## 0.14.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- 57a7e1d: Hide the connection ("server") password field for xxscreeps servers.
|
|
20
|
+
|
|
21
|
+
`screeps-connectivity` adds a `hasOfficialLike(version)` capability helper that
|
|
22
|
+
detects the `official-like` feature advertised at `/api/version`. The login
|
|
23
|
+
screens (web and desktop) now use it to hide the server-password field on
|
|
24
|
+
xxscreeps servers, where the screepsmod-auth connection password does not apply.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- 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.
|
|
29
|
+
- 3b9b951: Revamp the public profile page (`/profile/<username>`):
|
|
30
|
+
|
|
31
|
+
- Header now mirrors the self Overview chrome — small badge, the player's name
|
|
32
|
+
as the title, and a compact GCL/GPL readout rendered as rounded chips bordered
|
|
33
|
+
in the rank color (teal / red).
|
|
34
|
+
- The "Last 7 days" stat block is now a dropdown (1 hour / 24 hours / 7 days);
|
|
35
|
+
the tiles refetch the user's public stats for the selected window.
|
|
36
|
+
- Stat tiles now render correctly on servers that return `/api/user/stats`
|
|
37
|
+
metrics as a single pre-summed total per interval, not just per-tick buckets
|
|
38
|
+
(`ApiUserStatsResponse.stats` widened to `number | bucket[]`).
|
|
39
|
+
- Cross-links between the two account views: the username on the self Overview
|
|
40
|
+
links to the public profile, and your own public profile links back to your
|
|
41
|
+
private Overview.
|
|
42
|
+
- The top-bar Overview button no longer appears active while a profile is open —
|
|
43
|
+
a profile is a separate view.
|
|
44
|
+
|
|
45
|
+
- 79bd3d0: Add a read-only per-room overview page at `/room-overview/<shard>/<room>` (or
|
|
46
|
+
`/room-overview/<room>` on single-shard servers):
|
|
47
|
+
|
|
48
|
+
- Header with the room name, owner (badge + profile link, or "Unclaimed room"),
|
|
49
|
+
and a room minimap thumbnail that opens the live room view.
|
|
50
|
+
- The same seven stat tiles as the account Overview, summed over a selectable
|
|
51
|
+
interval (1 hour / 24 hours / 7 days).
|
|
52
|
+
- A history graph rendering the six per-bucket metrics (energy harvested,
|
|
53
|
+
construction, control, energy on creeps, creeps produced/lost) as
|
|
54
|
+
opacity-scaled dot strips.
|
|
55
|
+
- Entry points: a chart button next to each room name on the self Overview and
|
|
56
|
+
public Profile pages, plus a button in the in-game room view's left toolbar
|
|
57
|
+
(between the World Map and History buttons).
|
|
58
|
+
- `screeps-connectivity`: the existing `game.roomOverview` endpoint is now typed
|
|
59
|
+
with the new exported `ApiRoomOverviewResponse` (was `unknown`).
|
|
60
|
+
|
|
3
61
|
## 0.13.1
|
|
4
62
|
|
|
5
63
|
### Patch Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
ISC License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026 Bastian Hoyer
|
|
4
|
+
|
|
5
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
6
|
+
purpose with or without fee is hereby granted, provided that the above
|
|
7
|
+
copyright notice and this permission notice appear in all copies.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
10
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
11
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
12
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
13
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
14
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
15
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screeps-connectivity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -24,22 +24,22 @@
|
|
|
24
24
|
"require": "./dist/file-storage.cjs"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@eslint/js": "^10.0.1",
|
|
29
|
+
"@types/node": "^26.1.0",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^8.63.0",
|
|
31
|
+
"@typescript-eslint/parser": "^8.63.0",
|
|
32
|
+
"eslint": "^10.6.0",
|
|
33
|
+
"fake-indexeddb": "^6.0.0",
|
|
34
|
+
"globals": "^17.7.0",
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^6.0.3",
|
|
37
|
+
"vitest": "^4.1.10"
|
|
38
|
+
},
|
|
27
39
|
"scripts": {
|
|
28
40
|
"build": "tsup",
|
|
29
41
|
"test": "vitest run",
|
|
30
42
|
"test:watch": "vitest",
|
|
31
43
|
"lint": "eslint src tests"
|
|
32
|
-
},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@eslint/js": "^10.0.1",
|
|
35
|
-
"@types/node": "^25.7.0",
|
|
36
|
-
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
37
|
-
"@typescript-eslint/parser": "^8.0.0",
|
|
38
|
-
"eslint": "^9.0.0",
|
|
39
|
-
"fake-indexeddb": "^6.0.0",
|
|
40
|
-
"globals": "^17.6.0",
|
|
41
|
-
"tsup": "^8.0.0",
|
|
42
|
-
"typescript": "^5.5.0",
|
|
43
|
-
"vitest": "^4.1.6"
|
|
44
44
|
}
|
|
45
|
-
}
|
|
45
|
+
}
|
package/src/ScreepsClient.ts
CHANGED
|
@@ -41,6 +41,14 @@ export interface ScreepsClientOptions {
|
|
|
41
41
|
tokenRefresh?: TokenRefreshOptions | false
|
|
42
42
|
/** Override the /api/game/room-decorations response with static data (useful for dev/testing when the server doesn't support the endpoint). */
|
|
43
43
|
decorationsMock?: ApiRoomDecorationsResponse
|
|
44
|
+
/**
|
|
45
|
+
* Enable WebSocket message compression by sending `gzip on` after auth. The
|
|
46
|
+
* server then deflates event frames (room updates, etc.) and only sends the
|
|
47
|
+
* compressed `gz:` form when it's smaller. Defaults to `false` — matching the
|
|
48
|
+
* official client, which never enables it. The `gz:` decode path is always
|
|
49
|
+
* active regardless, so enabling this is a pure opt-in bandwidth trade.
|
|
50
|
+
*/
|
|
51
|
+
gzip?: boolean
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export class ScreepsClient {
|
|
@@ -77,7 +85,7 @@ export class ScreepsClient {
|
|
|
77
85
|
this.logger.log(`[screeps:client] init ${opts.url}`)
|
|
78
86
|
this.cache = new Cache(namespace, opts.storage ?? null)
|
|
79
87
|
this.http = new HttpClient({ url: opts.url, auth: opts.auth, logger: this.logger.child('http'), serverPassword: opts.serverPassword, decorationsMock: opts.decorationsMock })
|
|
80
|
-
this.socket = new SocketClient({ url: opts.url, WebSocket: opts.WebSocket, logger: this.logger.child('socket') })
|
|
88
|
+
this.socket = new SocketClient({ url: opts.url, WebSocket: opts.WebSocket, logger: this.logger.child('socket'), gzip: opts.gzip })
|
|
81
89
|
const map2Storage = new Map2Storage({
|
|
82
90
|
adapter: opts.storage ?? null,
|
|
83
91
|
namespace,
|
|
@@ -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)),
|
|
@@ -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'
|
|
@@ -8,6 +8,7 @@ export class SocketClient {
|
|
|
8
8
|
private readonly wsUrl: string
|
|
9
9
|
private readonly WS: WsConstructor
|
|
10
10
|
private readonly logger: Logger
|
|
11
|
+
private readonly gzip: boolean
|
|
11
12
|
private ws: WebSocket | null = null
|
|
12
13
|
private token: string | null = null
|
|
13
14
|
private authed = false
|
|
@@ -22,11 +23,14 @@ export class SocketClient {
|
|
|
22
23
|
private readonly MAX_DELAY_MS = 60_000
|
|
23
24
|
private _intentionalClose = false
|
|
24
25
|
|
|
25
|
-
constructor(opts: { url: string; WebSocket?: WsConstructor; logger?: Logger }) {
|
|
26
|
+
constructor(opts: { url: string; WebSocket?: WsConstructor; logger?: Logger; gzip?: boolean }) {
|
|
26
27
|
const base = opts.url.replace(/^http/, 'ws').replace(/\/$/, '')
|
|
27
28
|
this.wsUrl = `${base}/socket/websocket`
|
|
28
29
|
this.WS = opts.WebSocket ?? globalThis.WebSocket
|
|
29
30
|
this.logger = opts.logger ?? Logger.create()
|
|
31
|
+
// Off by default — matches the official client, which never sends `gzip on`.
|
|
32
|
+
// The decode path is always available; opt in explicitly to enable it.
|
|
33
|
+
this.gzip = opts.gzip ?? false
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
get isConnected(): boolean {
|
|
@@ -64,6 +68,11 @@ export class SocketClient {
|
|
|
64
68
|
this.token = cmd.token
|
|
65
69
|
this.emit('socket:tokenRefresh', { token: cmd.token })
|
|
66
70
|
}
|
|
71
|
+
// Ask the server to deflate outbound event frames before any
|
|
72
|
+
// subscribe is flushed, so the first updates already arrive as gz:.
|
|
73
|
+
// The server only sends the compressed form when it's actually
|
|
74
|
+
// smaller, so this never enlarges small control frames.
|
|
75
|
+
if (this.gzip) this.rawSend('gzip on')
|
|
67
76
|
while (this.queue.length) this.rawSend(this.queue.shift()!)
|
|
68
77
|
this.emit('connected', {})
|
|
69
78
|
resolve()
|
package/src/stores/UserStore.ts
CHANGED
|
@@ -82,7 +82,7 @@ export class UserStore extends TypedStore<UserStoreEvents> {
|
|
|
82
82
|
return this.worldStatus()
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
subscribe(channel: 'console' | 'cpu' | 'code'): Subscription {
|
|
85
|
+
subscribe(channel: 'console' | 'cpu' | 'code' | 'set-active-branch'): Subscription {
|
|
86
86
|
this.logger.log('subscribe', channel)
|
|
87
87
|
let socketSub: Subscription | null = null
|
|
88
88
|
let listenerSub: Subscription | null = null
|
|
@@ -115,6 +115,8 @@ export class UserStore extends TypedStore<UserStoreEvents> {
|
|
|
115
115
|
this.emit('user:console', { messages: msg })
|
|
116
116
|
} else if (channel === 'code') {
|
|
117
117
|
this.emit('user:code', data as { branch: string; modules: Record<string, string> })
|
|
118
|
+
} else if (channel === 'set-active-branch') {
|
|
119
|
+
this.emit('user:setActiveBranch', data as { activeName: 'activeWorld' | 'activeSim'; branch: string })
|
|
118
120
|
}
|
|
119
121
|
})
|
|
120
122
|
} catch (err) {
|
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
|
|
@@ -271,10 +280,14 @@ export interface ApiUserFindResponse {
|
|
|
271
280
|
}
|
|
272
281
|
}
|
|
273
282
|
|
|
274
|
-
/**
|
|
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
|
+
*/
|
|
275
288
|
export interface ApiUserStatsResponse {
|
|
276
289
|
ok: number
|
|
277
|
-
stats?: Record<string, Array<{ value: number; endTime?: number }>>
|
|
290
|
+
stats?: Record<string, number | Array<{ value: number; endTime?: number }>>
|
|
278
291
|
statsMax?: Record<string, number>
|
|
279
292
|
}
|
|
280
293
|
|
package/src/types/events.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface UserStoreEvents {
|
|
|
40
40
|
'user:cpu': CpuStats
|
|
41
41
|
'user:console': { messages: ConsoleMessage }
|
|
42
42
|
'user:code': { branch: string; modules: Record<string, string> }
|
|
43
|
+
'user:setActiveBranch': { activeName: 'activeWorld' | 'activeSim'; branch: string }
|
|
43
44
|
'user:stream': Record<string, unknown>
|
|
44
45
|
'user:memory': { path: string; shard: string | null; value: unknown }
|
|
45
46
|
'user:mapVisual': { shard: string | null; data: string }
|
|
@@ -58,6 +58,31 @@ describe('SocketClient', () => {
|
|
|
58
58
|
await expect(connectClient(client)).resolves.toBeDefined()
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
+
it('does not send gzip on by default', async () => {
|
|
62
|
+
const client = makeClient()
|
|
63
|
+
const ws = await connectClient(client)
|
|
64
|
+
expect(ws.sent).not.toContain('gzip on')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('sends gzip on when explicitly enabled, before any subscribe', async () => {
|
|
68
|
+
const client = new SocketClient({ url: 'https://screeps.com', gzip: true, WebSocket: MockWS as unknown as typeof WebSocket })
|
|
69
|
+
const connectPromise = client.connect('tok')
|
|
70
|
+
const ws = MockWS.instances[0]
|
|
71
|
+
ws.simulateOpen()
|
|
72
|
+
ws.simulateMessage('auth ok newtoken')
|
|
73
|
+
await connectPromise
|
|
74
|
+
expect(ws.sent).toContain('gzip on')
|
|
75
|
+
// gzip on must precede subscribes so the first updates already arrive as gz:
|
|
76
|
+
client.subscribe('room:shard0/W7N7')
|
|
77
|
+
expect(ws.sent.indexOf('gzip on')).toBeLessThan(ws.sent.indexOf('subscribe room:shard0/W7N7'))
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('explicit gzip:false does not send gzip on', async () => {
|
|
81
|
+
const client = new SocketClient({ url: 'https://screeps.com', gzip: false, WebSocket: MockWS as unknown as typeof WebSocket })
|
|
82
|
+
const ws = await connectClient(client)
|
|
83
|
+
expect(ws.sent).not.toContain('gzip on')
|
|
84
|
+
})
|
|
85
|
+
|
|
61
86
|
it('subscribe sends subscribe message when authed', async () => {
|
|
62
87
|
const client = makeClient()
|
|
63
88
|
const ws = await connectClient(client)
|