0.2.3: Clementia S2 releases a dead, hidden, uncarriable or pathless rider at once (slot and weight back before the same tick's boarding checks); same-browser match recovery never takes the seat of a window that is reloading or still starting (a tab keeps its own session token; seats are stamped at the server's welcome, the older welcome wins); golden: diy-135 moves (outcome unchanged)

This commit is contained in:
sganggs committed 2026-10-10 01:58:06 +08:00
1 parent 821ab827bd
commit ba5b998a72
19 files changed
+580 -60

No files matched your search

+77
View File
@@ -1310,6 +1310,83 @@ describe('multi-device & browser compatibility (static)', () => {
});
describe('explicit local match recovery (#431)', () => {
async function recoveryPair() {
const { createIdentity } = await mod('net.js');
const local = memStorage(), hub = channelHub(), ownerSession = memStorage(), guestSession = memStorage();
const ownerChannel = hub.create();
const owner = createIdentity({ local, session: ownerSession, tabId: 'z-owner', channel: ownerChannel, queryMs: 5, now: () => 100 });
const guest = createIdentity({ local, session: guestSession, tabId: 'a-guest', channel: hub.create(), queryMs: 5, now: () => 200 });
await owner.init(); owner.saveToken('seat-owner'); owner.rememberMatch({ name: 'Owner', code: 'ROOM1' });
await guest.init(); guest.saveToken('seat-guest');
return { createIdentity, local, hub, ownerSession, guestSession, ownerChannel, owner, guest, id: guest.recoverable()[0].id };
}
test('live: a smaller tab id cannot reclaim an active seat', async () => {
const { guest, owner, id } = await recoveryPair();
assert.equal(await guest.resume(id), false);
assert.equal(guest.getToken(), 'seat-guest');
assert.equal(owner.getToken(), 'seat-owner');
});
test('reload: the original session wins over a recovery attempt during navigation', async () => {
const p = await recoveryPair();
p.ownerChannel.close();
assert.equal(await p.guest.resume(p.id), true, 'old document is gone');
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), queryMs: 5 });
assert.equal(await reloaded.init(), 'seat-owner');
assert.equal(p.ownerSession.getItem('sp.token'), 'seat-owner');
assert.equal(p.guest.getToken(), 'seat-guest', 'unconfirmed recovery yields before hello');
});
test('midInit: the session token answers claims before init finishes', async () => {
const p = await recoveryPair();
p.ownerChannel.close();
let finish;
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), setTimeout: (fn) => { finish = fn; } });
const pending = reloaded.init();
assert.equal(await p.guest.resume(p.id), false);
finish();
assert.equal(await pending, 'seat-owner');
assert.equal(p.ownerSession.getItem('sp.token'), 'seat-owner');
assert.equal(p.guest.getToken(), 'seat-guest');
});
test('an occupied session token never falls back to a different recent identity', async () => {
const p = await recoveryPair();
p.local.setItem('sp.tokens', JSON.stringify(['seat-free', 'seat-owner']));
const duplicate = p.createIdentity({ local: p.local, session: p.ownerSession.clone(), tabId: '0-copy', channel: p.hub.create(), queryMs: 5 });
assert.equal(await duplicate.init(), null);
assert.equal(duplicate.getToken(), null);
});
test('resume persists neither its token nor a welcome stamp until the server welcomes it', async () => {
const p = await recoveryPair();
p.ownerChannel.close();
const stamp = p.guestSession.getItem('sp.tokenWelcome');
assert.equal(await p.guest.resume(p.id), true);
assert.equal(p.guestSession.getItem('sp.token'), 'seat-guest');
assert.equal(p.guestSession.getItem('sp.tokenWelcome'), stamp);
p.guest.saveToken('seat-owner');
assert.equal(p.guestSession.getItem('sp.token'), 'seat-owner');
assert.ok(p.guestSession.getItem('sp.tokenWelcome'));
});
test('an earlier welcome stamp survives reload and beats a later live welcome', async () => {
const p = await recoveryPair();
p.ownerChannel.close();
assert.equal(await p.guest.resume(p.id), true);
p.guest.saveToken('seat-owner'); // recovery's welcome is later than the original owner's
const originalStamp = p.ownerSession.getItem('sp.tokenWelcome');
const reloaded = p.createIdentity({ local: p.local, session: p.ownerSession, tabId: 'zz-reloaded', channel: p.hub.create(), queryMs: 5, now: () => 300 });
assert.equal(await reloaded.init(), 'seat-owner');
reloaded.saveToken('seat-owner');
assert.equal(p.ownerSession.getItem('sp.tokenWelcome'), originalStamp, 'reconnect does not reset seniority');
});
test('a welcome received during init cannot be overwritten or erased by its pending result', async () => {
const { createIdentity } = await mod('net.js');
const local = memStorage(), session = memStorage(), hub = channelHub();
session.setItem('sp.token', 'old');
let finish;
const id = createIdentity({ local, session, channel: hub.create(), setTimeout: (fn) => { finish = fn; } });
const pending = id.init();
id.saveToken('new'); finish();
assert.equal(await pending, 'new');
assert.equal(session.getItem('sp.token'), 'new');
});
test('an idle tab can reclaim a closed match tab without taking a live one', async () => {
const { createIdentity } = await mod('net.js');
const local = memStorage(), hub = channelHub(), ca = hub.create(), cb = hub.create();