onelobby 0.3.0 → 0.4.1
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 +2 -2
- package/dist/chunking.d.ts +4 -0
- package/dist/chunking.js +23 -2
- package/dist/chunking.js.map +1 -1
- package/dist/deflate.d.ts +2 -1
- package/dist/deflate.js +10 -4
- package/dist/deflate.js.map +1 -1
- package/dist/direct/hub-host.d.ts +19 -1
- package/dist/direct/hub-host.js +51 -5
- package/dist/direct/hub-host.js.map +1 -1
- package/dist/direct/hub.d.ts +3 -0
- package/dist/direct/hub.js +32 -6
- package/dist/direct/hub.js.map +1 -1
- package/dist/direct/index.d.ts +7 -1
- package/dist/direct/index.js +21 -3
- package/dist/direct/index.js.map +1 -1
- package/dist/friendly.d.ts +31 -0
- package/dist/friendly.js +32 -0
- package/dist/friendly.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lockstep.d.ts +5 -0
- package/dist/lockstep.js +17 -2
- package/dist/lockstep.js.map +1 -1
- package/dist/onelobby.bundle.mjs +5 -5
- package/dist/onelobby.bundle.mjs.map +3 -3
- package/dist/onelobby.d.ts +11 -1
- package/dist/onelobby.global.js +113 -28
- package/dist/onelobby.global.js.map +2 -2
- package/dist/onelobby.global.min.js +5 -5
- package/dist/onelobby.global.min.js.map +3 -3
- package/dist/onelobby.js +46 -24
- package/dist/onelobby.js.map +1 -1
- package/package.json +1 -1
package/dist/onelobby.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { type LobbySummary } from "./signaling.js";
|
|
|
5
5
|
import { type LinkFactory, type SocketFactory } from "./transport.js";
|
|
6
6
|
import type { VoiceCodec, FrameSource, AudioSink } from "./voice-types.js";
|
|
7
7
|
export declare const LIB_NAME = "onelobby";
|
|
8
|
-
export declare const LIB_VERSION = "0.
|
|
8
|
+
export declare const LIB_VERSION = "0.4.1";
|
|
9
9
|
/** Where `rememberSeat` keeps the resume token: the subset of the Web Storage API it uses. */
|
|
10
10
|
export interface SeatStorage {
|
|
11
11
|
getItem(key: string): string | null;
|
|
@@ -134,6 +134,8 @@ export declare class OneLobby extends Emitter<OneLobbyEvents> {
|
|
|
134
134
|
resume(): Promise<Lobby | null>;
|
|
135
135
|
private helloMsg;
|
|
136
136
|
private connect;
|
|
137
|
+
/** The signaling socket only (no hello yet); with a timeout (direct IP), no answer is `direct_unreachable`. */
|
|
138
|
+
private open;
|
|
137
139
|
private makeLobby;
|
|
138
140
|
host(h: HostOptions): Promise<Lobby>;
|
|
139
141
|
join(code: string): Promise<Lobby>;
|
|
@@ -144,8 +146,16 @@ export declare class OneLobby extends Emitter<OneLobbyEvents> {
|
|
|
144
146
|
* The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded("host_left")`.
|
|
145
147
|
*/
|
|
146
148
|
joinDirect(codeOrAddress: string, opts?: JoinDirectOptions): Promise<Lobby>;
|
|
149
|
+
/**
|
|
150
|
+
* Take a direct-IP seat back after a drop or a restart: the code (or address) joined before and that lobby's
|
|
151
|
+
* `resumeToken` (save it after every join; it changes on each resume). The host keeps a dropped seat for 60 s and the
|
|
152
|
+
* same peer id comes back. Rejects with `invalid_token` when the seat is gone: offer `joinDirect` then.
|
|
153
|
+
*/
|
|
154
|
+
rejoinDirect(codeOrAddress: string, resumeToken: string, opts?: JoinDirectOptions): Promise<Lobby>;
|
|
155
|
+
private directTarget;
|
|
147
156
|
/** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */
|
|
148
157
|
hostDirectWith(h: HostOptions, hooks: DirectLobbyHooks): Promise<Lobby>;
|
|
149
158
|
rejoin(resumeToken: string): Promise<Lobby>;
|
|
159
|
+
private resumeAt;
|
|
150
160
|
list(opts?: ListOptions): Promise<LobbySummary[]>;
|
|
151
161
|
}
|
package/dist/onelobby.global.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! onelobby 0.
|
|
1
|
+
/*! onelobby 0.4.1 | MIT license */
|
|
2
2
|
"use strict";
|
|
3
3
|
var OneLobby = (() => {
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
@@ -25,6 +25,7 @@ var OneLobby = (() => {
|
|
|
25
25
|
CHAT_MAX_BYTES: () => CHAT_MAX_BYTES,
|
|
26
26
|
Chat: () => Chat,
|
|
27
27
|
DEFAULT_READY_TIMEOUT_MS: () => DEFAULT_READY_TIMEOUT_MS,
|
|
28
|
+
ErrorCodes: () => ErrorCodes,
|
|
28
29
|
Keepalive: () => Keepalive,
|
|
29
30
|
KeepaliveError: () => KeepaliveError,
|
|
30
31
|
LIB_NAME: () => LIB_NAME,
|
|
@@ -629,13 +630,17 @@ var OneLobby = (() => {
|
|
|
629
630
|
}
|
|
630
631
|
return out;
|
|
631
632
|
}
|
|
633
|
+
var MAX_PARTIAL_SETS = 8;
|
|
632
634
|
var Reassembler = class {
|
|
633
635
|
parts = /* @__PURE__ */ new Map();
|
|
636
|
+
// insertion order = oldest first
|
|
634
637
|
/** Returns the full payload when the last chunk arrives, otherwise null. */
|
|
635
638
|
push(chunk) {
|
|
636
639
|
if (chunk.count === 0 || chunk.index >= chunk.count) return null;
|
|
637
640
|
let e = this.parts.get(chunk.id);
|
|
638
641
|
if (!e || e.count !== chunk.count) {
|
|
642
|
+
this.parts.delete(chunk.id);
|
|
643
|
+
while (this.parts.size >= MAX_PARTIAL_SETS) this.parts.delete(this.parts.keys().next().value);
|
|
639
644
|
e = { count: chunk.count, got: 0, parts: new Array(chunk.count), bytes: 0 };
|
|
640
645
|
this.parts.set(chunk.id, e);
|
|
641
646
|
}
|
|
@@ -647,7 +652,15 @@ var OneLobby = (() => {
|
|
|
647
652
|
this.parts.delete(chunk.id);
|
|
648
653
|
throw new CodecError("too_long", `chunk set over ${MAX_REASSEMBLED_BYTES} bytes`);
|
|
649
654
|
}
|
|
650
|
-
if (e.got < e.count)
|
|
655
|
+
if (e.got < e.count) {
|
|
656
|
+
while (this.pendingBytes > MAX_REASSEMBLED_BYTES && this.parts.size > 1) {
|
|
657
|
+
for (const id of this.parts.keys()) if (id !== chunk.id) {
|
|
658
|
+
this.parts.delete(id);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
return null;
|
|
663
|
+
}
|
|
651
664
|
this.parts.delete(chunk.id);
|
|
652
665
|
const out = new Uint8Array(e.bytes);
|
|
653
666
|
let pos = 0;
|
|
@@ -660,6 +673,12 @@ var OneLobby = (() => {
|
|
|
660
673
|
get pending() {
|
|
661
674
|
return this.parts.size;
|
|
662
675
|
}
|
|
676
|
+
/** Bytes held across incomplete sets. */
|
|
677
|
+
get pendingBytes() {
|
|
678
|
+
let n = 0;
|
|
679
|
+
for (const p of this.parts.values()) n += p.bytes;
|
|
680
|
+
return n;
|
|
681
|
+
}
|
|
663
682
|
clear() {
|
|
664
683
|
this.parts.clear();
|
|
665
684
|
}
|
|
@@ -669,9 +688,10 @@ var OneLobby = (() => {
|
|
|
669
688
|
function deflateAvailable() {
|
|
670
689
|
return typeof CompressionStream !== "undefined" && typeof DecompressionStream !== "undefined";
|
|
671
690
|
}
|
|
672
|
-
async function pipe(bytes, stream) {
|
|
691
|
+
async function pipe(bytes, stream, maxBytes = Infinity) {
|
|
673
692
|
const w = stream.writable.getWriter();
|
|
674
|
-
void w.write(bytes).then(() => w.close())
|
|
693
|
+
void w.write(bytes).then(() => w.close()).catch(() => {
|
|
694
|
+
});
|
|
675
695
|
const chunks = [];
|
|
676
696
|
const r = stream.readable.getReader();
|
|
677
697
|
let total = 0;
|
|
@@ -680,6 +700,11 @@ var OneLobby = (() => {
|
|
|
680
700
|
if (done) break;
|
|
681
701
|
chunks.push(value);
|
|
682
702
|
total += value.length;
|
|
703
|
+
if (total > maxBytes) {
|
|
704
|
+
void r.cancel().catch(() => {
|
|
705
|
+
});
|
|
706
|
+
throw new CodecError("too_long", `snapshot inflates to more than ${maxBytes} bytes`);
|
|
707
|
+
}
|
|
683
708
|
}
|
|
684
709
|
const out = new Uint8Array(total);
|
|
685
710
|
let pos = 0;
|
|
@@ -692,8 +717,8 @@ var OneLobby = (() => {
|
|
|
692
717
|
function deflate(bytes) {
|
|
693
718
|
return pipe(bytes, new CompressionStream("deflate"));
|
|
694
719
|
}
|
|
695
|
-
function inflate(bytes) {
|
|
696
|
-
return pipe(bytes, new DecompressionStream("deflate"));
|
|
720
|
+
function inflate(bytes, maxBytes = MAX_REASSEMBLED_BYTES) {
|
|
721
|
+
return pipe(bytes, new DecompressionStream("deflate"), maxBytes);
|
|
697
722
|
}
|
|
698
723
|
|
|
699
724
|
// src/lockstep.ts
|
|
@@ -701,6 +726,7 @@ var OneLobby = (() => {
|
|
|
701
726
|
var MAX_RESTORE_RETRIES = 3;
|
|
702
727
|
var HOST_SILENCE_MS = 3e3;
|
|
703
728
|
var PEER_HASH_SILENCE_MS = 1e4;
|
|
729
|
+
var MAX_PENDING_PER_PEER = 256;
|
|
704
730
|
var SNAPSHOT_COMPRESS_MIN = 1024;
|
|
705
731
|
var Lockstep = class extends Emitter {
|
|
706
732
|
constructor(hub, opts) {
|
|
@@ -732,6 +758,7 @@ var OneLobby = (() => {
|
|
|
732
758
|
hosting = false;
|
|
733
759
|
_started = false;
|
|
734
760
|
pending = [];
|
|
761
|
+
_droppedCommands = 0;
|
|
735
762
|
ready = /* @__PURE__ */ new Set();
|
|
736
763
|
snapshotting = /* @__PURE__ */ new Set();
|
|
737
764
|
cancelTicker = null;
|
|
@@ -746,6 +773,10 @@ var OneLobby = (() => {
|
|
|
746
773
|
// joiner side
|
|
747
774
|
pendingSnapshot = null;
|
|
748
775
|
// ---- public game-facing surface -------------------------------------------------------------------------------
|
|
776
|
+
/** Remote commands the host dropped because that peer already had MAX_PENDING_PER_PEER waiting. */
|
|
777
|
+
get droppedCommands() {
|
|
778
|
+
return this._droppedCommands;
|
|
779
|
+
}
|
|
749
780
|
get started() {
|
|
750
781
|
return this._started;
|
|
751
782
|
}
|
|
@@ -891,7 +922,12 @@ var OneLobby = (() => {
|
|
|
891
922
|
handle(from, msg) {
|
|
892
923
|
switch (msg.type) {
|
|
893
924
|
case "cmd":
|
|
894
|
-
if (this.hosting)
|
|
925
|
+
if (this.hosting) {
|
|
926
|
+
let waiting = 0;
|
|
927
|
+
for (const c of this.pending) if (c.peerId === from) waiting++;
|
|
928
|
+
if (waiting >= MAX_PENDING_PER_PEER) this._droppedCommands++;
|
|
929
|
+
else this.pending.push({ peerId: from, bytes: msg.bytes });
|
|
930
|
+
}
|
|
895
931
|
break;
|
|
896
932
|
case "frame":
|
|
897
933
|
if (!this.hosting) this.onFrame(msg.tick, msg.commands);
|
|
@@ -2980,7 +3016,7 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
2980
3016
|
|
|
2981
3017
|
// src/onelobby.ts
|
|
2982
3018
|
var LIB_NAME = "onelobby";
|
|
2983
|
-
var LIB_VERSION = "0.
|
|
3019
|
+
var LIB_VERSION = "0.4.1";
|
|
2984
3020
|
var DEFAULT_READY_TIMEOUT_MS = 15e3;
|
|
2985
3021
|
function hasMic() {
|
|
2986
3022
|
return typeof navigator !== "undefined" && !!navigator.mediaDevices?.getUserMedia && typeof AudioWorkletNode !== "undefined";
|
|
@@ -3105,6 +3141,17 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3105
3141
|
};
|
|
3106
3142
|
}
|
|
3107
3143
|
async connect(url = this.options.lobbyServer, factory = this.socketFactory, timeoutMs = 0) {
|
|
3144
|
+
const sig = await this.open(url, factory, timeoutMs);
|
|
3145
|
+
try {
|
|
3146
|
+
const welcome = await sig.request(this.helloMsg(), "welcome");
|
|
3147
|
+
return { sig, welcome };
|
|
3148
|
+
} catch (e) {
|
|
3149
|
+
sig.close();
|
|
3150
|
+
throw e;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
/** The signaling socket only (no hello yet); with a timeout (direct IP), no answer is `direct_unreachable`. */
|
|
3154
|
+
async open(url, factory, timeoutMs) {
|
|
3108
3155
|
const sig = new SignalingClient(url, factory, this.clock);
|
|
3109
3156
|
if (timeoutMs > 0) {
|
|
3110
3157
|
let cancel = () => {
|
|
@@ -3122,13 +3169,7 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3122
3169
|
cancel();
|
|
3123
3170
|
}
|
|
3124
3171
|
} else await sig.connect();
|
|
3125
|
-
|
|
3126
|
-
const welcome = await sig.request(this.helloMsg(), "welcome");
|
|
3127
|
-
return { sig, welcome };
|
|
3128
|
-
} catch (e) {
|
|
3129
|
-
sig.close();
|
|
3130
|
-
throw e;
|
|
3131
|
-
}
|
|
3172
|
+
return sig;
|
|
3132
3173
|
}
|
|
3133
3174
|
makeLobby(sig, welcome, state, direct, rateLimit) {
|
|
3134
3175
|
const lobby = new Lobby({
|
|
@@ -3210,6 +3251,30 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3210
3251
|
* The address is also given to ICE as the host's UDP candidate. When the host leaves, `sessionEnded("host_left")`.
|
|
3211
3252
|
*/
|
|
3212
3253
|
async joinDirect(codeOrAddress, opts = {}) {
|
|
3254
|
+
const { addr, links } = this.directTarget(codeOrAddress, opts);
|
|
3255
|
+
const { sig, welcome } = await this.connect(addr.url, this.socketFactory, opts.connectTimeoutMs ?? 5e3);
|
|
3256
|
+
try {
|
|
3257
|
+
const state = await sig.request({ t: "join", ...opts.password ? { password: opts.password } : {} }, "lobby");
|
|
3258
|
+
const lobby = this.makeLobby(sig, welcome, state, { links });
|
|
3259
|
+
lobby.joinCode = joinedWith(addr);
|
|
3260
|
+
return lobby;
|
|
3261
|
+
} catch (e) {
|
|
3262
|
+
sig.close();
|
|
3263
|
+
throw e;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
/**
|
|
3267
|
+
* Take a direct-IP seat back after a drop or a restart: the code (or address) joined before and that lobby's
|
|
3268
|
+
* `resumeToken` (save it after every join; it changes on each resume). The host keeps a dropped seat for 60 s and the
|
|
3269
|
+
* same peer id comes back. Rejects with `invalid_token` when the seat is gone: offer `joinDirect` then.
|
|
3270
|
+
*/
|
|
3271
|
+
async rejoinDirect(codeOrAddress, resumeToken, opts = {}) {
|
|
3272
|
+
const { addr, links } = this.directTarget(codeOrAddress, opts);
|
|
3273
|
+
const lobby = await this.resumeAt(resumeToken, addr.url, opts.connectTimeoutMs ?? 5e3, { links });
|
|
3274
|
+
lobby.joinCode = joinedWith(addr);
|
|
3275
|
+
return lobby;
|
|
3276
|
+
}
|
|
3277
|
+
directTarget(codeOrAddress, opts) {
|
|
3213
3278
|
let input = codeOrAddress.trim();
|
|
3214
3279
|
if (looksLikeJoinCode(input)) {
|
|
3215
3280
|
const d = decodeJoinCode(input);
|
|
@@ -3221,16 +3286,7 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3221
3286
|
if (!addr) throw new OneLobbyError("bad_address", `"${codeOrAddress}" is not a join code or an address (try 6B01-RGFE-P2B or 192.168.1.20:47810)`);
|
|
3222
3287
|
const inner = this.linkFactory;
|
|
3223
3288
|
const links = { create: (o) => inner.create({ ...o, iceServers: [], extraRemoteCandidates: [addr.candidate] }) };
|
|
3224
|
-
|
|
3225
|
-
try {
|
|
3226
|
-
const state = await sig.request({ t: "join", ...opts.password ? { password: opts.password } : {} }, "lobby");
|
|
3227
|
-
const lobby = this.makeLobby(sig, welcome, state, { links });
|
|
3228
|
-
lobby.joinCode = joinedWith(addr);
|
|
3229
|
-
return lobby;
|
|
3230
|
-
} catch (e) {
|
|
3231
|
-
sig.close();
|
|
3232
|
-
throw e;
|
|
3233
|
-
}
|
|
3289
|
+
return { addr, links };
|
|
3234
3290
|
}
|
|
3235
3291
|
/** @internal onelobby/direct's host: create the one lobby on the embedded hub, in direct mode. */
|
|
3236
3292
|
async hostDirectWith(h, hooks) {
|
|
@@ -3256,8 +3312,10 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3256
3312
|
}
|
|
3257
3313
|
}
|
|
3258
3314
|
async rejoin(resumeToken) {
|
|
3259
|
-
|
|
3260
|
-
|
|
3315
|
+
return this.resumeAt(resumeToken, this.options.lobbyServer, 0);
|
|
3316
|
+
}
|
|
3317
|
+
async resumeAt(resumeToken, url, timeoutMs, direct) {
|
|
3318
|
+
const sig = await this.open(url, this.socketFactory, timeoutMs);
|
|
3261
3319
|
try {
|
|
3262
3320
|
const lobbyP = sig.expect("lobby");
|
|
3263
3321
|
const welcomeP = sig.request(this.helloMsg(resumeToken), "welcome");
|
|
@@ -3266,7 +3324,7 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3266
3324
|
const welcome = await welcomeP;
|
|
3267
3325
|
if (welcome.peerId === void 0) throw new OneLobbyError("invalid_token", "resume token unknown or expired");
|
|
3268
3326
|
const state = await lobbyP;
|
|
3269
|
-
return this.makeLobby(sig, welcome, state);
|
|
3327
|
+
return this.makeLobby(sig, welcome, state, direct);
|
|
3270
3328
|
} catch (e) {
|
|
3271
3329
|
sig.close();
|
|
3272
3330
|
throw e;
|
|
@@ -3295,6 +3353,33 @@ registerProcessor('onelobby-capture', OneLobbyCapture);
|
|
|
3295
3353
|
}
|
|
3296
3354
|
|
|
3297
3355
|
// src/friendly.ts
|
|
3356
|
+
var ErrorCodes = {
|
|
3357
|
+
// from the lobby server or a direct host's hub
|
|
3358
|
+
UnknownCode: "unknown_code",
|
|
3359
|
+
BadCode: "bad_code",
|
|
3360
|
+
LobbyFull: "lobby_full",
|
|
3361
|
+
VersionMismatch: "version_mismatch",
|
|
3362
|
+
InGame: "in_game",
|
|
3363
|
+
Kicked: "kicked",
|
|
3364
|
+
BadPassword: "bad_password",
|
|
3365
|
+
NotHost: "not_host",
|
|
3366
|
+
RateLimited: "rate_limited",
|
|
3367
|
+
ServerFull: "server_full",
|
|
3368
|
+
GameNotAllowed: "game_not_allowed",
|
|
3369
|
+
BadRequest: "bad_request",
|
|
3370
|
+
// from this client
|
|
3371
|
+
ServerClosed: "server_closed",
|
|
3372
|
+
IceFailed: "ice_failed",
|
|
3373
|
+
Timeout: "timeout",
|
|
3374
|
+
DirectUnreachable: "direct_unreachable",
|
|
3375
|
+
BadAddress: "bad_address",
|
|
3376
|
+
PortInUse: "port_in_use",
|
|
3377
|
+
ConnectFailed: "connect_failed",
|
|
3378
|
+
Closed: "closed",
|
|
3379
|
+
ServerLost: "server_lost",
|
|
3380
|
+
InvalidToken: "invalid_token",
|
|
3381
|
+
NoServer: "no_server"
|
|
3382
|
+
};
|
|
3298
3383
|
var TEXT = {
|
|
3299
3384
|
// server errors (spec/signaling.md)
|
|
3300
3385
|
unknown_code: { message: "We couldn't find a lobby with that code.", hint: "Check the code with the host. Codes stop working once everyone leaves." },
|