Skip to content

[P2] Double-init race: auth state change can trigger initializeLoremaster mid-handshake #8

Description

@digitsu

scripts/loremaster.mjs:107-114:

authManager.onStateChange(async (state, user) => {
  if (state === AuthState.LOGGED_IN && !game.loremaster?.socketClient?.isConnected) {
    console.log(`${MODULE_NAME} | Auth successful, initializing connection...`);
    ui.notifications.info(`${MODULE_NAME}: Connected as ${user?.displayName}. Initializing...`);
    await initializeLoremaster();
  }
});

The guard is !isConnected, but a SocketClient that is mid-handshake has isConnected === false. If initializeLoremaster() is already running (started by the ready hook for an already-authenticated user) and the auth state then transitions to LOGGED_IN for any reason — token re-validation, refresh, manual login flow — this listener fires a second initializeLoremaster() while the first is still inside socketClient.connect().

Result: two parallel connect attempts, two pending sockets on game.loremaster, race on which one wins.

initializeLoremaster does try to clean up at the top:

// scripts/loremaster.mjs:189-192
if (game.loremaster?.socketClient) {
  game.loremaster.socketClient.disconnect();
}

…but the cleanup happens against whichever client is currently on game.loremaster, which may be the one the first invocation is mid-handshake on. Disconnecting it mid-handshake can leave the first invocation's await socketClient.connect() in an undefined state.

Suggested fix: track init state explicitly (isInitializing flag) and skip the listener path while it's true; or serialize all initializeLoremaster calls through a single Promise chain.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions