dredless 0.4.0 → 0.5.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.
@@ -60,16 +60,13 @@ Both runtime entrypoints re-export world/model classes, protocol builders, confi
60
60
 
61
61
  ### `Server`
62
62
 
63
- Returned by `fetchServers()`.
63
+ Returned by `fetchServers()`. Player counts and ping are returned separately by `fetchServerStatus()` or `fetchServerStatuses()`.
64
64
 
65
65
  ```ts
66
66
  interface Server {
67
67
  index: number;
68
68
  domain: string;
69
69
  description: string;
70
- playerCount: number;
71
- maxPlayerCount: number;
72
- ping: number | null;
73
70
  }
74
71
  ```
75
72
 
@@ -41,6 +41,8 @@ interface DredlessBrowserNamespace {
41
41
  fetchNoticeVersion: typeof fetchNoticeVersion;
42
42
  fetchGameVersion: typeof fetchGameVersion;
43
43
  fetchServers: typeof fetchServers;
44
+ fetchServerStatus: typeof fetchServerStatus;
45
+ fetchServerStatuses: typeof fetchServerStatuses;
44
46
  fetchShips: typeof fetchShips;
45
47
  fetchShipList: typeof fetchShipList;
46
48
  joinShip: typeof joinShip;
package/docs/NODE_API.md CHANGED
@@ -108,7 +108,22 @@ Scrapes the current game version string from the site.
108
108
  function fetchServers(): Promise<Server[]>;
109
109
  ```
110
110
 
111
- Scrapes and returns known game servers.
111
+ Scrapes and returns game servers.
112
+ ### `Dredless.fetchServerStatus(server)`
113
+
114
+ ```ts
115
+ function fetchServerStatus(server: ServerRef): Promise<{ playerCount: number; maxPlayerCount: number; ping: number }>;
116
+ ```
117
+
118
+ Pings the server and returns the current player counts and measured response time in milliseconds.
119
+
120
+ ### `Dredless.fetchServerStatuses()`
121
+
122
+ ```ts
123
+ function fetchServerStatuses(): Promise<Server[]>;
124
+ ```
125
+
126
+ Fetches status for every server and returns server objects with updated `playerCount`, `maxPlayerCount`, and `ping` fields.
112
127
 
113
128
  ### `Dredless.fetchShips(session, server)`
114
129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dredless",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A fully-featured and from-scratch headless drednot.io client implementation written in pure js.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/certainhuman/dredless#readme",
package/src/browser.d.ts CHANGED
@@ -76,6 +76,8 @@ export {
76
76
  decompressLz4Frame,
77
77
  fetchNoticeVersion,
78
78
  fetchGameVersion,
79
+ fetchServerStatus,
80
+ fetchServerStatuses,
79
81
  fetchServers
80
82
  } from "./index.js";
81
83
 
@@ -199,7 +201,7 @@ export interface DredlessBrowserNamespace {
199
201
  fetchAccountStatus: typeof fetchAccountStatus;
200
202
  fetchNoticeVersion: typeof import("./index.js").fetchNoticeVersion;
201
203
  fetchGameVersion: typeof import("./index.js").fetchGameVersion;
202
- fetchServers: typeof import("./index.js").fetchServers;
204
+ fetchServers: typeof import("./index.js").fetchServers;`r`n fetchServerStatus: typeof import("./index.js").fetchServerStatus;`r`n fetchServerStatuses: typeof import("./index.js").fetchServerStatuses;
203
205
  fetchShips: typeof fetchShips;
204
206
  fetchShipList: typeof fetchShipList;
205
207
  joinShip: typeof joinShip;
package/src/browser.js CHANGED
@@ -66,7 +66,7 @@ export {
66
66
  } from "./protocol/outbound/ui-config.js";
67
67
  export {decryptPayload} from "./crypto/chacha.js";
68
68
  export {decompressLz4Frame} from "./compression/lz4.js";
69
- export {fetchNoticeVersion, fetchGameVersion, fetchServers} from "./network/servers.js";
69
+ export {fetchNoticeVersion, fetchGameVersion, fetchServerStatus, fetchServerStatuses, fetchServers} from "./network/servers.js";
70
70
  export {fetchShips as fetchShipsWithSession, fetchShipList as fetchShipListWithSession} from "./network/ships.js";
71
71
  export {
72
72
  BrowserSession,
@@ -88,7 +88,7 @@ export {
88
88
 
89
89
  import {Connection} from "./network/connection.js";
90
90
  import {DredlessClient} from "./client/index.js";
91
- import {fetchGameVersion, fetchNoticeVersion, fetchServers} from "./network/servers.js";
91
+ import {fetchGameVersion, fetchNoticeVersion, fetchServerStatus, fetchServerStatuses, fetchServers} from "./network/servers.js";
92
92
  import {
93
93
  BrowserSession,
94
94
  browserSession,
@@ -121,6 +121,8 @@ export const DredlessBrowser = {
121
121
  fetchAccountStatus,
122
122
  fetchNoticeVersion,
123
123
  fetchGameVersion,
124
+ fetchServerStatus,
125
+ fetchServerStatuses,
124
126
  fetchServers,
125
127
  fetchShips,
126
128
  fetchShipList: fetchShipListForBrowser,
package/src/index.d.ts CHANGED
@@ -2,9 +2,6 @@ export interface Server {
2
2
  index: number;
3
3
  domain: string;
4
4
  description: string;
5
- playerCount: number;
6
- maxPlayerCount: number;
7
- ping: number | null;
8
5
  }
9
6
 
10
7
  export interface Ship {
@@ -31,6 +28,12 @@ export type ShipSpec =
31
28
  | { type: "new"; name?: string | null; color?: string | null }
32
29
  | { type: "invite"; code: string };
33
30
 
31
+ export interface ServerStatus {
32
+ playerCount: number;
33
+ maxPlayerCount: number;
34
+ ping: number;
35
+ }
36
+
34
37
  export type ServerRef = number | Server;
35
38
  export type ShipRef = number | string | Ship | ShipSpec | null;
36
39
  export type ShipPrivacy = 0 | 1 | boolean | "public" | "private";
@@ -2248,6 +2251,10 @@ export function fetchGameVersion(): Promise<string>;
2248
2251
 
2249
2252
  export function fetchServers(): Promise<Server[]>;
2250
2253
 
2254
+ export function fetchServerStatus(server: ServerRef): Promise<ServerStatus>;
2255
+
2256
+ export function fetchServerStatuses(): Promise<(Server & ServerStatus)[]>;
2257
+
2251
2258
  export function fetchShips(session: Session, server: ServerRef): Promise<Ship[]>;
2252
2259
 
2253
2260
  export function fetchShipList(session: Session, server: ServerRef): Promise<ShipList>;
@@ -2273,6 +2280,8 @@ export interface DredlessNamespace {
2273
2280
  fetchNoticeVersion: typeof fetchNoticeVersion;
2274
2281
  fetchGameVersion: typeof fetchGameVersion;
2275
2282
  fetchServers: typeof fetchServers;
2283
+ fetchServerStatus: typeof fetchServerStatus;
2284
+ fetchServerStatuses: typeof fetchServerStatuses;
2276
2285
  fetchShips: typeof fetchShips;
2277
2286
  fetchShipList: typeof fetchShipList;
2278
2287
  joinShip: typeof joinShip;
@@ -2280,6 +2289,5 @@ export interface DredlessNamespace {
2280
2289
  startNewShip: typeof startNewShip;
2281
2290
  joinInvite: typeof joinInvite;
2282
2291
  }
2283
-
2284
2292
  export const Dredless: DredlessNamespace;
2285
2293
  export default Dredless;
package/src/index.js CHANGED
@@ -81,7 +81,7 @@ export {decompressLz4Frame} from "./compression/lz4.js";
81
81
  import {AnonSession, createAnonSession, createAnonToken, createSession, Session} from "./network/session.js";
82
82
  import {Connection} from "./network/connection.js";
83
83
  import {DredlessClient} from "./client/index.js";
84
- import {fetchGameVersion, fetchNoticeVersion, fetchServers} from "./network/servers.js";
84
+ import {fetchGameVersion, fetchNoticeVersion, fetchServerStatus, fetchServerStatuses, fetchServers} from "./network/servers.js";
85
85
  import {fetchShipList, fetchShips} from "./network/ships.js";
86
86
 
87
87
  async function sessionOrAnon(session) {
@@ -114,6 +114,8 @@ export const Dredless = {
114
114
  createAnonToken,
115
115
  fetchNoticeVersion,
116
116
  fetchGameVersion,
117
+ fetchServerStatus,
118
+ fetchServerStatuses,
117
119
  fetchServers,
118
120
  fetchShips,
119
121
  fetchShipList,
@@ -1,4 +1,4 @@
1
1
  export {Connection} from "./connection.js";
2
- export {fetchGameVersion, fetchNoticeVersion, fetchServers, resolveServer} from "./servers.js";
2
+ export {fetchGameVersion, fetchNoticeVersion, fetchServerStatus, fetchServerStatuses, fetchServers, resolveServer} from "./servers.js";
3
3
  export {fetchShips, fetchShipList, createShipSpec, createInviteShipSpec, shipRef} from "./ships.js";
4
4
  export {Session, AnonSession, createSession, createAnonSession, createAnonToken} from "./session.js";
@@ -1,6 +1,8 @@
1
1
  export {
2
2
  fetchNoticeVersion,
3
3
  fetchGameVersion,
4
+ fetchServerStatus,
5
+ fetchServerStatuses,
4
6
  fetchServers,
5
7
  type Server,
6
8
  type ServerRef
@@ -1,5 +1,5 @@
1
1
  import {DEFAULT_BASE_URL} from "../constants.js";
2
- import {hostName, normalizeBaseUrl} from "../runtime.js";
2
+ import {getWebSocket, hostName, normalizeBaseUrl} from "../runtime.js";
3
3
  import {HttpClient} from "./http.js";
4
4
 
5
5
  const cache = new Map();
@@ -56,6 +56,56 @@ class ServerDirectory {
56
56
 
57
57
  export const fetchGameVersion = (baseUrl = DEFAULT_BASE_URL) => new ServerDirectory(baseUrl).version();
58
58
  export const fetchServers = (baseUrl = DEFAULT_BASE_URL) => new ServerDirectory(baseUrl).servers();
59
+ export async function fetchServerStatus(server, baseUrl = DEFAULT_BASE_URL) {
60
+ const resolved = await resolveServer(server, baseUrl);
61
+ if (!resolved?.domain) throw new Error(`Unable to resolve server ${serverId(server)}`);
62
+ const WebSocket = await getWebSocket();
63
+ const startedAt = Date.now();
64
+ const socket = new WebSocket(`wss://${resolved.domain}:4000/`);
65
+
66
+ return new Promise((resolve, reject) => {
67
+ let settled = false;
68
+ const finish = (callback, value) => {
69
+ if (settled) return;
70
+ settled = true;
71
+ clearTimeout(timeout);
72
+ callback(value);
73
+ if (socket.readyState === 0 || socket.readyState === 1) socket.close();
74
+ };
75
+ const timeout = setTimeout(() => finish(reject, new Error(`Timed out fetching status for ${resolved.domain}`)), 10000);
76
+ const onOpen = () => socket.send("yo");
77
+ const onMessage = async (event) => {
78
+ try {
79
+ const data = JSON.parse(await websocketText(event?.data ?? event));
80
+ const playerCount = Number(data.player_count);
81
+ const maxPlayerCount = Number(data.player_max);
82
+ if (!Number.isFinite(playerCount) || !Number.isFinite(maxPlayerCount)) throw new Error("Invalid server status response");
83
+ finish(resolve, {playerCount, maxPlayerCount, ping: Date.now() - startedAt});
84
+ } catch (error) {
85
+ finish(reject, new Error(`Invalid status response from ${resolved.domain}: ${error.message}`));
86
+ }
87
+ };
88
+ const onError = (event) => finish(reject, event?.error || new Error(`WebSocket error while fetching ${resolved.domain}`));
89
+ if (typeof socket.on === "function") {
90
+ socket.on("open", onOpen);
91
+ socket.on("message", onMessage);
92
+ socket.on("error", onError);
93
+ } else if (typeof socket.addEventListener === "function") {
94
+ socket.addEventListener("open", onOpen);
95
+ socket.addEventListener("message", onMessage);
96
+ socket.addEventListener("error", onError);
97
+ } else {
98
+ socket.onopen = onOpen;
99
+ socket.onmessage = onMessage;
100
+ socket.onerror = onError;
101
+ }
102
+ });
103
+ }
104
+
105
+ export async function fetchServerStatuses(baseUrl = DEFAULT_BASE_URL) {
106
+ const servers = await fetchServers(baseUrl);
107
+ return Promise.all(servers.map(async (server) => ({...server, ...await fetchServerStatus(server, baseUrl)})));
108
+ }
59
109
 
60
110
  export async function fetchNoticeVersion(baseUrl = DEFAULT_BASE_URL) {
61
111
  const response = await new HttpClient({baseUrl}).request("index.html");
@@ -128,6 +178,13 @@ function parseServerInfo(text, baseUrl) {
128
178
  };
129
179
  }
130
180
 
181
+ async function websocketText(data) {
182
+ if (typeof data === "string") return data;
183
+ if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
184
+ if (ArrayBuffer.isView(data)) return new TextDecoder().decode(data);
185
+ if (typeof data?.text === "function") return data.text();
186
+ return String(data);
187
+ }
131
188
  function server(index, domain, description) {
132
- return {index, domain, description, playerCount: 0, maxPlayerCount: 0, ping: null};
189
+ return {index, domain, description};
133
190
  }