dredless 0.2.0 → 0.3.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/README.md +65 -48
- package/dist/browser/dredless.global.js +12047 -0
- package/dist/browser/dredless.global.js.map +7 -0
- package/docs/API_REFERENCE.md +2481 -2481
- package/docs/BROWSER_API.md +98 -98
- package/docs/NODE_API.md +359 -359
- package/package.json +40 -35
- package/src/browser-global.js +3 -0
- package/src/browser.d.ts +212 -212
- package/src/browser.js +133 -133
- package/src/client/core.js +1202 -1202
- package/src/client/domains/index.js +1818 -1818
- package/src/client/index.d.ts +2 -2
- package/src/client/index.js +1 -1
- package/src/compression/lz4.d.ts +1 -1
- package/src/compression/lz4.js +112 -112
- package/src/constants.js +74 -74
- package/src/crypto/chacha-native.d.ts +9 -9
- package/src/crypto/chacha-native.js +48 -48
- package/src/crypto/chacha.d.ts +25 -25
- package/src/crypto/chacha.js +274 -274
- package/src/events.js +39 -39
- package/src/index.d.ts +2277 -2277
- package/src/index.js +125 -125
- package/src/network/browser-session.js +291 -291
- package/src/network/connection.d.ts +1 -1
- package/src/network/connection.js +31 -31
- package/src/network/cookies.js +85 -85
- package/src/network/http.js +34 -34
- package/src/network/index.d.ts +4 -4
- package/src/network/index.js +4 -4
- package/src/network/servers.d.ts +7 -7
- package/src/network/servers.js +133 -133
- package/src/network/session.d.ts +7 -7
- package/src/network/session.js +320 -320
- package/src/network/ships.d.ts +12 -12
- package/src/network/ships.js +73 -73
- package/src/node.d.ts +2 -2
- package/src/node.js +8 -8
- package/src/protocol/codec/binary.d.ts +3 -3
- package/src/protocol/codec/binary.js +52 -52
- package/src/protocol/codec/msgpack.d.ts +3 -3
- package/src/protocol/codec/msgpack.js +555 -555
- package/src/protocol/inbound/frame.d.ts +1 -1
- package/src/protocol/inbound/frame.js +8 -8
- package/src/protocol/inbound/world-payload.d.ts +3 -3
- package/src/protocol/inbound/world-payload.js +21 -21
- package/src/protocol/index.d.ts +11 -11
- package/src/protocol/index.js +19 -19
- package/src/protocol/outbound/blueprint.d.ts +22 -22
- package/src/protocol/outbound/blueprint.js +36 -36
- package/src/protocol/outbound/commands.d.ts +3 -3
- package/src/protocol/outbound/commands.js +149 -149
- package/src/protocol/outbound/comms.d.ts +28 -28
- package/src/protocol/outbound/comms.js +40 -40
- package/src/protocol/outbound/inventory.d.ts +64 -64
- package/src/protocol/outbound/inventory.js +147 -147
- package/src/protocol/outbound/ship-management.d.ts +84 -84
- package/src/protocol/outbound/ship-management.js +221 -221
- package/src/protocol/outbound/sign.d.ts +18 -18
- package/src/protocol/outbound/sign.js +45 -45
- package/src/protocol/outbound/ui-config.d.ts +157 -157
- package/src/protocol/outbound/ui-config.js +578 -578
- package/src/runtime.js +36 -36
- package/src/state/generator-maze.d.ts +32 -32
- package/src/state/generator-maze.js +370 -370
- package/src/state/index.d.ts +3 -3
- package/src/state/index.js +3 -3
- package/src/state/items.js +20 -20
- package/src/state/model/decoder.js +1554 -1554
- package/src/state/model/index.d.ts +33 -33
- package/src/state/model/index.js +2 -2
- package/src/state/model/loader-config.js +1020 -1020
- package/src/state/model/reader.js +69 -69
- package/src/state/model/state.js +1257 -1257
- package/src/state/overworld.js +72 -72
- package/src/state/tilesets.js +53 -53
- package/src/state/world/index.d.ts +29 -29
- package/src/state/world/index.js +180 -180
- package/src/state/world/state.js +297 -297
package/src/network/session.js
CHANGED
|
@@ -1,320 +1,320 @@
|
|
|
1
|
-
import {DEFAULT_BASE_URL, DEFAULT_NOTICE_VERSION, JOIN_USER_AGENT} from "../constants.js";
|
|
2
|
-
import {asNumber, isNode, normalizeBaseUrl} from "../runtime.js";
|
|
3
|
-
import {cookieName, cookiePrefix, readSetCookies, setCookieValues} from "./cookies.js";
|
|
4
|
-
import {HttpClient} from "./http.js";
|
|
5
|
-
import {fetchGameVersion, resolveServer, serverId} from "./servers.js";
|
|
6
|
-
import {createInviteShipSpec, createShipSpec, fetchShipList, fetchShips, shipRef} from "./ships.js";
|
|
7
|
-
import {Connection} from "./connection.js";
|
|
8
|
-
import {DredlessClient} from "../client/index.js";
|
|
9
|
-
|
|
10
|
-
export class Session {
|
|
11
|
-
constructor(gameSession = null, gameVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
12
|
-
this.baseUrl = baseUrl;
|
|
13
|
-
this.cookies = setCookieValues(this.baseUrl, {});
|
|
14
|
-
this.gameVersion = gameVersion || null;
|
|
15
|
-
this._noticeVersion = null;
|
|
16
|
-
this.account = null;
|
|
17
|
-
this.geoServer = null;
|
|
18
|
-
this.upgraded = false;
|
|
19
|
-
this.isRegistered = false;
|
|
20
|
-
this.showAds = false;
|
|
21
|
-
this.forceTutorial = false;
|
|
22
|
-
this.ban = null;
|
|
23
|
-
if (gameSession) this.cookies.set(cookieName(this.baseUrl, "game_session"), String(gameSession));
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
get gameSession() {
|
|
27
|
-
return this.cookies.get(cookieName(this.baseUrl, "game_session")) || "";
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
get gameToken() {
|
|
31
|
-
return this.cookies.get(cookieName(this.baseUrl, "game_token")) || "";
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
get noticeVersion() {
|
|
35
|
-
return this._noticeVersion ?? this.cookies.get(cookieName(this.baseUrl, "notice_version")) ?? null;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
set noticeVersion(value) {
|
|
39
|
-
this._noticeVersion = value == null ? null : Number(value);
|
|
40
|
-
const key = cookieName(this.baseUrl, "notice_version");
|
|
41
|
-
if (value == null) this.cookies.delete(key);
|
|
42
|
-
else this.cookies.set(key, String(value));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
request(path, init = {}) {
|
|
46
|
-
return new HttpClient({baseUrl: this.baseUrl, session: this}).request(path, init);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async fetchAccountStatus() {
|
|
50
|
-
const response = await this.request("account/status", {
|
|
51
|
-
method: "GET",
|
|
52
|
-
mode: "cors",
|
|
53
|
-
credentials: "include",
|
|
54
|
-
referrer: `${this.baseUrl}/`,
|
|
55
|
-
headers: statusHeaders(this.baseUrl)
|
|
56
|
-
});
|
|
57
|
-
if (!response.ok) throw new Error(`account/status failed: ${response.status} ${response.statusText}`);
|
|
58
|
-
this.mergeSetCookies(response);
|
|
59
|
-
const raw = await response.json();
|
|
60
|
-
this.#readStatus(raw);
|
|
61
|
-
return raw;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async fetchShips(server) {
|
|
65
|
-
return fetchShips(this, server);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
async fetchShipList(server) {
|
|
69
|
-
return fetchShipList(this, server);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async joinShipConnection(server, ship = null) {
|
|
73
|
-
return this.#startShipConnection(server, ship, true);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
async startShipConnection(server, ship = null) {
|
|
77
|
-
return this.#startShipConnection(server, ship, false);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async startNewShipConnection(server, name = "", color = "") {
|
|
81
|
-
return this.#startShipConnection(server, createShipSpec(name, color), false);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async joinInviteConnection(server, code) {
|
|
85
|
-
return this.#startShipConnection(server, createInviteShipSpec(code), false);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async joinShip(server, ship = null) {
|
|
89
|
-
return readyClient(await this.joinShipConnection(server, ship));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async startShip(server, ship = null) {
|
|
93
|
-
return readyClient(await this.startShipConnection(server, ship));
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async startNewShip(server, name = "", color = "") {
|
|
97
|
-
return readyClient(await this.startNewShipConnection(server, name, color));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async joinInvite(server, code) {
|
|
101
|
-
return readyClient(await this.joinInviteConnection(server, code));
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
toJSON() {
|
|
105
|
-
return {
|
|
106
|
-
baseUrl: this.baseUrl,
|
|
107
|
-
gameSession: this.gameSession,
|
|
108
|
-
gameToken: this.gameToken,
|
|
109
|
-
gameVersion: this.gameVersion,
|
|
110
|
-
noticeVersion: this.noticeVersion,
|
|
111
|
-
cookies: Object.fromEntries(this.cookies),
|
|
112
|
-
account: this.account,
|
|
113
|
-
geoServer: this.geoServer,
|
|
114
|
-
upgraded: this.upgraded,
|
|
115
|
-
isRegistered: this.isRegistered,
|
|
116
|
-
showAds: this.showAds,
|
|
117
|
-
forceTutorial: this.forceTutorial,
|
|
118
|
-
ban: this.ban,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async #startShipConnection(server, ship, neverLoad) {
|
|
123
|
-
if (server == null) throw new Error("A server id or server object is required");
|
|
124
|
-
const resolvedServer = await resolveServer(server, this.baseUrl);
|
|
125
|
-
if (!resolvedServer) throw new Error(`Unable to resolve server ${serverId(server)}`);
|
|
126
|
-
if (!this.gameVersion) this.gameVersion = await fetchGameVersion(this.baseUrl);
|
|
127
|
-
if (this.noticeVersion == null) this.noticeVersion = DEFAULT_NOTICE_VERSION;
|
|
128
|
-
|
|
129
|
-
const joinShip = ship == null ? createShipSpec("", "") : shipRef(ship);
|
|
130
|
-
const response = await this.request("join", {
|
|
131
|
-
method: "POST",
|
|
132
|
-
mode: "cors",
|
|
133
|
-
credentials: "include",
|
|
134
|
-
referrer: `${this.baseUrl}/`,
|
|
135
|
-
body: {
|
|
136
|
-
join_info: {
|
|
137
|
-
game_version: this.gameVersion,
|
|
138
|
-
hide_badges: false,
|
|
139
|
-
never_load: Boolean(neverLoad),
|
|
140
|
-
server_id: resolvedServer.index,
|
|
141
|
-
ship: joinShip
|
|
142
|
-
}
|
|
143
|
-
},
|
|
144
|
-
headers: joinHeaders(this.baseUrl)
|
|
145
|
-
});
|
|
146
|
-
if (!response.ok) throw new Error(`Join request failed: ${await response.text()}`);
|
|
147
|
-
this.mergeSetCookies(response, ["game_session", "anon_key", "game_token"]);
|
|
148
|
-
const join = await response.json();
|
|
149
|
-
if (join.reject != null) {
|
|
150
|
-
const error = new Error(joinRejectMessage(join.reject, {
|
|
151
|
-
neverLoad,
|
|
152
|
-
ship: joinShip,
|
|
153
|
-
server: resolvedServer
|
|
154
|
-
}));
|
|
155
|
-
error.code = join.reject;
|
|
156
|
-
error.join = join;
|
|
157
|
-
throw error;
|
|
158
|
-
}
|
|
159
|
-
if (join.okay !== true) throw new Error("Bad join result");
|
|
160
|
-
|
|
161
|
-
const token = this.gameToken;
|
|
162
|
-
if (!token) throw new Error("Join did not return a game_token cookie");
|
|
163
|
-
return new Connection(this, token, join.net_port, Number.isInteger(join.server_id) ? join.server_id : resolvedServer.index, resolvedServer);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
mergeSetCookies(response, names = ["game_session", "anon_key"]) {
|
|
167
|
-
const prefix = cookiePrefix(this.baseUrl);
|
|
168
|
-
for (const [name, value] of readSetCookies(response.headers, names, this.baseUrl)) {
|
|
169
|
-
const cookieName = name.startsWith(prefix) ? name : `${prefix}${name}`;
|
|
170
|
-
this.cookies.set(cookieName, value);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
#readStatus(raw) {
|
|
175
|
-
const account = raw?.account && typeof raw.account === "object" ? raw.account : null;
|
|
176
|
-
this.account = account ? {
|
|
177
|
-
name: account.name || account.username || account.display_name || account.displayName || account.handle || "Unknown",
|
|
178
|
-
color: asNumber(account.color, 0),
|
|
179
|
-
game_rank: asNumber(account.game_rank, 0),
|
|
180
|
-
user_badges: Array.isArray(account.user_badges) ? account.user_badges.filter((item) => typeof item === "string") : [],
|
|
181
|
-
is_registered: Boolean(account.is_registered)
|
|
182
|
-
} : null;
|
|
183
|
-
this.geoServer = asNumber(raw?.geo_server ?? raw?.geoServer, null);
|
|
184
|
-
this.upgraded = Boolean(raw?.upgraded ?? account?.upgraded);
|
|
185
|
-
this.isRegistered = Boolean(account?.is_registered);
|
|
186
|
-
this.showAds = Boolean(raw?.show_ads ?? raw?.showAds);
|
|
187
|
-
this.forceTutorial = Boolean(raw?.force_tutorial ?? raw?.forceTutorial);
|
|
188
|
-
this.ban = raw?.ban || null;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export class AnonSession extends Session {
|
|
193
|
-
constructor(gameSession = null, anonKey = null, gameVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
194
|
-
super(gameSession, gameVersion, baseUrl);
|
|
195
|
-
if (anonKey) this.cookies.set(cookieName(this.baseUrl, "anon_key"), String(anonKey));
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
get anonKey() {
|
|
199
|
-
return this.cookies.get(cookieName(this.baseUrl, "anon_key")) || "";
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
toJSON() {
|
|
203
|
-
return {
|
|
204
|
-
...super.toJSON(),
|
|
205
|
-
anonKey: this.anonKey
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export async function createSession(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
211
|
-
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
|
|
212
|
-
const session = new Session(null, null, resolvedBaseUrl);
|
|
213
|
-
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
214
|
-
await session.fetchAccountStatus();
|
|
215
|
-
return session;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export async function createAnonToken(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
219
|
-
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
|
|
220
|
-
const session = new AnonSession(null, null, null, resolvedBaseUrl);
|
|
221
|
-
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
222
|
-
const response = await session.request("account/login/anon", {
|
|
223
|
-
method: "POST",
|
|
224
|
-
mode: "cors",
|
|
225
|
-
credentials: "include",
|
|
226
|
-
referrer: `${session.baseUrl}/`,
|
|
227
|
-
headers: anonHeaders(session.baseUrl)
|
|
228
|
-
});
|
|
229
|
-
if (response.status === 503) throw new Error("Anon token creation is rate limited by the server");
|
|
230
|
-
if (!response.ok) throw new Error(`account/login/anon failed: ${response.status} ${response.statusText}`);
|
|
231
|
-
session.mergeSetCookies(response, ["anon_key", "game_session"]);
|
|
232
|
-
if (!session.anonKey) throw new Error("Unable to obtain anon_key");
|
|
233
|
-
return session.anonKey;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export async function createAnonSession(anonKey = null, noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
237
|
-
if (typeof noticeVersion === "string" && noticeVersion.includes("://") && (baseUrl == null || baseUrl === DEFAULT_BASE_URL)) {
|
|
238
|
-
baseUrl = noticeVersion;
|
|
239
|
-
noticeVersion = null;
|
|
240
|
-
}
|
|
241
|
-
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 3 ? arguments[2] : baseUrl);
|
|
242
|
-
const resolvedAnonKey = anonKey || await createAnonToken(noticeVersion, resolvedBaseUrl);
|
|
243
|
-
const session = new AnonSession(null, resolvedAnonKey, null, resolvedBaseUrl);
|
|
244
|
-
session.baseUrl = resolvedBaseUrl;
|
|
245
|
-
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
246
|
-
await session.fetchAccountStatus();
|
|
247
|
-
return session;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async function readyClient(connection) {
|
|
251
|
-
const client = new DredlessClient(connection);
|
|
252
|
-
await client.whenReady();
|
|
253
|
-
return client;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function internalNoticeVersion(value, baseUrl = DEFAULT_BASE_URL) {
|
|
257
|
-
if (value != null) return value;
|
|
258
|
-
try {
|
|
259
|
-
const {fetchNoticeVersion} = await import("./servers.js");
|
|
260
|
-
return await fetchNoticeVersion(baseUrl);
|
|
261
|
-
} catch (_) {
|
|
262
|
-
return DEFAULT_NOTICE_VERSION;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function statusHeaders(baseUrl) {
|
|
267
|
-
const headers = {accept: "*/*"};
|
|
268
|
-
if (isNode()) {
|
|
269
|
-
Object.assign(headers, {
|
|
270
|
-
"user-agent": JOIN_USER_AGENT,
|
|
271
|
-
"accept-language": "en-US,en;q=0.9",
|
|
272
|
-
referer: `${normalizeBaseUrl(baseUrl)}/`,
|
|
273
|
-
dnt: "1",
|
|
274
|
-
"sec-gpc": "1",
|
|
275
|
-
"sec-fetch-dest": "empty",
|
|
276
|
-
"sec-fetch-mode": "cors",
|
|
277
|
-
"sec-fetch-site": "same-origin"
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
return headers;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function anonHeaders(baseUrl) {
|
|
284
|
-
const origin = normalizeBaseUrl(baseUrl);
|
|
285
|
-
return {
|
|
286
|
-
accept: "*/*",
|
|
287
|
-
origin,
|
|
288
|
-
referer: `${origin}/`
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function joinHeaders(baseUrl) {
|
|
293
|
-
const headers = {
|
|
294
|
-
accept: "*/*",
|
|
295
|
-
"accept-language": "en-US,en;q=0.9",
|
|
296
|
-
"content-type": "application/json",
|
|
297
|
-
origin: normalizeBaseUrl(baseUrl),
|
|
298
|
-
referer: `${normalizeBaseUrl(baseUrl)}/`,
|
|
299
|
-
pragma: "no-cache",
|
|
300
|
-
"cache-control": "no-cache",
|
|
301
|
-
dnt: "1",
|
|
302
|
-
"sec-gpc": "1",
|
|
303
|
-
"sec-fetch-dest": "empty",
|
|
304
|
-
"sec-fetch-mode": "cors",
|
|
305
|
-
"sec-fetch-site": "same-origin",
|
|
306
|
-
priority: "u=0"
|
|
307
|
-
};
|
|
308
|
-
if (isNode()) headers["user-agent"] = JOIN_USER_AGENT;
|
|
309
|
-
return headers;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function joinRejectMessage(code, {neverLoad, ship, server} = {}) {
|
|
313
|
-
if (code === 4007 && neverLoad) {
|
|
314
|
-
const name = ship?.name ? ` "${ship.name}"` : "";
|
|
315
|
-
const id = ship?.id != null ? ` (${ship.id})` : "";
|
|
316
|
-
const serverText = server?.description ? ` on ${server.description}` : "";
|
|
317
|
-
return `join rejected: 4007. ${name}${id}${serverText} could not be joined with never_load=true. Use session.startShip(server, ship) instead of session.joinShip(server, ship) when loading or starting a saved ship.`;
|
|
318
|
-
}
|
|
319
|
-
return `join rejected: ${code}`;
|
|
320
|
-
}
|
|
1
|
+
import {DEFAULT_BASE_URL, DEFAULT_NOTICE_VERSION, JOIN_USER_AGENT} from "../constants.js";
|
|
2
|
+
import {asNumber, isNode, normalizeBaseUrl} from "../runtime.js";
|
|
3
|
+
import {cookieName, cookiePrefix, readSetCookies, setCookieValues} from "./cookies.js";
|
|
4
|
+
import {HttpClient} from "./http.js";
|
|
5
|
+
import {fetchGameVersion, resolveServer, serverId} from "./servers.js";
|
|
6
|
+
import {createInviteShipSpec, createShipSpec, fetchShipList, fetchShips, shipRef} from "./ships.js";
|
|
7
|
+
import {Connection} from "./connection.js";
|
|
8
|
+
import {DredlessClient} from "../client/index.js";
|
|
9
|
+
|
|
10
|
+
export class Session {
|
|
11
|
+
constructor(gameSession = null, gameVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
12
|
+
this.baseUrl = baseUrl;
|
|
13
|
+
this.cookies = setCookieValues(this.baseUrl, {});
|
|
14
|
+
this.gameVersion = gameVersion || null;
|
|
15
|
+
this._noticeVersion = null;
|
|
16
|
+
this.account = null;
|
|
17
|
+
this.geoServer = null;
|
|
18
|
+
this.upgraded = false;
|
|
19
|
+
this.isRegistered = false;
|
|
20
|
+
this.showAds = false;
|
|
21
|
+
this.forceTutorial = false;
|
|
22
|
+
this.ban = null;
|
|
23
|
+
if (gameSession) this.cookies.set(cookieName(this.baseUrl, "game_session"), String(gameSession));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get gameSession() {
|
|
27
|
+
return this.cookies.get(cookieName(this.baseUrl, "game_session")) || "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get gameToken() {
|
|
31
|
+
return this.cookies.get(cookieName(this.baseUrl, "game_token")) || "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get noticeVersion() {
|
|
35
|
+
return this._noticeVersion ?? this.cookies.get(cookieName(this.baseUrl, "notice_version")) ?? null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set noticeVersion(value) {
|
|
39
|
+
this._noticeVersion = value == null ? null : Number(value);
|
|
40
|
+
const key = cookieName(this.baseUrl, "notice_version");
|
|
41
|
+
if (value == null) this.cookies.delete(key);
|
|
42
|
+
else this.cookies.set(key, String(value));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
request(path, init = {}) {
|
|
46
|
+
return new HttpClient({baseUrl: this.baseUrl, session: this}).request(path, init);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async fetchAccountStatus() {
|
|
50
|
+
const response = await this.request("account/status", {
|
|
51
|
+
method: "GET",
|
|
52
|
+
mode: "cors",
|
|
53
|
+
credentials: "include",
|
|
54
|
+
referrer: `${this.baseUrl}/`,
|
|
55
|
+
headers: statusHeaders(this.baseUrl)
|
|
56
|
+
});
|
|
57
|
+
if (!response.ok) throw new Error(`account/status failed: ${response.status} ${response.statusText}`);
|
|
58
|
+
this.mergeSetCookies(response);
|
|
59
|
+
const raw = await response.json();
|
|
60
|
+
this.#readStatus(raw);
|
|
61
|
+
return raw;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async fetchShips(server) {
|
|
65
|
+
return fetchShips(this, server);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async fetchShipList(server) {
|
|
69
|
+
return fetchShipList(this, server);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async joinShipConnection(server, ship = null) {
|
|
73
|
+
return this.#startShipConnection(server, ship, true);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async startShipConnection(server, ship = null) {
|
|
77
|
+
return this.#startShipConnection(server, ship, false);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async startNewShipConnection(server, name = "", color = "") {
|
|
81
|
+
return this.#startShipConnection(server, createShipSpec(name, color), false);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async joinInviteConnection(server, code) {
|
|
85
|
+
return this.#startShipConnection(server, createInviteShipSpec(code), false);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async joinShip(server, ship = null) {
|
|
89
|
+
return readyClient(await this.joinShipConnection(server, ship));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async startShip(server, ship = null) {
|
|
93
|
+
return readyClient(await this.startShipConnection(server, ship));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async startNewShip(server, name = "", color = "") {
|
|
97
|
+
return readyClient(await this.startNewShipConnection(server, name, color));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async joinInvite(server, code) {
|
|
101
|
+
return readyClient(await this.joinInviteConnection(server, code));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
toJSON() {
|
|
105
|
+
return {
|
|
106
|
+
baseUrl: this.baseUrl,
|
|
107
|
+
gameSession: this.gameSession,
|
|
108
|
+
gameToken: this.gameToken,
|
|
109
|
+
gameVersion: this.gameVersion,
|
|
110
|
+
noticeVersion: this.noticeVersion,
|
|
111
|
+
cookies: Object.fromEntries(this.cookies),
|
|
112
|
+
account: this.account,
|
|
113
|
+
geoServer: this.geoServer,
|
|
114
|
+
upgraded: this.upgraded,
|
|
115
|
+
isRegistered: this.isRegistered,
|
|
116
|
+
showAds: this.showAds,
|
|
117
|
+
forceTutorial: this.forceTutorial,
|
|
118
|
+
ban: this.ban,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async #startShipConnection(server, ship, neverLoad) {
|
|
123
|
+
if (server == null) throw new Error("A server id or server object is required");
|
|
124
|
+
const resolvedServer = await resolveServer(server, this.baseUrl);
|
|
125
|
+
if (!resolvedServer) throw new Error(`Unable to resolve server ${serverId(server)}`);
|
|
126
|
+
if (!this.gameVersion) this.gameVersion = await fetchGameVersion(this.baseUrl);
|
|
127
|
+
if (this.noticeVersion == null) this.noticeVersion = DEFAULT_NOTICE_VERSION;
|
|
128
|
+
|
|
129
|
+
const joinShip = ship == null ? createShipSpec("", "") : shipRef(ship);
|
|
130
|
+
const response = await this.request("join", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
mode: "cors",
|
|
133
|
+
credentials: "include",
|
|
134
|
+
referrer: `${this.baseUrl}/`,
|
|
135
|
+
body: {
|
|
136
|
+
join_info: {
|
|
137
|
+
game_version: this.gameVersion,
|
|
138
|
+
hide_badges: false,
|
|
139
|
+
never_load: Boolean(neverLoad),
|
|
140
|
+
server_id: resolvedServer.index,
|
|
141
|
+
ship: joinShip
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
headers: joinHeaders(this.baseUrl)
|
|
145
|
+
});
|
|
146
|
+
if (!response.ok) throw new Error(`Join request failed: ${await response.text()}`);
|
|
147
|
+
this.mergeSetCookies(response, ["game_session", "anon_key", "game_token"]);
|
|
148
|
+
const join = await response.json();
|
|
149
|
+
if (join.reject != null) {
|
|
150
|
+
const error = new Error(joinRejectMessage(join.reject, {
|
|
151
|
+
neverLoad,
|
|
152
|
+
ship: joinShip,
|
|
153
|
+
server: resolvedServer
|
|
154
|
+
}));
|
|
155
|
+
error.code = join.reject;
|
|
156
|
+
error.join = join;
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
if (join.okay !== true) throw new Error("Bad join result");
|
|
160
|
+
|
|
161
|
+
const token = this.gameToken;
|
|
162
|
+
if (!token) throw new Error("Join did not return a game_token cookie");
|
|
163
|
+
return new Connection(this, token, join.net_port, Number.isInteger(join.server_id) ? join.server_id : resolvedServer.index, resolvedServer);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
mergeSetCookies(response, names = ["game_session", "anon_key"]) {
|
|
167
|
+
const prefix = cookiePrefix(this.baseUrl);
|
|
168
|
+
for (const [name, value] of readSetCookies(response.headers, names, this.baseUrl)) {
|
|
169
|
+
const cookieName = name.startsWith(prefix) ? name : `${prefix}${name}`;
|
|
170
|
+
this.cookies.set(cookieName, value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#readStatus(raw) {
|
|
175
|
+
const account = raw?.account && typeof raw.account === "object" ? raw.account : null;
|
|
176
|
+
this.account = account ? {
|
|
177
|
+
name: account.name || account.username || account.display_name || account.displayName || account.handle || "Unknown",
|
|
178
|
+
color: asNumber(account.color, 0),
|
|
179
|
+
game_rank: asNumber(account.game_rank, 0),
|
|
180
|
+
user_badges: Array.isArray(account.user_badges) ? account.user_badges.filter((item) => typeof item === "string") : [],
|
|
181
|
+
is_registered: Boolean(account.is_registered)
|
|
182
|
+
} : null;
|
|
183
|
+
this.geoServer = asNumber(raw?.geo_server ?? raw?.geoServer, null);
|
|
184
|
+
this.upgraded = Boolean(raw?.upgraded ?? account?.upgraded);
|
|
185
|
+
this.isRegistered = Boolean(account?.is_registered);
|
|
186
|
+
this.showAds = Boolean(raw?.show_ads ?? raw?.showAds);
|
|
187
|
+
this.forceTutorial = Boolean(raw?.force_tutorial ?? raw?.forceTutorial);
|
|
188
|
+
this.ban = raw?.ban || null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export class AnonSession extends Session {
|
|
193
|
+
constructor(gameSession = null, anonKey = null, gameVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
194
|
+
super(gameSession, gameVersion, baseUrl);
|
|
195
|
+
if (anonKey) this.cookies.set(cookieName(this.baseUrl, "anon_key"), String(anonKey));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get anonKey() {
|
|
199
|
+
return this.cookies.get(cookieName(this.baseUrl, "anon_key")) || "";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
toJSON() {
|
|
203
|
+
return {
|
|
204
|
+
...super.toJSON(),
|
|
205
|
+
anonKey: this.anonKey
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function createSession(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
211
|
+
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
|
|
212
|
+
const session = new Session(null, null, resolvedBaseUrl);
|
|
213
|
+
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
214
|
+
await session.fetchAccountStatus();
|
|
215
|
+
return session;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function createAnonToken(noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
219
|
+
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 2 ? arguments[1] : baseUrl);
|
|
220
|
+
const session = new AnonSession(null, null, null, resolvedBaseUrl);
|
|
221
|
+
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
222
|
+
const response = await session.request("account/login/anon", {
|
|
223
|
+
method: "POST",
|
|
224
|
+
mode: "cors",
|
|
225
|
+
credentials: "include",
|
|
226
|
+
referrer: `${session.baseUrl}/`,
|
|
227
|
+
headers: anonHeaders(session.baseUrl)
|
|
228
|
+
});
|
|
229
|
+
if (response.status === 503) throw new Error("Anon token creation is rate limited by the server");
|
|
230
|
+
if (!response.ok) throw new Error(`account/login/anon failed: ${response.status} ${response.statusText}`);
|
|
231
|
+
session.mergeSetCookies(response, ["anon_key", "game_session"]);
|
|
232
|
+
if (!session.anonKey) throw new Error("Unable to obtain anon_key");
|
|
233
|
+
return session.anonKey;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function createAnonSession(anonKey = null, noticeVersion = null, baseUrl = DEFAULT_BASE_URL) {
|
|
237
|
+
if (typeof noticeVersion === "string" && noticeVersion.includes("://") && (baseUrl == null || baseUrl === DEFAULT_BASE_URL)) {
|
|
238
|
+
baseUrl = noticeVersion;
|
|
239
|
+
noticeVersion = null;
|
|
240
|
+
}
|
|
241
|
+
const resolvedBaseUrl = normalizeBaseUrl(arguments.length >= 3 ? arguments[2] : baseUrl);
|
|
242
|
+
const resolvedAnonKey = anonKey || await createAnonToken(noticeVersion, resolvedBaseUrl);
|
|
243
|
+
const session = new AnonSession(null, resolvedAnonKey, null, resolvedBaseUrl);
|
|
244
|
+
session.baseUrl = resolvedBaseUrl;
|
|
245
|
+
session.noticeVersion = await internalNoticeVersion(noticeVersion, resolvedBaseUrl);
|
|
246
|
+
await session.fetchAccountStatus();
|
|
247
|
+
return session;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function readyClient(connection) {
|
|
251
|
+
const client = new DredlessClient(connection);
|
|
252
|
+
await client.whenReady();
|
|
253
|
+
return client;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function internalNoticeVersion(value, baseUrl = DEFAULT_BASE_URL) {
|
|
257
|
+
if (value != null) return value;
|
|
258
|
+
try {
|
|
259
|
+
const {fetchNoticeVersion} = await import("./servers.js");
|
|
260
|
+
return await fetchNoticeVersion(baseUrl);
|
|
261
|
+
} catch (_) {
|
|
262
|
+
return DEFAULT_NOTICE_VERSION;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function statusHeaders(baseUrl) {
|
|
267
|
+
const headers = {accept: "*/*"};
|
|
268
|
+
if (isNode()) {
|
|
269
|
+
Object.assign(headers, {
|
|
270
|
+
"user-agent": JOIN_USER_AGENT,
|
|
271
|
+
"accept-language": "en-US,en;q=0.9",
|
|
272
|
+
referer: `${normalizeBaseUrl(baseUrl)}/`,
|
|
273
|
+
dnt: "1",
|
|
274
|
+
"sec-gpc": "1",
|
|
275
|
+
"sec-fetch-dest": "empty",
|
|
276
|
+
"sec-fetch-mode": "cors",
|
|
277
|
+
"sec-fetch-site": "same-origin"
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return headers;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function anonHeaders(baseUrl) {
|
|
284
|
+
const origin = normalizeBaseUrl(baseUrl);
|
|
285
|
+
return {
|
|
286
|
+
accept: "*/*",
|
|
287
|
+
origin,
|
|
288
|
+
referer: `${origin}/`
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function joinHeaders(baseUrl) {
|
|
293
|
+
const headers = {
|
|
294
|
+
accept: "*/*",
|
|
295
|
+
"accept-language": "en-US,en;q=0.9",
|
|
296
|
+
"content-type": "application/json",
|
|
297
|
+
origin: normalizeBaseUrl(baseUrl),
|
|
298
|
+
referer: `${normalizeBaseUrl(baseUrl)}/`,
|
|
299
|
+
pragma: "no-cache",
|
|
300
|
+
"cache-control": "no-cache",
|
|
301
|
+
dnt: "1",
|
|
302
|
+
"sec-gpc": "1",
|
|
303
|
+
"sec-fetch-dest": "empty",
|
|
304
|
+
"sec-fetch-mode": "cors",
|
|
305
|
+
"sec-fetch-site": "same-origin",
|
|
306
|
+
priority: "u=0"
|
|
307
|
+
};
|
|
308
|
+
if (isNode()) headers["user-agent"] = JOIN_USER_AGENT;
|
|
309
|
+
return headers;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function joinRejectMessage(code, {neverLoad, ship, server} = {}) {
|
|
313
|
+
if (code === 4007 && neverLoad) {
|
|
314
|
+
const name = ship?.name ? ` "${ship.name}"` : "";
|
|
315
|
+
const id = ship?.id != null ? ` (${ship.id})` : "";
|
|
316
|
+
const serverText = server?.description ? ` on ${server.description}` : "";
|
|
317
|
+
return `join rejected: 4007. ${name}${id}${serverText} could not be joined with never_load=true. Use session.startShip(server, ship) instead of session.joinShip(server, ship) when loading or starting a saved ship.`;
|
|
318
|
+
}
|
|
319
|
+
return `join rejected: ${code}`;
|
|
320
|
+
}
|
package/src/network/ships.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export {
|
|
2
|
-
fetchShips,
|
|
3
|
-
fetchShipList,
|
|
4
|
-
type Ship,
|
|
5
|
-
type ShipList,
|
|
6
|
-
type ShipRef,
|
|
7
|
-
type ShipSpec
|
|
8
|
-
} from "../index.js";
|
|
9
|
-
|
|
10
|
-
export function createShipSpec(name?: string, color?: string): ShipSpec;
|
|
11
|
-
|
|
12
|
-
export function createInviteShipSpec(code: string): ShipSpec;
|
|
1
|
+
export {
|
|
2
|
+
fetchShips,
|
|
3
|
+
fetchShipList,
|
|
4
|
+
type Ship,
|
|
5
|
+
type ShipList,
|
|
6
|
+
type ShipRef,
|
|
7
|
+
type ShipSpec
|
|
8
|
+
} from "../index.js";
|
|
9
|
+
|
|
10
|
+
export function createShipSpec(name?: string, color?: string): ShipSpec;
|
|
11
|
+
|
|
12
|
+
export function createInviteShipSpec(code: string): ShipSpec;
|