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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screeps-connectivity",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "repository": {
@@ -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 fetch(url.toString(), init)
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 fetch(`${baseUrl(url)}api/version`)
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 fetch(`${baseUrl(url)}api/authmod`)
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 fetch(`${baseUrl(url)}api/register/check-username?${params}`)
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 fetch(`${baseUrl(url)}api/register/check-email?${params}`)
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 fetch(`${baseUrl(url)}api/register/submit`, {
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 }),
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { ScreepsClient } from './ScreepsClient.js'
2
+ export { setFetch } from './http/fetchFn.js'
2
3
  export type { ScreepsClientOptions } from './ScreepsClient.js'
3
4
 
4
5
  export { Logger } from './logger.js'