dredless 0.2.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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +48 -0
  3. package/docs/API_REFERENCE.md +2481 -0
  4. package/docs/BROWSER_API.md +98 -0
  5. package/docs/NODE_API.md +359 -0
  6. package/package.json +173 -0
  7. package/src/browser.d.ts +212 -0
  8. package/src/browser.js +133 -0
  9. package/src/client/core.js +1202 -0
  10. package/src/client/domains/index.js +1818 -0
  11. package/src/client/index.d.ts +2 -0
  12. package/src/client/index.js +1 -0
  13. package/src/compression/lz4.d.ts +1 -0
  14. package/src/compression/lz4.js +112 -0
  15. package/src/constants.js +74 -0
  16. package/src/crypto/chacha-native.d.ts +9 -0
  17. package/src/crypto/chacha-native.js +48 -0
  18. package/src/crypto/chacha.d.ts +25 -0
  19. package/src/crypto/chacha.js +274 -0
  20. package/src/data/item-schema.js +2235 -0
  21. package/src/data/official-client-data.js +156 -0
  22. package/src/events.js +39 -0
  23. package/src/index.d.ts +2277 -0
  24. package/src/index.js +125 -0
  25. package/src/network/browser-session.js +291 -0
  26. package/src/network/connection.d.ts +1 -0
  27. package/src/network/connection.js +31 -0
  28. package/src/network/cookies.js +85 -0
  29. package/src/network/http.js +34 -0
  30. package/src/network/index.d.ts +4 -0
  31. package/src/network/index.js +4 -0
  32. package/src/network/servers.d.ts +7 -0
  33. package/src/network/servers.js +133 -0
  34. package/src/network/session.d.ts +7 -0
  35. package/src/network/session.js +320 -0
  36. package/src/network/ships.d.ts +12 -0
  37. package/src/network/ships.js +73 -0
  38. package/src/node.d.ts +2 -0
  39. package/src/node.js +8 -0
  40. package/src/protocol/codec/binary.d.ts +3 -0
  41. package/src/protocol/codec/binary.js +52 -0
  42. package/src/protocol/codec/msgpack.d.ts +3 -0
  43. package/src/protocol/codec/msgpack.js +555 -0
  44. package/src/protocol/inbound/frame.d.ts +1 -0
  45. package/src/protocol/inbound/frame.js +8 -0
  46. package/src/protocol/inbound/world-payload.d.ts +3 -0
  47. package/src/protocol/inbound/world-payload.js +21 -0
  48. package/src/protocol/index.d.ts +11 -0
  49. package/src/protocol/index.js +19 -0
  50. package/src/protocol/outbound/blueprint.d.ts +22 -0
  51. package/src/protocol/outbound/blueprint.js +36 -0
  52. package/src/protocol/outbound/commands.d.ts +3 -0
  53. package/src/protocol/outbound/commands.js +149 -0
  54. package/src/protocol/outbound/comms.d.ts +28 -0
  55. package/src/protocol/outbound/comms.js +40 -0
  56. package/src/protocol/outbound/inventory.d.ts +64 -0
  57. package/src/protocol/outbound/inventory.js +147 -0
  58. package/src/protocol/outbound/ship-management.d.ts +84 -0
  59. package/src/protocol/outbound/ship-management.js +221 -0
  60. package/src/protocol/outbound/sign.d.ts +18 -0
  61. package/src/protocol/outbound/sign.js +45 -0
  62. package/src/protocol/outbound/ui-config.d.ts +157 -0
  63. package/src/protocol/outbound/ui-config.js +578 -0
  64. package/src/runtime.js +36 -0
  65. package/src/state/generator-maze.d.ts +32 -0
  66. package/src/state/generator-maze.js +370 -0
  67. package/src/state/index.d.ts +3 -0
  68. package/src/state/index.js +3 -0
  69. package/src/state/items.js +20 -0
  70. package/src/state/model/decoder.js +1554 -0
  71. package/src/state/model/index.d.ts +33 -0
  72. package/src/state/model/index.js +2 -0
  73. package/src/state/model/loader-config.js +1020 -0
  74. package/src/state/model/reader.js +69 -0
  75. package/src/state/model/state.js +1257 -0
  76. package/src/state/overworld.js +72 -0
  77. package/src/state/tilesets.js +53 -0
  78. package/src/state/world/index.d.ts +29 -0
  79. package/src/state/world/index.js +180 -0
  80. package/src/state/world/state.js +297 -0
