screeps-connectivity 0.8.1 → 0.9.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 +6 -0
- package/README.md +182 -0
- package/package.json +1 -1
- package/src/http/HttpClient.ts +2 -1
- package/src/http/fetchFn.ts +17 -0
- package/src/http/fetchServerVersion.ts +6 -5
- package/src/index.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e73c85f: Add `setFetch()` to `screeps-connectivity` so consumers can supply a custom fetch implementation (e.g. Tauri's HTTP plugin) without patching `window.fetch`. `screeps-client` uses this to enable CORS-free HTTP in the standalone Tauri desktop app without affecting the browser runtime.
|
|
8
|
+
|
|
3
9
|
## 0.8.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# screeps-connectivity
|
|
2
|
+
|
|
3
|
+
TypeScript library for connecting to [Screeps](https://screeps.com) servers. Handles HTTP, WebSocket, authentication, data stores, caching, and persistent storage — with **zero production dependencies**.
|
|
4
|
+
|
|
5
|
+
Works in the browser and Node.js. Ships as ESM + CJS via tsup.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install screeps-connectivity
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`FileStorage` (Node.js only) is a separate entry point so browser bundles stay clean:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { FileStorage } from 'screeps-connectivity/file-storage'
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { ScreepsClient, TokenAuth, IndexedDBStorage } from 'screeps-connectivity'
|
|
27
|
+
|
|
28
|
+
const client = new ScreepsClient({
|
|
29
|
+
url: 'https://screeps.com',
|
|
30
|
+
auth: new TokenAuth({ token: 'your-auth-token' }),
|
|
31
|
+
storage: new IndexedDBStorage('my-app'),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Subscribe before connecting — fires once the eager fetch completes
|
|
35
|
+
client.stores.user.on('user:me', (info) => {
|
|
36
|
+
console.log('Connected as', info.username)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
await client.connect()
|
|
40
|
+
|
|
41
|
+
// Load terrain
|
|
42
|
+
const terrain = await client.stores.room.terrain('W7N7', 'shard0')
|
|
43
|
+
|
|
44
|
+
// Subscribe to live room updates
|
|
45
|
+
const roomSub = client.stores.room.subscribe('W7N7', 'shard0')
|
|
46
|
+
client.stores.room.on('room:update', ({ gameTime, objects }) => {
|
|
47
|
+
console.log('Tick', gameTime, '— objects:', Object.keys(objects).length)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// Cleanup
|
|
51
|
+
roomSub.dispose()
|
|
52
|
+
client.disconnect()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
### Authentication
|
|
60
|
+
|
|
61
|
+
Three built-in strategies, or implement your own via the `AuthStrategy` interface:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
// Pre-issued token (official server, private servers with token auth)
|
|
65
|
+
new TokenAuth({ token: 'your-token' })
|
|
66
|
+
|
|
67
|
+
// Email + password — exchanged for a token on connect
|
|
68
|
+
new PasswordAuth({ email: 'user@example.com', password: 'secret' })
|
|
69
|
+
|
|
70
|
+
// Read-only guest access (xxscreeps-compatible private servers only)
|
|
71
|
+
new GuestAuth()
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Screeps servers rotate the session token on every request. `ScreepsClient` tracks the latest token across both HTTP and WebSocket transports, keeps them in sync, and issues an idle keep-alive if no traffic has been seen for 30 seconds.
|
|
75
|
+
|
|
76
|
+
### Stores (event-based data layer)
|
|
77
|
+
|
|
78
|
+
All stores extend `EventTarget`. `store.on(type, handler)` returns a `Subscription` with a `dispose()` method. `SubscriptionGroup` composes multiple subscriptions for batch teardown.
|
|
79
|
+
|
|
80
|
+
| Store | `client.stores.*` | What it manages |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `UserStore` | `user` | Identity, CPU stats, console output, code change events |
|
|
83
|
+
| `ServerStore` | `server` | Server version, shard list, world bounds, connection lifecycle |
|
|
84
|
+
| `RoomStore` | `room` | Terrain (binary), live object state + diffs, subscriptions |
|
|
85
|
+
| `MapStore` | `map` | `roomMap2` mini-map data, diff detection, LRU cache |
|
|
86
|
+
| `NavigationStore` | `navigation` | Bounded room-navigation history with back/forward |
|
|
87
|
+
|
|
88
|
+
### Room subscriptions
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
// Terrain — Uint8Array(2500), one byte per tile (values 0–3)
|
|
92
|
+
const terrain = await client.stores.room.terrain('W7N7', 'shard0')
|
|
93
|
+
|
|
94
|
+
// Live objects — first message is full state; subsequent messages are diffs
|
|
95
|
+
const sub = client.stores.room.subscribe('W7N7', 'shard0')
|
|
96
|
+
client.stores.room.on('room:update', ({ gameTime, objects, diff }) => { /* … */ })
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### World map subscriptions
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
// Subscribe up to 500 concurrent roomMap2 channels (configurable)
|
|
103
|
+
// Excess rooms queue on a FIFO waitlist and are promoted as slots free
|
|
104
|
+
client.stores.map.subscribe('W7N7', 'shard0')
|
|
105
|
+
client.stores.map.on('map:roomMap2', ({ roomName, data }) => { /* … */ })
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### World bounds
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// Detects which quadrants (E/W, N/S) actually contain rooms
|
|
112
|
+
// Works for standard servers, private servers, and single-quadrant maps
|
|
113
|
+
const info = await client.stores.server.worldInfo()
|
|
114
|
+
// → { width, height, minX, maxX, minY, maxY }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Storage adapters
|
|
118
|
+
|
|
119
|
+
| Adapter | Environment | Notes |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `IndexedDBStorage` | Browser | Persists terrain and map cache across reloads |
|
|
122
|
+
| `FileStorage` | Node.js | Import from `screeps-connectivity/file-storage` |
|
|
123
|
+
| `NullStorage` | Any | Disables persistence (in-memory only) |
|
|
124
|
+
|
|
125
|
+
Implement `StorageAdapter` (`get`, `set`, `delete`, `keys`) to plug in any backend.
|
|
126
|
+
|
|
127
|
+
### Pre-login server info
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
import { fetchServerVersion, getScreepsmodAuth } from 'screeps-connectivity'
|
|
131
|
+
|
|
132
|
+
const version = await fetchServerVersion('http://my-server:21025')
|
|
133
|
+
const auth = getScreepsmodAuth(version)
|
|
134
|
+
// auth?.authTypes → ['password', 'steam']
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Architecture
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
ScreepsClient — facade; wires everything together
|
|
143
|
+
├─ HttpClient — fetch wrapper, auth headers, rate limiting, gzip
|
|
144
|
+
│ └─ endpoints/ — auth · game · user · leaderboard · experimental
|
|
145
|
+
└─ SocketClient — WebSocket lifecycle, exponential-backoff reconnect
|
|
146
|
+
└─ MessageParser — plain-text commands + JSON-array messages, gzip
|
|
147
|
+
DataStores — UserStore · ServerStore · RoomStore · MapStore · NavigationStore
|
|
148
|
+
Cache — two-tier: in-memory Map + optional StorageAdapter
|
|
149
|
+
StorageAdapter — binary interface (Uint8Array)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Node.js usage
|
|
155
|
+
|
|
156
|
+
Pass a custom `WebSocket` constructor for Node 18/20 compatibility:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
import { WebSocket } from 'ws'
|
|
160
|
+
import { ScreepsClient, TokenAuth, FileStorage } from 'screeps-connectivity'
|
|
161
|
+
// FileStorage is a separate entry point:
|
|
162
|
+
// import { FileStorage } from 'screeps-connectivity/file-storage'
|
|
163
|
+
|
|
164
|
+
const client = new ScreepsClient({
|
|
165
|
+
url: 'https://screeps.com',
|
|
166
|
+
auth: new TokenAuth({ token: 'your-token' }),
|
|
167
|
+
storage: new FileStorage('./screeps-cache'),
|
|
168
|
+
WebSocket,
|
|
169
|
+
})
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## API reference
|
|
175
|
+
|
|
176
|
+
Full documentation — all stores, events, options, and types — is in [`docs/screeps-connectivity.md`](../docs/screeps-connectivity.md).
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
ISC
|
package/package.json
CHANGED
package/src/http/HttpClient.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decompressGzip } from './decompress.js'
|
|
2
|
+
import { getFetch } from './fetchFn.js'
|
|
2
3
|
import { Logger } from '../logger.js'
|
|
3
4
|
import type { AuthStrategy } from './auth/AuthStrategy.js'
|
|
4
5
|
import { createAuthEndpoints, type AuthEndpoints } from './endpoints/auth.js'
|
|
@@ -113,7 +114,7 @@ export class HttpClient extends EventTarget {
|
|
|
113
114
|
init.body = JSON.stringify(body)
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
const res = await
|
|
117
|
+
const res = await getFetch()(url.toString(), init)
|
|
117
118
|
|
|
118
119
|
const newToken = res.headers.get('x-token')
|
|
119
120
|
if (newToken) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
let _fetch: typeof globalThis.fetch | undefined
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Override the fetch implementation used by all screeps-connectivity HTTP calls.
|
|
5
|
+
* Call this once at app startup when the platform requires a custom transport
|
|
6
|
+
* (e.g. Tauri desktop, where the native WKWebView fetch cannot reach cross-origin
|
|
7
|
+
* Screeps servers and the Tauri HTTP plugin must be used instead).
|
|
8
|
+
*
|
|
9
|
+
* When not called, screeps-connectivity uses `globalThis.fetch`.
|
|
10
|
+
*/
|
|
11
|
+
export function setFetch(fetchFn: typeof globalThis.fetch): void {
|
|
12
|
+
_fetch = fetchFn
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getFetch(): typeof globalThis.fetch {
|
|
16
|
+
return _fetch ?? globalThis.fetch
|
|
17
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ServerVersion } from '../types/game.js'
|
|
2
2
|
import type { ScreepsmodAuthFeature, ServerFeature } from '../types/game.js'
|
|
3
3
|
import type { ApiAuthModInfoResponse, ApiRegisterCheckResponse } from '../types/api.js'
|
|
4
|
+
import { getFetch } from './fetchFn.js'
|
|
4
5
|
|
|
5
6
|
export type { ApiAuthModInfoResponse }
|
|
6
7
|
|
|
@@ -56,7 +57,7 @@ export async function fetchServerVersion(url: string): Promise<ServerVersion> {
|
|
|
56
57
|
const cached = readFromSession<ServerVersion>(key)
|
|
57
58
|
if (cached) return cached
|
|
58
59
|
|
|
59
|
-
const res = await
|
|
60
|
+
const res = await getFetch()(`${baseUrl(url)}api/version`)
|
|
60
61
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
61
62
|
const data = await res.json() as ServerVersion
|
|
62
63
|
writeToSession(key, data)
|
|
@@ -73,7 +74,7 @@ export async function fetchAuthModInfo(url: string): Promise<ApiAuthModInfoRespo
|
|
|
73
74
|
const cached = readFromSession<ApiAuthModInfoResponse>(key)
|
|
74
75
|
if (cached) return cached
|
|
75
76
|
|
|
76
|
-
const res = await
|
|
77
|
+
const res = await getFetch()(`${baseUrl(url)}api/authmod`)
|
|
77
78
|
if (!res.ok) return null
|
|
78
79
|
const data = await res.json() as ApiAuthModInfoResponse
|
|
79
80
|
if (!data.ok) return null
|
|
@@ -87,7 +88,7 @@ export async function fetchAuthModInfo(url: string): Promise<ApiAuthModInfoRespo
|
|
|
87
88
|
*/
|
|
88
89
|
export async function checkUsername(url: string, username: string): Promise<ApiRegisterCheckResponse> {
|
|
89
90
|
const params = new URLSearchParams({ username })
|
|
90
|
-
const res = await
|
|
91
|
+
const res = await getFetch()(`${baseUrl(url)}api/register/check-username?${params}`)
|
|
91
92
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
92
93
|
return res.json() as Promise<ApiRegisterCheckResponse>
|
|
93
94
|
}
|
|
@@ -98,7 +99,7 @@ export async function checkUsername(url: string, username: string): Promise<ApiR
|
|
|
98
99
|
*/
|
|
99
100
|
export async function checkEmail(url: string, email: string): Promise<ApiRegisterCheckResponse> {
|
|
100
101
|
const params = new URLSearchParams({ email })
|
|
101
|
-
const res = await
|
|
102
|
+
const res = await getFetch()(`${baseUrl(url)}api/register/check-email?${params}`)
|
|
102
103
|
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
103
104
|
return res.json() as Promise<ApiRegisterCheckResponse>
|
|
104
105
|
}
|
|
@@ -113,7 +114,7 @@ export async function registerUser(
|
|
|
113
114
|
email: string,
|
|
114
115
|
password: string,
|
|
115
116
|
): Promise<{ ok: number; error?: string }> {
|
|
116
|
-
const res = await
|
|
117
|
+
const res = await getFetch()(`${baseUrl(url)}api/register/submit`, {
|
|
117
118
|
method: 'POST',
|
|
118
119
|
headers: { 'Content-Type': 'application/json' },
|
|
119
120
|
body: JSON.stringify({ username, email, password }),
|