@myvillage/cli 2.8.0 → 2.8.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/package.json
CHANGED
|
@@ -136,6 +136,9 @@ function requestHostContext(timeoutMs) {
|
|
|
136
136
|
// `npm run dev` or the Studio preview — there is no platform session, so it
|
|
137
137
|
// runs in DEV MODE against the public staging match server: open two browser
|
|
138
138
|
// tabs and use the same code (or both press quick match) to play yourself.
|
|
139
|
+
// Inside a host that doesn't provide a session (mode 'unavailable'), every
|
|
140
|
+
// call fails with code 'host_unavailable' — real players never land on the
|
|
141
|
+
// dev server.
|
|
139
142
|
|
|
140
143
|
const DEFAULT_DEV_SERVER = 'https://myvillage-realtime-staging.myvillage-realtime.workers.dev';
|
|
141
144
|
const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
|
|
@@ -175,7 +178,7 @@ function devPlayerId() {
|
|
|
175
178
|
|
|
176
179
|
class Multiplayer {
|
|
177
180
|
constructor(ctx, options) {
|
|
178
|
-
this.mode = ctx ? 'platform' : 'dev';
|
|
181
|
+
this.mode = ctx ? 'platform' : standalone ? 'dev' : 'unavailable';
|
|
179
182
|
this._ctx = ctx;
|
|
180
183
|
this._opts = options;
|
|
181
184
|
this._net = null;
|
|
@@ -189,18 +192,21 @@ class Multiplayer {
|
|
|
189
192
|
|
|
190
193
|
/** Host a match. visibility: 'private' (code only) or 'village' (listed + quick match). */
|
|
191
194
|
async host({ visibility = 'private', maxPlayers, lobby } = {}) {
|
|
195
|
+
this._requireAvailable();
|
|
192
196
|
if (this.mode === 'dev') return this._devMatch(randomCode(), true, lobby);
|
|
193
197
|
return this._enter(await this._api('POST', `/api/v1/games/${this._game()}/matches`, { visibility, maxPlayers, lobby, platform: this._ctx.platform }));
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
/** Join a match by its code (forgiving: "abc-234" works). */
|
|
197
201
|
async join(code) {
|
|
202
|
+
this._requireAvailable();
|
|
198
203
|
if (this.mode === 'dev') return this._devMatch(normalizeCode(code), false);
|
|
199
204
|
return this._enter(await this._api('POST', `/api/v1/games/${this._game()}/matches/join`, { code, platform: this._ctx.platform }));
|
|
200
205
|
}
|
|
201
206
|
|
|
202
207
|
/** Join the fullest open match in the player's villages, or host one. */
|
|
203
208
|
async quickMatch({ lobby } = {}) {
|
|
209
|
+
this._requireAvailable();
|
|
204
210
|
// Finished match rooms can't be reused, so dev quick match rotates rooms
|
|
205
211
|
// every 10 minutes; tabs that press it within the same window meet.
|
|
206
212
|
if (this.mode === 'dev') return this._devMatch('QUICK' + Math.floor(Date.now() / 600000).toString(36).toUpperCase(), false, lobby);
|
|
@@ -209,6 +215,7 @@ class Multiplayer {
|
|
|
209
215
|
|
|
210
216
|
/** Open village matches the player can join, plus matches they're in. */
|
|
211
217
|
async list() {
|
|
218
|
+
this._requireAvailable();
|
|
212
219
|
if (this.mode === 'dev') return this.match ? [this.match] : [];
|
|
213
220
|
return (await this._api('GET', `/api/v1/games/${this._game()}/matches`)).matches;
|
|
214
221
|
}
|
|
@@ -285,6 +292,7 @@ class Multiplayer {
|
|
|
285
292
|
* (src/myvillage-realtime.js). Call again after a disconnect to rejoin.
|
|
286
293
|
*/
|
|
287
294
|
async connect() {
|
|
295
|
+
this._requireAvailable();
|
|
288
296
|
if (!this.match) throw new MultiplayerError('no_match', 'Host, join or quick match first.');
|
|
289
297
|
const ticket = await this._freshTicket();
|
|
290
298
|
const { ArenaClient } = await import('./myvillage-realtime.js');
|
|
@@ -297,6 +305,12 @@ class Multiplayer {
|
|
|
297
305
|
|
|
298
306
|
// ── internals ──
|
|
299
307
|
|
|
308
|
+
_requireAvailable() {
|
|
309
|
+
if (this.mode === 'unavailable') {
|
|
310
|
+
throw new MultiplayerError('host_unavailable', "Multiplayer isn't available here yet.");
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
300
314
|
_game() {
|
|
301
315
|
return encodeURIComponent(this._ctx.game);
|
|
302
316
|
}
|
|
@@ -320,9 +334,29 @@ class Multiplayer {
|
|
|
320
334
|
_enter(result) {
|
|
321
335
|
this.match = result.match;
|
|
322
336
|
this._ticket = result.realtime;
|
|
337
|
+
this._leaveOnUnload();
|
|
323
338
|
return this.match;
|
|
324
339
|
}
|
|
325
340
|
|
|
341
|
+
// Closing the page (or the host's Exit) frees the player's seat in the lobby
|
|
342
|
+
// straight away rather than when the lobby expires.
|
|
343
|
+
_leaveOnUnload() {
|
|
344
|
+
if (this._unloadHooked || !hasWindow) return;
|
|
345
|
+
this._unloadHooked = true;
|
|
346
|
+
window.addEventListener('pagehide', () => {
|
|
347
|
+
if (!this.match || this.mode !== 'platform') return;
|
|
348
|
+
try {
|
|
349
|
+
fetch(this._ctx.apiBase + '/api/v1/matches/' + this.match.id + '/leave', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
keepalive: true,
|
|
352
|
+
headers: { Authorization: 'Bearer ' + this._ctx.playToken },
|
|
353
|
+
});
|
|
354
|
+
} catch (_) {
|
|
355
|
+
// best effort
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
326
360
|
async _update(body) {
|
|
327
361
|
if (!this.match) throw new MultiplayerError('no_match', 'Not in a match.');
|
|
328
362
|
if (this.mode === 'dev') {
|
|
@@ -421,7 +455,7 @@ export const MyVillageGame = {
|
|
|
421
455
|
* devServer — dev-mode match server (defaults to the MyVillage staging server)
|
|
422
456
|
*/
|
|
423
457
|
async multiplayer(options = {}) {
|
|
424
|
-
const ctx = await requestHostContext(standalone ? 300 :
|
|
458
|
+
const ctx = await requestHostContext(standalone ? 300 : 10000);
|
|
425
459
|
return new Multiplayer(ctx, options);
|
|
426
460
|
},
|
|
427
461
|
};
|
|
@@ -86,6 +86,8 @@ function friendlyError(err) {
|
|
|
86
86
|
return 'That match has already started.';
|
|
87
87
|
case 'not_in_village':
|
|
88
88
|
return 'You can only play with people from your village.';
|
|
89
|
+
case 'host_unavailable':
|
|
90
|
+
return "Multiplayer isn't available in this player yet.";
|
|
89
91
|
case 'no_village':
|
|
90
92
|
return 'Join a village to play multiplayer — you play with your village.';
|
|
91
93
|
default:
|