package/src/index.js ADDED
@@ -0,0 +1,125 @@
1
+ export {Session, AnonSession} from "./network/session.js";
2
+ export {Connection} from "./network/connection.js";
3
+ export {DredlessClient, WrenchMode, TurretMode} from "./client/index.js";
4
+ export {WorldStore, WorldState} from "./state/world/index.js";
5
+ export {FabricatorType, ModelState, decodeModelData} from "./state/model/index.js";
6
+ export {generateGeneratorMaze, solveGeneratorMazeSeed, maybeSolveGeneratorMazeSeed} from "./state/generator-maze.js";
7
+ export {buildBlueprintPlacementMessage} from "./protocol/outbound/blueprint.js";
8
+ export {decodeMsgpack, encodeMsgpack} from "./protocol/codec/msgpack.js";
9
+ export {
10
+ buildCommsMessage,
11
+ flattenRichText,
12
+ normalizeCommsEvent
13
+ } from "./protocol/outbound/comms.js";
14
+ export {buildSignedCommandPacket} from "./protocol/outbound/commands.js";
15
+ export {
16
+ buildEquipItemCommand,
17
+ buildInventoryDragCommand,
18
+ buildUnequipItemCommand,
19
+ equipmentSlotName,
20
+ normalizeEquipmentSlot,
21
+ normalizeInventoryEvent,
22
+ EquipmentSlot
23
+ } from "./protocol/outbound/inventory.js";
24
+ export {
25
+ buildBanPlayerMessage,
26
+ buildDemoteSelfMessage,
27
+ buildInviteResetMessage,
28
+ buildKickPlayerMessage,
29
+ buildPlayerListMessage,
30
+ buildShipManagementMessage,
31
+ buildSetPlayerRankMessage,
32
+ buildShipPrivacyMessage,
33
+ buildStarterRecoveryMessage,
34
+ normalizeCaptainSubrank,
35
+ normalizeShipPlayerList,
36
+ normalizePlayerRank,
37
+ normalizePrivacy,
38
+ normalizeShipConfig
39
+ } from "./protocol/outbound/ship-management.js";
40
+ export {
41
+ SignDisplayMode,
42
+ buildSignTextMessage,
43
+ normalizeSignDisplayMode,
44
+ signDisplayModeName
45
+ } from "./protocol/outbound/sign.js";
46
+ export {
47
+ buildCargoEjectorClipboardDirectionData,
48
+ buildCargoEjectorCopyConfigData,
49
+ buildCargoEjectorDirectionData,
50
+ buildCargoEjectorPasteConfigData,
51
+ buildCargoHatchCopyConfigData,
52
+ buildCargoHatchFilterConfigData,
53
+ buildCargoHatchFilterItemsData,
54
+ buildCargoHatchFullConfigData,
55
+ buildClipboardConfigData,
56
+ buildClipboardFixedAngleData,
57
+ buildExpandoClipboardAngleData,
58
+ buildGeneratorClipboardDirectionData,
59
+ buildGeneratorMazePuzzleData,
60
+ buildLoaderClipboardConfigData,
61
+ buildLoaderConfigData,
62
+ buildLoaderCopyConfigData,
63
+ buildLoaderFilterConfigData,
64
+ buildLoaderFilterItemsData,
65
+ buildLoaderFullConfigData,
66
+ buildNavigationUnitClipboardConfigData,
67
+ buildNavigationUnitConfigData,
68
+ buildNavigationUnitPasteConfigData,
69
+ buildPusherConfigData,
70
+ buildPusherFilterItemsData,
71
+ PusherMode,
72
+ LoaderPosition,
73
+ LoaderFilterMode,
74
+ FixedAngleDirection,
75
+ LoaderPriority
76
+ } from "./protocol/outbound/ui-config.js";
77
+ export {decryptPayload} from "./crypto/chacha.js";
78
+ export {decompressLz4Frame} from "./compression/lz4.js";
79
+
80
+ import {AnonSession, createAnonSession, createAnonToken, createSession, Session} from "./network/session.js";
81
+ import {Connection} from "./network/connection.js";
82
+ import {DredlessClient} from "./client/index.js";
83
+ import {fetchGameVersion, fetchNoticeVersion, fetchServers} from "./network/servers.js";
84
+ import {fetchShipList, fetchShips} from "./network/ships.js";
85
+
86
+ async function sessionOrAnon(session) {
87
+ return session || createAnonSession();
88
+ }
89
+
90
+ export async function joinShip(server, ship = null, session = null) {
91
+ return (await sessionOrAnon(session)).joinShip(server, ship);
92
+ }
93
+
94
+ export async function startShip(server, ship = null, session = null) {
95
+ return (await sessionOrAnon(session)).startShip(server, ship);
96
+ }
97
+
98
+ export async function startNewShip(server, name = "", color = "", session = null) {
99
+ return (await sessionOrAnon(session)).startNewShip(server, name, color);
100
+ }
101
+
102
+ export async function joinInvite(server, code, session = null) {
103
+ return (await sessionOrAnon(session)).joinInvite(server, code);
104
+ }
105
+
106
+ export const Dredless = {
107
+ Session,
108
+ AnonSession,
109
+ Connection,
110
+ DredlessClient,
111
+ createSession,
112
+ createAnonSession,
113
+ createAnonToken,
114
+ fetchNoticeVersion,
115
+ fetchGameVersion,
116
+ fetchServers,
117
+ fetchShips,
118
+ fetchShipList,
119
+ joinShip,
120
+ startShip,
121
+ startNewShip,
122
+ joinInvite
123
+ };
124
+
125
+ export default Dredless;
@@ -0,0 +1,291 @@
1
+ import {DEFAULT_BASE_URL, DEFAULT_NOTICE_VERSION} from "../constants.js";
2
+ import {normalizeBaseUrl} from "../runtime.js";
3
+ import {browserCookies, cookieName, readBrowserCookie} from "./cookies.js";
4
+ import {fetchGameVersion, fetchNoticeVersion, resolveServer, serverId} from "./servers.js";
5
+ import {
6
+ createInviteShipSpec,
7
+ createShipSpec,
8
+ fetchShipList,
9
+ fetchShips as fetchSessionShips,
10
+ shipRef
11
+ } from "./ships.js";
12
+ import {Connection} from "./connection.js";
13
+ import {DredlessClient} from "../client/index.js";
14
+ import {Session} from "./session.js";
15
+
16
+ export class BrowserSession extends Session {
17
+ constructor(baseUrl = DEFAULT_BASE_URL) {
18
+ super(null, null, baseUrl);
19
+ this.baseUrl = normalizeBaseUrl(baseUrl);
20
+ this.ambientAuth = true;
21
+ }
22
+
23
+ get gameSession() {
24
+ return browserCookies().get(cookieName(this.baseUrl, "game_session")) || "";
25
+ }
26
+
27
+ get gameToken() {
28
+ return browserCookies().get(cookieName(this.baseUrl, "game_token")) || "";
29
+ }
30
+
31
+ get anonKey() {
32
+ return this._anonKey || browserCookies().get(cookieName(this.baseUrl, "anon_key")) || "";
33
+ }
34
+
35
+ async readAnonKey() {
36
+ const key = await readBrowserCookie(cookieName(this.baseUrl, "anon_key"));
37
+ if (key) this._anonKey = key;
38
+ return key || "";
39
+ }
40
+
41
+ async fetchAnonKey(noticeVersion = null) {
42
+ this.noticeVersion = await browserNoticeVersion(noticeVersion, this.baseUrl);
43
+ await createAnonLogin(this);
44
+ const key = await this.readAnonKey();
45
+ if (!key) throw new Error("Unable to read anon_key after anon login. Browser support is tested in page and content-script contexts; background, popup, worker, and other non-page contexts are unlikely to expose drednot.io cookies.");
46
+ return key;
47
+ }
48
+
49
+ get noticeVersion() {
50
+ return this._noticeVersion ?? browserCookies().get(cookieName(this.baseUrl, "notice_version")) ?? null;
51
+ }
52
+
53
+ set noticeVersion(value) {
54
+ this._noticeVersion = value == null ? null : Number(value);
55
+ if (typeof document === "undefined" || value == null) return;
56
+ document.cookie = `${cookieName(this.baseUrl, "notice_version")}=${encodeURIComponent(String(value))}; path=/; SameSite=Lax`;
57
+ }
58
+
59
+ mergeSetCookies() {
60
+ // Browsers own the cookie jar. Set-Cookie is intentionally not readable.
61
+ }
62
+
63
+ async joinShipConnection(server, ship = null) {
64
+ return this.#joinBrowserConnection(server, ship, true);
65
+ }
66
+
67
+ async startShipConnection(server, ship = null) {
68
+ return this.#joinBrowserConnection(server, ship, false);
69
+ }
70
+
71
+ async startNewShipConnection(server, name = "", color = "") {
72
+ return this.#joinBrowserConnection(server, createShipSpec(name, color), false);
73
+ }
74
+
75
+ async joinInviteConnection(server, code) {
76
+ return this.#joinBrowserConnection(server, createInviteShipSpec(code), false);
77
+ }
78
+
79
+ async joinShip(server, ship = null) {
80
+ return readyBrowserClient(await this.joinShipConnection(server, ship));
81
+ }
82
+
83
+ async startShip(server, ship = null) {
84
+ return readyBrowserClient(await this.startShipConnection(server, ship));
85
+ }
86
+
87
+ async startNewShip(server, name = "", color = "") {
88
+ return readyBrowserClient(await this.startNewShipConnection(server, name, color));
89
+ }
90
+
91
+ async joinInvite(server, code) {
92
+ return readyBrowserClient(await this.joinInviteConnection(server, code));
93
+ }
94
+
95
+ async #joinBrowserConnection(server, ship, neverLoad) {
96
+ if (server == null) throw new Error("A server id or server object is required");
97
+ const resolvedServer = await resolveServer(server, this.baseUrl);
98
+ if (!resolvedServer) throw new Error(`Unable to resolve server ${serverId(server)}`);
99
+ if (!this.gameVersion) this.gameVersion = await fetchGameVersion(this.baseUrl);
100
+ if (this.noticeVersion == null) this.noticeVersion = DEFAULT_NOTICE_VERSION;
101
+
102
+ const joinShip = ship == null ? createShipSpec("", "") : shipRef(ship);
103
+ const response = await this.request("join", {
104
+ method: "POST",
105
+ mode: "cors",
106
+ credentials: "include",
107
+ referrer: `${this.baseUrl}/`,
108
+ body: {
109
+ join_info: {
110
+ game_version: this.gameVersion,
111
+ hide_badges: false,
112
+ never_load: Boolean(neverLoad),
113
+ server_id: resolvedServer.index,
114
+ ship: joinShip
115
+ }
116
+ },
117
+ headers: browserJoinHeaders(this.baseUrl)
118
+ });
119
+ if (!response.ok) throw new Error(`Join request failed: ${await response.text()}`);
120
+ const join = await response.json();
121
+ if (join.reject != null) {
122
+ const error = new Error(joinRejectMessage(join.reject, {
123
+ neverLoad,
124
+ ship: joinShip,
125
+ server: resolvedServer
126
+ }));
127
+ error.code = join.reject;
128
+ error.join = join;
129
+ throw error;
130
+ }
131
+ if (join.okay !== true) throw new Error("Bad join result");
132
+
133
+ const token = join.game_token ?? join.gameToken ?? this.gameToken;
134
+ return new Connection(this, token || "", join.net_port, Number.isInteger(join.server_id) ? join.server_id : resolvedServer.index, resolvedServer);
135
+ }
136
+
137
+ toJSON() {
138
+ return {
139
+ ...super.toJSON(),
140
+ ambientAuth: true,
141
+ cookies: Object.fromEntries(browserCookies())
142
+ };
143
+ }
144
+ }
145
+
146
+ let defaultBrowserSession = null;
147
+
148
+ export function browserSession(baseUrl = DEFAULT_BASE_URL) {
149
+ const normalized = normalizeBaseUrl(baseUrl);
150
+ if (!defaultBrowserSession || defaultBrowserSession.baseUrl !== normalized) defaultBrowserSession = new BrowserSession(normalized);
151
+ return defaultBrowserSession;
152
+ }
153
+
154
+ export function setBrowserSession(session = null) {
155
+ defaultBrowserSession = session;
156
+ return defaultBrowserSession;
157
+ }
158
+
159
+ export const createBrowserSession = browserSession;
160
+
161
+ export async function createSession(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
162
+ const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
163
+ const session = browserSession(resolvedBaseUrl);
164
+ session.noticeVersion = await browserNoticeVersion(noticeVersion, resolvedBaseUrl);
165
+ await session.fetchAccountStatus();
166
+ return session;
167
+ }
168
+
169
+ export async function createAnonSession(anonKey = null, noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
170
+ if (typeof noticeVersion === "string" && noticeVersion.includes("://") && (baseUrl == null || baseUrl === DEFAULT_BASE_URL)) {
171
+ baseUrl = noticeVersion;
172
+ noticeVersion = null;
173
+ }
174
+ const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 3 ? arguments[2] : baseUrl);
175
+ const session = browserSession(resolvedBaseUrl);
176
+ session.noticeVersion = await browserNoticeVersion(noticeVersion, resolvedBaseUrl);
177
+ if (anonKey) {
178
+ session._anonKey = String(anonKey);
179
+ await setAmbientCookie(resolvedBaseUrl, "anon_key", anonKey);
180
+ } else {
181
+ await session.fetchAnonKey(session.noticeVersion);
182
+ }
183
+ await session.fetchAccountStatus();
184
+ return session;
185
+ }
186
+
187
+ export async function fetchAnonKey(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
188
+ const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
189
+ return browserSession(resolvedBaseUrl).fetchAnonKey(noticeVersion);
190
+ }
191
+
192
+ export const createAnonToken = fetchAnonKey;
193
+
194
+ export async function fetchAccountStatus(session = null) {
195
+ return (session || browserSession()).fetchAccountStatus();
196
+ }
197
+
198
+ export async function fetchShips(server, session = null) {
199
+ return fetchSessionShips(session || browserSession(), server);
200
+ }
201
+
202
+ export async function fetchShipListForBrowser(server, session = null) {
203
+ return fetchShipList(session || browserSession(), server);
204
+ }
205
+
206
+ export async function joinShip(server, ship = null, session = null) {
207
+ return (session || browserSession()).joinShip(server, ship);
208
+ }
209
+
210
+ export async function startShip(server, ship = null, session = null) {
211
+ return (session || browserSession()).startShip(server, ship);
212
+ }
213
+
214
+ export async function startNewShip(server, name = "", color = "", session = null) {
215
+ return (session || browserSession()).startNewShip(server, name, color);
216
+ }
217
+
218
+ export async function joinInvite(server, code, session = null) {
219
+ return (session || browserSession()).joinInvite(server, code);
220
+ }
221
+
222
+ async function readyBrowserClient(connection) {
223
+ const client = new DredlessClient(connection);
224
+ await client.whenReady();
225
+ return client;
226
+ }
227
+
228
+ async function createAnonLogin(session) {
229
+ const response = await session.request("account/login/anon", {
230
+ method: "POST",
231
+ mode: "cors",
232
+ credentials: "include",
233
+ referrer: `${session.baseUrl}/`,
234
+ headers: browserAnonHeaders(session.baseUrl)
235
+ });
236
+ if (response.status === 503) throw new Error("Anon session creation is rate limited by the server");
237
+ if (!response.ok) throw new Error(`account/login/anon failed: ${response.status} ${response.statusText}`);
238
+ session.mergeSetCookies(response, ["anon_key", "game_session"]);
239
+ }
240
+
241
+ async function browserNoticeVersion(value, baseUrl = DEFAULT_BASE_URL) {
242
+ if (value != null) return value;
243
+ try {
244
+ return await fetchNoticeVersion(baseUrl);
245
+ } catch (_) {
246
+ return DEFAULT_NOTICE_VERSION;
247
+ }
248
+ }
249
+
250
+ async function setAmbientCookie(baseUrl, name, value) {
251
+ const key = cookieName(baseUrl, name);
252
+ if (typeof globalThis.cookieStore !== "undefined" && typeof globalThis.cookieStore.set === "function") {
253
+ try {
254
+ await globalThis.cookieStore.set(key, String(value));
255
+ return;
256
+ } catch (_) {
257
+ // Fall through to document.cookie.
258
+ }
259
+ }
260
+ if (typeof document === "undefined") return;
261
+ document.cookie = `${key}=${encodeURIComponent(String(value))}; path=/; SameSite=Lax`;
262
+ }
263
+
264
+ function browserAnonHeaders(baseUrl) {
265
+ const origin = normalizeBaseUrl(baseUrl);
266
+ return {
267
+ accept: "*/*",
268
+ origin,
269
+ referer: `${origin}/`
270
+ };
271
+ }
272
+
273
+ function browserJoinHeaders(baseUrl) {
274
+ const origin = normalizeBaseUrl(baseUrl);
275
+ return {
276
+ accept: "*/*",
277
+ "content-type": "application/json",
278
+ origin,
279
+ referer: `${origin}/`
280
+ };
281
+ }
282
+
283
+ function joinRejectMessage(code, {neverLoad, ship, server} = {}) {
284
+ if (code === 4007 && neverLoad) {
285
+ const name = ship?.name ? ` "${ship.name}"` : "";
286
+ const id = ship?.id != null ? ` (${ship.id})` : "";
287
+ const serverText = server?.description ? ` on ${server.description}` : "";
288
+ return `join rejected: 4007. ${name}${id}${serverText} could not be joined with never_load=true. Use startShip(server, ship) instead of joinShip(server, ship) when loading or starting a saved ship.`;
289
+ }
290
+ return `join rejected: ${code}`;
291
+ }
@@ -0,0 +1 @@
1
+ export {Connection} from "../index.js";
@@ -0,0 +1,31 @@
1
+ import {DEFAULT_BASE_URL} from "../constants.js";
2
+ import {normalizeBaseUrl} from "../runtime.js";
3
+ import {cookieName} from "./cookies.js";
4
+
5
+ export class Connection {
6
+ constructor(session, gameToken, netPort, serverId, server = null) {
7
+ if (!session) throw new Error("Connection requires a session");
8
+ if (!gameToken && !session.ambientAuth) throw new Error("Connection requires a game token");
9
+ if (netPort == null) throw new Error("Connection requires a net port");
10
+
11
+ this.session = session;
12
+ this.baseUrl = normalizeBaseUrl(session.baseUrl || DEFAULT_BASE_URL);
13
+ this.gameToken = gameToken ? String(gameToken) : "";
14
+ this.netPort = Number(netPort);
15
+ this.server = serverId && typeof serverId === "object" ? serverId : server;
16
+ this.serverId = Number(serverId && typeof serverId === "object" ? serverId.index ?? serverId.id : serverId);
17
+
18
+ this.session.cookies?.set(cookieName(this.baseUrl, "game_token"), this.gameToken);
19
+ }
20
+
21
+ toJSON() {
22
+ return {
23
+ baseUrl: this.baseUrl,
24
+ gameToken: this.gameToken,
25
+ netPort: this.netPort,
26
+ serverId: this.serverId,
27
+ server: this.server,
28
+ session: this.session?.toJSON?.() || this.session
29
+ };
30
+ }
31
+ }
@@ -0,0 +1,85 @@
1
+ import {DEFAULT_BASE_URL} from "../constants.js";
2
+ import {hostName, normalizeBaseUrl} from "../runtime.js";
3
+
4
+ export function cookiePrefix(baseUrl = DEFAULT_BASE_URL) {
5
+ const host = hostName(baseUrl);
6
+ if (host === "test.drednot.io") return "test_";
7
+ if (host === "local.drednot.io") return "local_";
8
+ return "";
9
+ }
10
+
11
+ export const cookieName = (baseUrl, name) => `${cookiePrefix(baseUrl)}${name}`;
12
+
13
+ export function parseCookies(text = "") {
14
+ const cookies = new Map();
15
+ for (const part of String(text).split(/;\s*/)) {
16
+ const index = part.indexOf("=");
17
+ if (index <= 0) continue;
18
+ cookies.set(part.slice(0, index).trim(), decodeURIComponent(part.slice(index + 1).trim()));
19
+ }
20
+ return cookies;
21
+ }
22
+
23
+ export function browserCookies() {
24
+ return canReadDocumentCookie() ? parseCookies(document.cookie || "") : new Map();
25
+ }
26
+
27
+ export function canReadDocumentCookie() {
28
+ try {
29
+ return typeof document !== "undefined" && typeof document.cookie === "string";
30
+ } catch (_) {
31
+ return false;
32
+ }
33
+ }
34
+
35
+ export function hasCookieStore() {
36
+ return typeof globalThis.cookieStore !== "undefined" && typeof globalThis.cookieStore.getAll === "function";
37
+ }
38
+
39
+ export async function readBrowserCookie(name) {
40
+ if (hasCookieStore()) {
41
+ try {
42
+ const cookie = typeof globalThis.cookieStore.get === "function" ? await globalThis.cookieStore.get(name) : null;
43
+ if (cookie?.value != null) return cookie.value;
44
+ } catch (_) {
45
+ // Fall through to document.cookie below.
46
+ }
47
+ }
48
+ return browserCookies().get(name) || "";
49
+ }
50
+
51
+ export function setCookieValues(baseUrl, source = {}) {
52
+ const base = normalizeBaseUrl(baseUrl);
53
+ const values = source instanceof Map ? source : source?.cookies instanceof Map ? source.cookies : Object.entries(source || {});
54
+ const cookies = new Map(values);
55
+ const prefix = cookiePrefix(base);
56
+ const anonKey = source.anonKey || source.anon_key;
57
+ const gameSession = source.gameSession || source.game_session;
58
+ const noticeVersion = source.noticeVersion || source.notice_version;
59
+ if (anonKey && !cookies.has(`${prefix}anon_key`)) cookies.set(`${prefix}anon_key`, anonKey);
60
+ if (gameSession && !cookies.has(`${prefix}game_session`)) cookies.set(`${prefix}game_session`, gameSession);
61
+ if (noticeVersion && !cookies.has(`${prefix}notice_version`)) cookies.set(`${prefix}notice_version`, noticeVersion);
62
+ return cookies;
63
+ }
64
+
65
+ export function cookieHeader(baseUrl, source = {}) {
66
+ return [...setCookieValues(baseUrl, source)].map(([key, value]) => `${key}=${encodeURIComponent(value)}`).join("; ");
67
+ }
68
+
69
+ export function readSetCookies(headers, names = ["game_session", "anon_key"], baseUrl = DEFAULT_BASE_URL) {
70
+ const values = typeof headers?.getSetCookie === "function"
71
+ ? headers.getSetCookie()
72
+ : headers?.get?.("set-cookie") ? [headers.get("set-cookie")] : [];
73
+ const found = new Map();
74
+ const prefix = cookiePrefix(baseUrl);
75
+ for (const header of values) {
76
+ for (const name of names) {
77
+ for (const key of prefix ? [name, `${prefix}${name}`] : [name]) {
78
+ const regex = new RegExp(`(?:^|,\\s*)${key}=([^;]+)`, "g");
79
+ let match;
80
+ while ((match = regex.exec(header))) found.set(key, decodeURIComponent(match[1]));
81
+ }
82
+ }
83
+ }
84
+ return found;
85
+ }
@@ -0,0 +1,34 @@
1
+ import {getFetch, isBrowser, normalizeBaseUrl, resolveUrl} from "../runtime.js";
2
+ import {cookieHeader} from "./cookies.js";
3
+
4
+ export class HttpClient {
5
+ constructor({baseUrl, session = null} = {}) {
6
+ this.baseUrl = normalizeBaseUrl(baseUrl);
7
+ this.session = session;
8
+ }
9
+
10
+ async request(path, init = {}) {
11
+ const headers = new Headers(init.headers || {});
12
+ const body = this.#body(init.body, headers);
13
+ const cookie = this.session && !isBrowser() ? cookieHeader(this.baseUrl, this.session) : "";
14
+ if (cookie) headers.set("cookie", cookie);
15
+ return getFetch()(resolveUrl(this.baseUrl, path), {
16
+ ...init,
17
+ body,
18
+ headers,
19
+ credentials: isBrowser() ? "include" : init.credentials
20
+ });
21
+ }
22
+
23
+ async json(path, init = {}) {
24
+ const response = await this.request(path, init);
25
+ if (!response.ok) throw new Error(`${path} failed: ${response.status} ${response.statusText}`);
26
+ return response.json();
27
+ }
28
+
29
+ #body(body, headers) {
30
+ if (body == null || typeof body === "string" || body instanceof ArrayBuffer || ArrayBuffer.isView(body)) return body;
31
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
32
+ return JSON.stringify(body);
33
+ }
34
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./connection.js";
2
+ export * from "./servers.js";
3
+ export * from "./ships.js";
4
+ export * from "./session.js";
@@ -0,0 +1,4 @@
1
+ export {Connection} from "./connection.js";
2
+ export {fetchGameVersion, fetchNoticeVersion, fetchServers, resolveServer} from "./servers.js";
3
+ export {fetchShips, fetchShipList, createShipSpec, createInviteShipSpec, shipRef} from "./ships.js";
4
+ export {Session, AnonSession, createSession, createAnonSession, createAnonToken} from "./session.js";
@@ -0,0 +1,7 @@
1
+ export {
2
+ fetchNoticeVersion,
3
+ fetchGameVersion,
4
+ fetchServers,
5
+ type Server,
6
+ type ServerRef
7
+ } from "../index.js";