Track jsPsych#3694's updated multiplayer contract - #90
Merged
Merged
Conversation
A cancelled wait() (experiment end, abort, disconnect) now rejects with a MultiplayerCancelledError instead of hanging, so the plugins that wait on the group stop quietly rather than treating it as a timeout or a backend failure. Scoreboard previously logged an error and drew a final board over the cleared display, leaving a Continue button that called finishTrial after the run ended; it also called getAll() on failure paths, which now throws once disconnected. Also: resolve the API only from jsPsych.multiplayer (pre-#3694 pluginAPI builds predate this contract), replace hand-rolled read-merge-write with update(), and stop normalizing timeouts core now handles. Two bugs found alongside that work: countdown and draw drove repeating timers with a raw setInterval that survived abortExperiment(), so countdown could finishTrial after the run ended and draw kept writing forever — both now tick through jsPsych's timer registry. Chat and reference-game rebuilt this client's whole message list from the adapter cache on every send, so two quick sends could drop a message; each now keeps its own messages locally. Mocks modelled the old contract (live snapshots, no cancellation, null timeouts firing at once) and could not catch any of this; they now follow the real one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
connect() set `connected` before arming onDisconnect cleanup and wiring the reconnect handler, so a failure there rejected while leaving an adapter that looked connected, with a live session listener, an owned app that disconnect() would never delete, and a retry that returned immediately without reconnecting. It now flips `connected` last and tears down on any failure. Also keep the last pushed payload JSON-encoded rather than by reference, so a caller mutating the object it pushed can't change what a reconnect re-sends, and refresh adapter comments for the new core contract (snapshots are copied by the API; core cancels subscriptions before calling adapter.disconnect()). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The reference and introduction pages still said nothing cancels subscriptions automatically, told readers to await each update(), and documented participantId as a plain string; they now cover cancellation, copied snapshots, the timeout rules, and rejections when not connected. Setup snippets use an async wrapper, since top-level await fails in a classic <script> tag. Every example and the tutorial move from the eddf2f62 preview build to one carrying the current contract, and examples/README.md now says all of them are pinned together. Also: the JATOS adapter example called jsPsych.pluginAPI.connect (a TypeError), the root README linked the closed #3692, and the live-scoreboard and draw-room examples hand-rolled read-merge-write or dropped rejections. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follows the contract changes made on jsPsych#3694 after a review of the core
MultiplayerAPI. The core side is commits02e113c3,96515f67,5e3f18caandcfe4c340on that PR.What changed in core
wait()rejects with aMultiplayerCancelledErrorwhen the experiment ends or is aborted, instead of hanging. jsPsych also cancels multiplayer subscriptions inabortExperiment()and afteron_finish.push(),update()andwait()reject rather than throwing when the API is not connected.get(),getAll(), subscriber arguments and thewait()result are JSON copies, so session data must be JSON-serializable.update()merges onto this client's last write and coalesces writes issued faster than the backend confirms them.wait()treatsnull, negative and non-finite timeouts as no timeout;participantIdisnulluntilconnect()resolves.Fixes
abortExperiment()the plugin logged an error and drew a final board over the cleared display, leaving a Continue button that calledfinishTrialafter the run had ended. Afterdisconnect(),getAll()on that path threw inside avoid-started async function, stranding the participant on the waiting message.setInterval, which survivesabortExperiment(). Countdown could callfinishTrialafter the run ended; draw kept writing to the session indefinitely. Both now tick through jsPsych's timer registry.connect()marked itself connected before arming disconnect cleanup, so a failure there left a connected-looking adapter with a leaked listener, an owned app thatdisconnect()never released, and a retry that silently did nothing.jsPsych.pluginAPI.connect, a TypeError.Cleanup
resolveMultiplayerApi()reads onlyjsPsych.multiplayer; thepluginAPIfallback is gone, since any build exposing the API there predates this contract.update()(choice, countdown, match, role, scoreboard, reference-game, and the live-scoreboard example).isMultiplayerCancelledErrorhelper where the plugin waits.Testing
npm test— 576 tests in 26 suites pass. Mocks modelled the old contract (live snapshots, no cancellation,nulltimeouts firing at once) and could not catch any of the bugs above, so they now follow the real one. Each plugin that waits has a cancelled-wait test, and the timer, message-race and Firebase fixes have regression tests that were each checked to fail against the old code.Notes for review
end()fromgetAll(), so with a lagging cache a message the participant saw can be missing from the trial data.push()rather than merging, since that is documented behaviour.🤖 Generated with Claude Code