Share a link that plays the game without the editor (#266)
A project could only be played inside the editor, behind the login.
To show a game to someone else there was no way short of an account.
A release freezes the saved project under a random token and serves
it on a page that holds nothing but the game view.
- Menu -> Release... opens a dialog with the state of the release,
the link with Copy link and Open, Publish or Update release, and
Take down behind a confirmation.
- Publish saves first and refuses while anything is unsaved, like
snapshots. Updating keeps the link. Take down kills it, and a new
Publish hands out a new token.
- The link is play.html?game=<32 hex>. The page needs no session,
paints Theme.colors.background, zooms with Auto and waits for
"Click to play" so that sound and keyboard focus are allowed.
- The game always starts in the start scene. Persistent variables
are stored per link in the browser. Plugins are not loaded, and the
dialog warns about them and about a project without a scene.
- GET ?r=releases/{token} is the only route besides auth/* that works
without a login. The public answer carries the token as project.id,
so the project id stays hidden. Release files are written with 0640
and nginx does not serve them directly (measured 403).
- Starting, the frame and Load scene moved into runtime/GameRun.ts.
Play in the editor and the play page run through the same code.
- WorldViewport moved to src/viewport/ and no longer needs the
EditorState. Pointer forwarding (gamePointer.ts) and setting up
textures, fonts and sounds (projectFiles.ts) exist only once.
- Vite builds two pages. The play page loads 4 kB of its own code
plus the shared chunk (Pixi, runtime, drawing, 653 kB) and Rapier,
without the editor.
Found on the way, not fixed here: data/projects/<id>.json answers 200
without a login because the files are 0644.
Tests:
- ./check.sh: 194 files, 2808 tests, exit 0.
- tests/php/releases.test.php: 20 checks, also added to CI.
- New e2e module 60 with 10 checks, played in a browser without a
session. Module 46 counts the menu entries and was updated.
- Full e2e run: 1572 checks passed.
- Counter-checks: 3 unit, 2 PHP and 3 browser mutations all fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
94c1055895
commit
4194f3e150
42 files changed
+1600
-246
No files matched your search
@@ -38,6 +38,8 @@ import { attachAutosave } from './autosave';
|
||||
import { PasswordDialog } from './auth/PasswordDialog';
|
||||
import type { OpenProjectDialog } from './OpenProjectDialog';
|
||||
import type { SnapshotsDialog } from './SnapshotsDialog';
|
||||
import type { ReleaseStore } from '../core/store/ReleaseStore';
|
||||
import type { ReleaseDialog } from './ReleaseDialog';
|
||||
import type { ProjectSettingsDialog } from './ProjectSettingsDialog';
|
||||
import { dialogSlot } from './FormDialog';
|
||||
import { ContainerEditor } from './container/ContainerEditor';
|
||||
@@ -271,6 +273,7 @@ export class Editor implements PlayHost {
|
||||
fonts: FontStore,
|
||||
sounds: SoundStore,
|
||||
snapshots: SnapshotStore,
|
||||
releases: ReleaseStore,
|
||||
account: EditorAccount,
|
||||
) {
|
||||
this.viewport = new SceneViewport(state);
|
||||
@@ -307,7 +310,7 @@ export class Editor implements PlayHost {
|
||||
() => {
|
||||
if (this.play.isPlaying) this.play.stop();
|
||||
},
|
||||
{ store: snapshots, flush: () => this.autosave.flush() },
|
||||
{ snapshots, releases, flush: () => this.autosave.flush() },
|
||||
// Every project brings its own plugins (#119); until `startPlugins` has run there is no
|
||||
// manager, and a project with plugins simply has none of them.
|
||||
async (raw) => void (await this.plugins?.syncTo(pluginsOf(raw))),
|
||||
@@ -580,6 +583,11 @@ export class Editor implements PlayHost {
|
||||
return this.projects.snapshotList();
|
||||
}
|
||||
|
||||
/** The open "Release" dialog (#266), for the check scripts. */
|
||||
releaseDialog(): ReleaseDialog | null {
|
||||
return this.projects.releaseView();
|
||||
}
|
||||
|
||||
/** Whether `id` names a tab already – a built-in one by its own id, a plugin's by the id it gave. */
|
||||
hasTab(id: string): boolean {
|
||||
return Object.hasOwn(TAB_LABELS, id) || this.pluginPanes.has(`plugin:${id}`);
|
||||
|
||||
Reference in new issue
Block a user