screeps-connectivity 0.12.0 → 0.13.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 94a6658: Add Discord OAuth login: getDiscordFeature() helper in screeps-connectivity, and a "Login with Discord" button in the web and desktop login forms.
8
+
9
+ ### Patch Changes
10
+
11
+ - 46b5e2d: Fix Steam/password logins silently expiring after ~5 minutes on screepsmod-auth servers. These logins reconnect via a rotating, TTL-limited session token, but `TokenAuth` was hard-coded to ignore the server-issued `X-Token`, so the client kept replaying the original token until the server expired it — surfacing as a sudden `401` on `/api/user/world-status` (and every other authed request) even while actively using the client.
12
+
13
+ `TokenAuth` now accepts `supportsTokenRefresh` (default `false`, preserving durable personal-API-token behavior). The client enables it for Steam/password-derived session tokens so the rotated `X-Token` is adopted on every response, keeping the session alive.
14
+
3
15
  ## 0.12.0
4
16
 
5
17
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screeps-connectivity",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "license": "ISC",
5
5
  "type": "module",
6
6
  "repository": {
@@ -2,11 +2,19 @@ import type { AuthStrategy } from './AuthStrategy.js'
2
2
  import type { HttpClient } from '../HttpClient.js'
3
3
 
4
4
  export class TokenAuth implements AuthStrategy {
5
- readonly supportsTokenRefresh = false
5
+ readonly supportsTokenRefresh: boolean
6
6
  private readonly token: string
7
7
 
8
- constructor(opts: { token: string }) {
8
+ /**
9
+ * @param opts.supportsTokenRefresh Set true when the token is a rotating,
10
+ * TTL-limited session token (e.g. obtained from a screepsmod-auth steam/password
11
+ * exchange) so the client adopts the server-issued `X-Token` and the session
12
+ * stays alive. Leave false (default) for a durable personal API token, which
13
+ * must never be replaced by a server-issued token.
14
+ */
15
+ constructor(opts: { token: string; supportsTokenRefresh?: boolean }) {
9
16
  this.token = opts.token
17
+ this.supportsTokenRefresh = opts.supportsTokenRefresh ?? false
10
18
  }
11
19
 
12
20
  async authenticate(_http: HttpClient): Promise<string> {
@@ -1,5 +1,5 @@
1
1
  import type { ServerVersion } from '../types/game.js'
2
- import type { ScreepsmodAuthFeature, ServerFeature, XxscreepsModClientFeature } from '../types/game.js'
2
+ import type { DiscordFeature, ScreepsmodAuthFeature, ServerFeature, XxscreepsModClientFeature } from '../types/game.js'
3
3
  import type { ApiAuthMeResponse, ApiAuthModInfoResponse, ApiRegisterCheckResponse } from '../types/api.js'
4
4
  import { getFetch } from './fetchFn.js'
5
5
 
@@ -193,3 +193,16 @@ export function getScreepsmodAuth(version: ServerVersion): ScreepsmodAuthFeature
193
193
  export function getXxscreepsModClientFeature(version: ServerVersion): XxscreepsModClientFeature | undefined {
194
194
  return getServerFeature<XxscreepsModClientFeature>(version, 'xxscreeps-mod-client')
195
195
  }
196
+
197
+ /**
198
+ * Returns the `discord` feature entry (published by `xxscreeps-mod-discord`) if
199
+ * present, otherwise `undefined`. Use `discordLogin` to gate the "Login with
200
+ * Discord" button before showing it.
201
+ *
202
+ * @example
203
+ * const discord = getDiscordFeature(version)
204
+ * if (discord?.discordLogin) showDiscordButton()
205
+ */
206
+ export function getDiscordFeature(version: ServerVersion): DiscordFeature | undefined {
207
+ return getServerFeature<DiscordFeature>(version, 'discord')
208
+ }
package/src/index.ts CHANGED
@@ -33,6 +33,7 @@ export type {
33
33
  ServerFeature,
34
34
  ScreepsmodAuthFeature,
35
35
  XxscreepsModClientFeature,
36
+ DiscordFeature,
36
37
  ShardInfo,
37
38
  WorldInfo,
38
39
  Badge,
@@ -41,7 +42,7 @@ export type {
41
42
  MapVisualEntry,
42
43
  } from './types/game.js'
43
44
 
44
- export { fetchServerVersion, fetchAuthModInfo, checkUsername, checkEmail, registerUser, fetchAuthMeWithToken, completeProviderRegistration, getServerFeature, getScreepsmodAuth, getXxscreepsModClientFeature } from './http/fetchServerVersion.js'
45
+ export { fetchServerVersion, fetchAuthModInfo, checkUsername, checkEmail, registerUser, fetchAuthMeWithToken, completeProviderRegistration, getServerFeature, getScreepsmodAuth, getXxscreepsModClientFeature, getDiscordFeature } from './http/fetchServerVersion.js'
45
46
  export type { ApiAuthModInfoResponse } from './http/fetchServerVersion.js'
46
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
48
  export { ROOM_DECORATIONS_MOCK } from './mocks/roomDecorations.js'
package/src/types/game.ts CHANGED
@@ -125,6 +125,18 @@ export interface XxscreepsModClientFeature extends ServerFeature {
125
125
  steamLogin: boolean
126
126
  }
127
127
 
128
+ /**
129
+ * Published by `xxscreeps-mod-discord` at `/api/version` when Discord login is
130
+ * configured. `discordLogin` gates the UI; `loginUrl` is the OAuth entry point
131
+ * (a popup navigates there to start the flow).
132
+ */
133
+ export interface DiscordFeature extends ServerFeature {
134
+ name: 'discord'
135
+ version: number
136
+ discordLogin: boolean
137
+ loginUrl: string
138
+ }
139
+
128
140
  export interface ServerVersion {
129
141
  ok: number
130
142
  package: number
@@ -98,6 +98,16 @@ describe('HttpClient', () => {
98
98
  expect(http.token).toBe('my-static-token')
99
99
  })
100
100
 
101
+ it('DOES update token from x-token header when TokenAuth opts into refresh (session token)', async () => {
102
+ fetchMock.mockResolvedValue(mockResponse({ ok: 1 }, {
103
+ headers: { 'content-type': 'application/json', 'x-token': 'rotated' },
104
+ }))
105
+ const http = new HttpClient({ url: 'http://test.local', auth: new TokenAuth({ token: 'session-token', supportsTokenRefresh: true }) })
106
+ http.token = 'session-token'
107
+ await http.request('GET', '/api/version')
108
+ expect(http.token).toBe('rotated')
109
+ })
110
+
101
111
  it('retries once on 401 after re-authenticating', async () => {
102
112
  let calls = 0
103
113
  fetchMock.mockImplementation(() => {