dredless 0.3.1 → 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
 
@@ -322,7 +319,7 @@ interface ClientSnapshot {
322
319
  currentWorldId: number | null;
323
320
  worlds: WorldSnapshot[];
324
321
  cpuLoad: number | null;
325
- inventory: InventoryState | null;
322
+ inventory: InventoryState;
326
323
  puiPanels: PuiEvent[];
327
324
  commsPanels: CommsEvent[];
328
325
  currentCommsPanel: CommsEvent | null;
@@ -493,7 +490,7 @@ const EquipmentSlot = { Head: "head", Face: "face", Body: "body", Back: "back",
493
490
  type EquipmentSlot = typeof EquipmentSlot[keyof typeof EquipmentSlot];
494
491
 
495
492
  interface InventoryDomain {
496
- state(): InventoryState | null;
493
+ state(): InventoryState;
497
494
  hotbarSize(): number;
498
495
  allSlots(): InventorySlotHandle[];
499
496
  slot(ref: InventorySlotRef): InventorySlotHandle;
@@ -1320,7 +1317,7 @@ interface HelmHandle extends MachineHandle {
1320
1317
  occupied: boolean | undefined;
1321
1318
  }
1322
1319
 
1323
- interface DoorHandle extends MachineHandle {
1320
+ interface DoorHandle extends EntityHandle {
1324
1321
  state: DoorSummary | null;
1325
1322
  rank: number | undefined;
1326
1323
  open: boolean | undefined;
@@ -2131,7 +2128,7 @@ chat-command path used by its chat commands, so client.sendChatMessage() can
2131
2128
  send executable chat commands as well as ordinary chat text. For example:
2132
2129
 
2133
2130
  ```js
2134
- client.sendChatMessage("/help");
2131
+ client.sendChatMessage("/save");
2135
2132
  ```
2136
2133
 
2137
2134
  ### Comms helpers
@@ -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.3.1",
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,
@@ -144,7 +144,7 @@ export class DredlessClient extends EventBus {
144
144
  this.inventory = new InventoryDomain(this);
145
145
  this.management = new ShipManagementDomain(this);
146
146
  this.cpuLoad = null;
147
- this.inventoryState = null;
147
+ this.inventoryState = normalizeInventoryEvent({type: "inventory", general_slots: 5});
148
148
  this.puiPanels = new Map();
149
149
  this.commsPanels = new Map();
150
150
  this.currentCommsPanel = null;
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";
@@ -612,7 +615,7 @@ export interface ShipManagementDomain {
612
615
  }
613
616
 
614
617
  export interface InventoryDomain {
615
- state(): InventoryState | null;
618
+ state(): InventoryState;
616
619
 
617
620
  hotbarSize(): number;
618
621
 
@@ -1114,7 +1117,7 @@ export interface HelmHandle extends MachineHandle {
1114
1117
  occupied: boolean | undefined;
1115
1118
  }
1116
1119
 
1117
- export interface DoorHandle extends MachineHandle {
1120
+ export interface DoorHandle extends EntityHandle {
1118
1121
  state: DoorSummary | null;
1119
1122
  rank: number | undefined;
1120
1123
  open: boolean | undefined;
@@ -1180,7 +1183,7 @@ export interface ClientSnapshot {
1180
1183
  currentWorldId: number | null;
1181
1184
  worlds: WorldSnapshot[];
1182
1185
  cpuLoad: number | null;
1183
- inventory: InventoryState | null;
1186
+ inventory: InventoryState;
1184
1187
  puiPanels: PuiEvent[];
1185
1188
  commsPanels: CommsEvent[];
1186
1189
  currentCommsPanel: CommsEvent | null;
@@ -1763,7 +1766,6 @@ export interface BotSummary {
1763
1766
  export interface FabricatorSummary {
1764
1767
  entity: number;
1765
1768
  type: FabricatorType | null;
1766
- typeIndex: number | null;
1767
1769
  typeIndex: number | null;
1768
1770
  state: ModelRecord;
1769
1771
  rows: { itemId: number | null; itemName: string | null; count: number | null }[];
@@ -2249,6 +2251,10 @@ export function fetchGameVersion(): Promise<string>;
2249
2251
 
2250
2252
  export function fetchServers(): Promise<Server[]>;
2251
2253
 
2254
+ export function fetchServerStatus(server: ServerRef): Promise<ServerStatus>;
2255
+
2256
+ export function fetchServerStatuses(): Promise<(Server & ServerStatus)[]>;
2257
+
2252
2258
  export function fetchShips(session: Session, server: ServerRef): Promise<Ship[]>;
2253
2259
 
2254
2260
  export function fetchShipList(session: Session, server: ServerRef): Promise<ShipList>;
@@ -2274,6 +2280,8 @@ export interface DredlessNamespace {
2274
2280
  fetchNoticeVersion: typeof fetchNoticeVersion;
2275
2281
  fetchGameVersion: typeof fetchGameVersion;
2276
2282
  fetchServers: typeof fetchServers;
2283
+ fetchServerStatus: typeof fetchServerStatus;
2284
+ fetchServerStatuses: typeof fetchServerStatuses;
2277
2285
  fetchShips: typeof fetchShips;
2278
2286
  fetchShipList: typeof fetchShipList;
2279
2287
  joinShip: typeof joinShip;
@@ -2281,6 +2289,5 @@ export interface DredlessNamespace {
2281
2289
  startNewShip: typeof startNewShip;
2282
2290
  joinInvite: typeof joinInvite;
2283
2291
  }
2284
-
2285
2292
  export const Dredless: DredlessNamespace;
2286
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
  }