@auraindustry/aurajs 0.0.7 → 0.1.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/README.md +98 -2
- package/benchmarks/perf-thresholds.json +54 -0
- package/package.json +4 -7
- package/src/asset-pack.mjs +8 -1
- package/src/authored-project.mjs +1449 -0
- package/src/authored-runtime.mjs +2016 -0
- package/src/authoring/avatar-animation-graph.mjs +648 -0
- package/src/bin-integrity.mjs +272 -0
- package/src/build-contract/assets.mjs +130 -0
- package/src/build-contract/capabilities.mjs +116 -0
- package/src/build-contract/constants.mjs +6 -0
- package/src/build-contract/helpers.mjs +44 -0
- package/src/build-contract/web-templates.mjs +5993 -0
- package/src/build-contract.mjs +27 -2910
- package/src/bundler.mjs +188 -55
- package/src/cli.mjs +4840 -1512
- package/src/commands/project-authoring.mjs +454 -0
- package/src/config.mjs +44 -0
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +3309 -0
- package/src/conformance/cases/core-runtime-cases.mjs +1431 -0
- package/src/conformance/cases/index.mjs +11 -0
- package/src/conformance/cases/scene3d-and-media-cases.mjs +2094 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +1776 -0
- package/src/conformance/shared.mjs +27 -0
- package/src/conformance-runner.mjs +25 -13
- package/src/conformance.mjs +619 -4020
- package/src/cutscene.mjs +362 -5
- package/src/dev-cli-action.mjs +249 -0
- package/src/dev-cli-inspect.mjs +92 -0
- package/src/dev-cli-state.mjs +80 -0
- package/src/external-asset-cache.mjs +587 -0
- package/src/external-asset-policy.mjs +217 -0
- package/src/external-package-surface.mjs +206 -0
- package/src/game-action-runtime.mjs +869 -0
- package/src/game-state-runtime.mjs +206 -6
- package/src/headless-action.mjs +186 -0
- package/src/headless-test/runtime-animation.mjs +1173 -0
- package/src/headless-test/runtime-coordinator.mjs +1514 -0
- package/src/headless-test/runtime-primitives.mjs +320 -0
- package/src/headless-test/runtime-world.mjs +2253 -0
- package/src/headless-test.mjs +392 -4298
- package/src/host-binary.mjs +342 -14
- package/src/icon-discovery.mjs +64 -0
- package/src/make-catalog.mjs +109 -0
- package/src/make.mjs +197 -0
- package/src/package-integrity.mjs +586 -0
- package/src/perf-benchmark.mjs +353 -0
- package/src/postinstall.mjs +5 -5
- package/src/prefabs/index.mjs +34 -0
- package/src/prefabs/scene-serialization.mjs +184 -0
- package/src/project-importer.mjs +620 -0
- package/src/project-registry.mjs +24 -0
- package/src/publish-command.mjs +195 -0
- package/src/publish-env-example.mjs +83 -0
- package/src/publish-validation.mjs +708 -0
- package/src/retro/assets/compile.mjs +232 -0
- package/src/retro/backend-gba/authoring.mjs +1029 -0
- package/src/retro/backend-gba/rom.mjs +363 -0
- package/src/retro/backend-gbc/rom.mjs +85 -0
- package/src/retro/build.mjs +278 -0
- package/src/retro/cli/commands.mjs +292 -0
- package/src/retro/cli/templates.mjs +84 -0
- package/src/retro/diagnostics/catalog.mjs +110 -0
- package/src/retro/diagnostics/emit.mjs +72 -0
- package/src/retro/emulator/case-overlay.mjs +64 -0
- package/src/retro/emulator/discovery.mjs +158 -0
- package/src/retro/emulator/macos-case-overlay.swift +220 -0
- package/src/retro/emulator/profiles.mjs +146 -0
- package/src/retro/emulator/runner.mjs +289 -0
- package/src/retro/frontend/load-project.mjs +98 -0
- package/src/retro/index.mjs +30 -0
- package/src/retro/ir/build-ir.mjs +108 -0
- package/src/retro/runtime-gba/contract.mjs +151 -0
- package/src/retro/runtime-gbc/contract.mjs +117 -0
- package/src/retro/shared/span.mjs +26 -0
- package/src/retro/shared/targets.mjs +64 -0
- package/src/retro/validator/check-project.mjs +114 -0
- package/src/runtime-hotspot-audit.mjs +707 -0
- package/src/scaffold/config.mjs +1000 -0
- package/src/scaffold/fs.mjs +56 -0
- package/src/scaffold/layout.mjs +318 -0
- package/src/scaffold/project-docs.mjs +439 -0
- package/src/scaffold.mjs +93 -596
- package/src/scene-composition/index.mjs +326 -0
- package/src/scene-composition/runtime.mjs +751 -0
- package/src/self-hosted-assets.mjs +604 -0
- package/src/session-client.mjs +750 -0
- package/src/session-native-launcher.mjs +74 -0
- package/src/session-protocol.mjs +75 -0
- package/src/session-runtime.mjs +321 -0
- package/src/session-server.mjs +360 -0
- package/src/shader-kits/index.mjs +773 -0
- package/src/starter-content-registry.mjs +292 -0
- package/src/state-artifacts.mjs +662 -24
- package/src/state-dev-reload.mjs +99 -2
- package/src/terminal-ui.mjs +245 -0
- package/src/web-conformance.mjs +219 -0
- package/templates/create/2d/config/gameplay/shooter.config.js +26 -0
- package/templates/create/2d/content/gameplay/waves.json +26 -0
- package/templates/create/2d/content/registries/.gitkeep +1 -0
- package/templates/create/2d/docs/design/.gitkeep +1 -0
- package/templates/create/2d/docs/design/loop.md +5 -0
- package/templates/create/2d/prefabs/enemies.prefab.js +90 -0
- package/templates/create/2d/prefabs/enemy-basic.prefab.js +18 -0
- package/templates/create/2d/prefabs/player.prefab.js +36 -0
- package/templates/create/2d/prefabs/projectiles.prefab.js +35 -0
- package/templates/create/2d/scenes/boot.scene.js +12 -0
- package/templates/create/2d/scenes/gameplay.scene.js +230 -0
- package/templates/create/2d/scenes/menu.scene.js +9 -0
- package/templates/create/2d/src/main.js +6 -185
- package/templates/create/2d/src/runtime/app.js +49 -0
- package/templates/create/2d/src/runtime/capabilities.js +35 -0
- package/templates/create/2d/ui/hud.screen.js +40 -0
- package/templates/create/2d/ui/pause.screen.js +149 -0
- package/templates/create/2d/ui/settings.screen.js +347 -0
- package/templates/create/2d/ui/title.screen.js +13 -0
- package/templates/create/2d-adventure/aura.config.json +28 -0
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +14 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +46 -0
- package/templates/create/2d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/2d-adventure/docs/design/loop.md +5 -0
- package/templates/create/2d-adventure/prefabs/player.prefab.js +54 -0
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +38 -0
- package/templates/create/2d-adventure/prefabs/world.prefab.js +125 -0
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +256 -0
- package/templates/create/2d-adventure/src/runtime/capabilities.js +34 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +60 -0
- package/templates/create/2d-survivor/config/gameplay/survivor.config.js +33 -0
- package/templates/create/2d-survivor/content/gameplay/spawn-zones.json +29 -0
- package/templates/create/2d-survivor/content/registries/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/.gitkeep +1 -0
- package/templates/create/2d-survivor/docs/design/loop.md +5 -0
- package/templates/create/2d-survivor/prefabs/enemies.prefab.js +178 -0
- package/templates/create/2d-survivor/prefabs/enemy-swarm.prefab.js +18 -0
- package/templates/create/2d-survivor/prefabs/player.prefab.js +42 -0
- package/templates/create/2d-survivor/prefabs/projectiles.prefab.js +56 -0
- package/templates/create/2d-survivor/scenes/boot.scene.js +12 -0
- package/templates/create/2d-survivor/scenes/gameplay.scene.js +314 -0
- package/templates/create/2d-survivor/scenes/menu.scene.js +9 -0
- package/templates/create/2d-survivor/src/main.js +5 -332
- package/templates/create/2d-survivor/src/runtime/app.js +49 -0
- package/templates/create/2d-survivor/src/runtime/capabilities.js +35 -0
- package/templates/create/2d-survivor/ui/hud.screen.js +45 -0
- package/templates/create/2d-survivor/ui/title.screen.js +13 -0
- package/templates/create/3d/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d/content/gameplay/checkpoints.json +33 -0
- package/templates/create/3d/content/gameplay/course.js +40 -0
- package/templates/create/3d/content/registries/.gitkeep +1 -0
- package/templates/create/3d/docs/design/.gitkeep +1 -0
- package/templates/create/3d/docs/design/loop.md +5 -0
- package/templates/create/3d/prefabs/checkpoint.prefab.js +15 -0
- package/templates/create/3d/prefabs/player.prefab.js +204 -0
- package/templates/create/3d/prefabs/world.prefab.js +112 -0
- package/templates/create/3d/scenes/boot.scene.js +12 -0
- package/templates/create/3d/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d/scenes/gameplay.scene.js +292 -0
- package/templates/create/3d/src/main.js +6 -295
- package/templates/create/3d/src/runtime/app.js +49 -0
- package/templates/create/3d/src/runtime/capabilities.js +53 -0
- package/templates/create/3d/src/runtime/materials.js +34 -0
- package/templates/create/3d/src/runtime/state.js +39 -0
- package/templates/create/3d/ui/hud.screen.js +75 -0
- package/templates/create/3d/ui/pause.screen.js +166 -0
- package/templates/create/3d/ui/settings.screen.js +387 -0
- package/templates/create/3d-adventure/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-adventure/aura.config.json +28 -0
- package/templates/create/3d-adventure/config/gameplay/adventure.config.js +9 -0
- package/templates/create/3d-adventure/content/gameplay/course.js +62 -0
- package/templates/create/3d-adventure/content/registries/.gitkeep +1 -0
- package/templates/create/3d-adventure/docs/design/loop.md +5 -0
- package/templates/create/3d-adventure/prefabs/player.prefab.js +168 -0
- package/templates/create/3d-adventure/prefabs/relic.prefab.js +35 -0
- package/templates/create/3d-adventure/prefabs/world.prefab.js +119 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +358 -0
- package/templates/create/3d-adventure/src/runtime/capabilities.js +56 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +39 -0
- package/templates/create/3d-adventure/src/runtime/state.js +31 -0
- package/templates/create/3d-adventure/ui/hud.screen.js +70 -0
- package/templates/create/3d-adventure/ui/pause.screen.js +437 -0
- package/templates/create/3d-collectathon/assets/models/starter-avatar.gltf +184 -0
- package/templates/create/3d-collectathon/config/gameplay/.gitkeep +1 -0
- package/templates/create/3d-collectathon/content/gameplay/collectibles.json +26 -0
- package/templates/create/3d-collectathon/content/gameplay/course.js +46 -0
- package/templates/create/3d-collectathon/content/registries/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/.gitkeep +1 -0
- package/templates/create/3d-collectathon/docs/design/loop.md +5 -0
- package/templates/create/3d-collectathon/prefabs/collectible.prefab.js +15 -0
- package/templates/create/3d-collectathon/prefabs/player.prefab.js +207 -0
- package/templates/create/3d-collectathon/prefabs/world.prefab.js +112 -0
- package/templates/create/3d-collectathon/scenes/boot.scene.js +12 -0
- package/templates/create/3d-collectathon/scenes/checkpoint.scene.js +9 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +200 -0
- package/templates/create/3d-collectathon/src/main.js +5 -355
- package/templates/create/3d-collectathon/src/runtime/app.js +49 -0
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +53 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +34 -0
- package/templates/create/3d-collectathon/src/runtime/state.js +27 -0
- package/templates/create/3d-collectathon/ui/hud.screen.js +66 -0
- package/templates/create/3d-collectathon/ui/pause.screen.js +13 -0
- package/templates/create/blank/config/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/gameplay/.gitkeep +1 -0
- package/templates/create/blank/content/registries/.gitkeep +1 -0
- package/templates/create/blank/docs/design/.gitkeep +1 -0
- package/templates/create/blank/docs/design/loop.md +5 -0
- package/templates/create/blank/prefabs/.gitkeep +1 -0
- package/templates/create/blank/scenes/.gitkeep +1 -0
- package/templates/create/blank/src/runtime/.gitkeep +1 -0
- package/templates/create/blank/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/audio/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/fonts/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/sprites/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/assets/starter/README.md +11 -0
- package/templates/create/deckbuilder-2d/assets/ui/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/aura.config.json +28 -0
- package/templates/create/deckbuilder-2d/config/gameplay/deckbuilder.config.js +26 -0
- package/templates/create/deckbuilder-2d/content/cards/guard.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/spark.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/cards/starter.deck.js +69 -0
- package/templates/create/deckbuilder-2d/content/cards/strike.card.js +19 -0
- package/templates/create/deckbuilder-2d/content/cards/survey.card.js +20 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.encounter.js +14 -0
- package/templates/create/deckbuilder-2d/content/encounters/training-battle.js +65 -0
- package/templates/create/deckbuilder-2d/content/enemies/training-automaton.enemy.js +48 -0
- package/templates/create/deckbuilder-2d/content/gameplay/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/content/registries/cards.registry.js +26 -0
- package/templates/create/deckbuilder-2d/content/registries/encounters.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/enemies.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/registries/relics.registry.js +20 -0
- package/templates/create/deckbuilder-2d/content/relics/ember-charm.relic.js +18 -0
- package/templates/create/deckbuilder-2d/docs/design/loop.md +12 -0
- package/templates/create/deckbuilder-2d/prefabs/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/scenes/boot.scene.js +84 -0
- package/templates/create/deckbuilder-2d/scenes/gameplay.scene.js +641 -0
- package/templates/create/deckbuilder-2d/src/components/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/main.js +17 -0
- package/templates/create/deckbuilder-2d/src/runtime/capabilities.js +22 -0
- package/templates/create/deckbuilder-2d/src/shared/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/src/systems/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/tests/smoke/.gitkeep +1 -0
- package/templates/create/deckbuilder-2d/ui/hud.screen.js +80 -0
- package/templates/create/deckbuilder-2d/ui/pause.screen.js +146 -0
- package/templates/create/deckbuilder-2d/ui/settings.screen.js +342 -0
- package/templates/create/local-multiplayer/aura.config.json +41 -0
- package/templates/create/local-multiplayer/config/gameplay/local-multiplayer.config.js +26 -0
- package/templates/create/local-multiplayer/content/gameplay/room-layout.js +13 -0
- package/templates/create/local-multiplayer/content/registries/.gitkeep +1 -0
- package/templates/create/local-multiplayer/docs/design/loop.md +16 -0
- package/templates/create/local-multiplayer/prefabs/player.prefab.js +99 -0
- package/templates/create/local-multiplayer/scenes/boot.scene.js +12 -0
- package/templates/create/local-multiplayer/scenes/gameplay.scene.js +472 -0
- package/templates/create/local-multiplayer/src/main.js +17 -0
- package/templates/create/local-multiplayer/src/runtime/capabilities.js +28 -0
- package/templates/create/local-multiplayer/ui/hud.screen.js +65 -0
- package/templates/create/shared/src/runtime/project-inspector.js +105 -0
- package/templates/create/shared/src/runtime/scene-flow.js +290 -0
- package/templates/create/shared/src/runtime/screen-shell.js +222 -0
- package/templates/create/shared/src/runtime/ui-forms.js +209 -0
- package/templates/create/shared/src/runtime/ui-settings.js +237 -0
- package/templates/create/shared/src/runtime/ui-theme.js +352 -0
- package/templates/create/shared/src/starter-utils/adventure-objectives.js +102 -0
- package/templates/create/shared/src/starter-utils/animation-2d.js +337 -0
- package/templates/create/shared/src/starter-utils/avatar-3d.js +404 -0
- package/templates/create/shared/src/starter-utils/combat-feedback-2d.js +320 -0
- package/templates/create/shared/src/starter-utils/core.js +39 -3
- package/templates/create/shared/src/starter-utils/index.js +8 -2
- package/templates/create/shared/src/starter-utils/platformer-3d.js +34 -3
- package/templates/create/shared/src/starter-utils/triggers.js +662 -0
- package/templates/create/shared/src/starter-utils/tween-2d.js +615 -0
- package/templates/create/video-cutscene/assets/video/.gitkeep +0 -0
- package/templates/create/video-cutscene/aura.config.json +28 -0
- package/templates/create/video-cutscene/config/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/gameplay/.gitkeep +0 -0
- package/templates/create/video-cutscene/content/registries/.gitkeep +0 -0
- package/templates/create/video-cutscene/docs/design/loop.md +22 -0
- package/templates/create/video-cutscene/prefabs/.gitkeep +0 -0
- package/templates/create/video-cutscene/scenes/boot.scene.js +11 -0
- package/templates/create/video-cutscene/scenes/cutscene.scene.js +113 -0
- package/templates/create/video-cutscene/scenes/gameplay.scene.js +50 -0
- package/templates/create/video-cutscene/src/main.js +17 -0
- package/templates/create/video-cutscene/src/runtime/app.js +52 -0
- package/templates/create/video-cutscene/src/runtime/capabilities.js +35 -0
- package/templates/create/video-cutscene/src/runtime/state.js +13 -0
- package/templates/create/video-cutscene/ui/.gitkeep +0 -0
- package/templates/create-bin/play.js +1192 -0
- package/templates/make/README.md +46 -0
- package/templates/make/catalog.json +51 -0
- package/templates/make/component/files/{{MAKE_NAME}}.component.js +20 -0
- package/templates/make/component/manifest.json +9 -0
- package/templates/make/data/files/{{MAKE_NAME}}.json +14 -0
- package/templates/make/data/manifest.json +9 -0
- package/templates/make/material/files/{{MAKE_NAME}}.material.json +17 -0
- package/templates/make/material/manifest.json +9 -0
- package/templates/make/prefab/files/{{MAKE_NAME}}.prefab.js +20 -0
- package/templates/make/prefab/manifest.json +9 -0
- package/templates/make/scene/files/{{MAKE_NAME}}.scene.js +31 -0
- package/templates/make/scene/manifest.json +9 -0
- package/templates/make/shader/files/{{MAKE_NAME}}.shader.js +23 -0
- package/templates/make/shader/manifest.json +9 -0
- package/templates/make/system/files/{{MAKE_NAME}}.system.js +15 -0
- package/templates/make/system/manifest.json +9 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.js +16 -0
- package/templates/make/ui-screen/files/{{MAKE_NAME}}.screen.json +23 -0
- package/templates/make/ui-screen/manifest.json +10 -0
- package/templates/make-starters/deckbuilder-2d/card/files/{{MAKE_NAME}}.card.js +22 -0
- package/templates/make-starters/deckbuilder-2d/card/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/catalog.json +34 -0
- package/templates/make-starters/deckbuilder-2d/encounter/files/{{MAKE_NAME}}.encounter.js +18 -0
- package/templates/make-starters/deckbuilder-2d/encounter/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/enemy/files/{{MAKE_NAME}}.enemy.js +28 -0
- package/templates/make-starters/deckbuilder-2d/enemy/manifest.json +9 -0
- package/templates/make-starters/deckbuilder-2d/relic/files/{{MAKE_NAME}}.relic.js +23 -0
- package/templates/make-starters/deckbuilder-2d/relic/manifest.json +9 -0
- package/templates/retro/platformer/README.md +10 -0
- package/templates/retro/platformer/assets/retro/assets.json +91 -0
- package/templates/retro/platformer/aura.config.json +7 -0
- package/templates/retro/platformer/package.json +5 -0
- package/templates/retro/platformer/src/main.js +40 -0
- package/templates/retro/puzzle-grid/README.md +10 -0
- package/templates/retro/puzzle-grid/assets/retro/assets.json +90 -0
- package/templates/retro/puzzle-grid/aura.config.json +7 -0
- package/templates/retro/puzzle-grid/package.json +5 -0
- package/templates/retro/puzzle-grid/src/main.js +29 -0
- package/templates/retro/tactics-grid/README.md +10 -0
- package/templates/retro/tactics-grid/assets/retro/assets.json +90 -0
- package/templates/retro/tactics-grid/aura.config.json +7 -0
- package/templates/retro/tactics-grid/package.json +5 -0
- package/templates/retro/tactics-grid/src/main.js +35 -0
- package/templates/retro/topdown-adventure/README.md +10 -0
- package/templates/retro/topdown-adventure/assets/retro/assets.json +95 -0
- package/templates/retro/topdown-adventure/aura.config.json +7 -0
- package/templates/retro/topdown-adventure/package.json +5 -0
- package/templates/retro/topdown-adventure/src/main.js +29 -0
- package/templates/skills/aurajs/SKILL.md +61 -5
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
export const GAME_ACTION_SCHEMA_VERSION = 'aurajs.game-action-schema.v1';
|
|
2
|
+
export const GAME_ACTION_REQUEST_SCHEMA_VERSION = 'aurajs.game-action-request.v1';
|
|
3
|
+
export const GAME_ACTION_RESULT_SCHEMA_VERSION = 'aurajs.game-action-result.v1';
|
|
4
|
+
|
|
5
|
+
const ACTION_CONTRACT_STORAGE_KEY = '__gameActionContract';
|
|
6
|
+
const ACTION_SCHEMA_KEY_ORDER = ['schemaVersion', 'actions'];
|
|
7
|
+
const ACTION_DEFINITION_KEY_ORDER = ['id', 'title', 'description', 'inputSchema'];
|
|
8
|
+
const ACTION_RESULT_KEY_ORDER = ['schemaVersion', 'ok', 'reasonCode', 'actionId', 'detail', 'output'];
|
|
9
|
+
|
|
10
|
+
export function createGameActionRuntimeHooks({ aura } = {}) {
|
|
11
|
+
if (!aura || typeof aura !== 'object') {
|
|
12
|
+
throw new Error('createGameActionRuntimeHooks requires an aura runtime object.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
defineActionContract(contract) {
|
|
17
|
+
return defineGameActionContract(aura, contract);
|
|
18
|
+
},
|
|
19
|
+
getActionSchema() {
|
|
20
|
+
return exportCanonicalGameActionSchema(aura);
|
|
21
|
+
},
|
|
22
|
+
runAction(request) {
|
|
23
|
+
return runCanonicalGameAction(aura, request);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function installGameActionRuntimeBootstrap(aura) {
|
|
29
|
+
const schemaVersion = 'aurajs.game-action-schema.v1';
|
|
30
|
+
const requestSchemaVersion = 'aurajs.game-action-request.v1';
|
|
31
|
+
const resultSchemaVersion = 'aurajs.game-action-result.v1';
|
|
32
|
+
const storageKey = '__gameActionContract';
|
|
33
|
+
const schemaKeyOrder = ['schemaVersion', 'actions'];
|
|
34
|
+
const definitionKeyOrder = ['id', 'title', 'description', 'inputSchema'];
|
|
35
|
+
const resultKeyOrder = ['schemaVersion', 'ok', 'reasonCode', 'actionId', 'detail', 'output'];
|
|
36
|
+
|
|
37
|
+
const isPlainObjectLocal = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
const sanitizeJsonValueLocal = (value) => {
|
|
39
|
+
if (value === null) return null;
|
|
40
|
+
if (typeof value === 'string' || typeof value === 'boolean') return value;
|
|
41
|
+
if (typeof value === 'number') {
|
|
42
|
+
return Number.isFinite(value) ? Number(value) : null;
|
|
43
|
+
}
|
|
44
|
+
if (Array.isArray(value)) {
|
|
45
|
+
return value.map((entry) => sanitizeJsonValueLocal(entry));
|
|
46
|
+
}
|
|
47
|
+
if (isPlainObjectLocal(value)) {
|
|
48
|
+
const out = {};
|
|
49
|
+
const keys = Object.keys(value).sort();
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
const next = sanitizeJsonValueLocal(value[key]);
|
|
52
|
+
if (next === undefined) continue;
|
|
53
|
+
out[key] = next;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
};
|
|
59
|
+
const orderPayloadLocal = (payload, keyOrder) => {
|
|
60
|
+
const ordered = {};
|
|
61
|
+
for (const key of keyOrder) {
|
|
62
|
+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
63
|
+
ordered[key] = payload[key];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return ordered;
|
|
67
|
+
};
|
|
68
|
+
const createActionResultLocal = ({ ok, reasonCode, actionId = null, detail = null, output = undefined }) => {
|
|
69
|
+
const payload = {
|
|
70
|
+
schemaVersion: resultSchemaVersion,
|
|
71
|
+
ok: ok === true,
|
|
72
|
+
reasonCode: typeof reasonCode === 'string' && reasonCode
|
|
73
|
+
? reasonCode
|
|
74
|
+
: ok === true
|
|
75
|
+
? 'action_run_ok'
|
|
76
|
+
: 'action_run_failed',
|
|
77
|
+
};
|
|
78
|
+
if (typeof actionId === 'string' && actionId) {
|
|
79
|
+
payload.actionId = actionId;
|
|
80
|
+
}
|
|
81
|
+
if (typeof detail === 'string' && detail.trim()) {
|
|
82
|
+
payload.detail = detail.trim();
|
|
83
|
+
}
|
|
84
|
+
if (output !== undefined) {
|
|
85
|
+
payload.output = sanitizeJsonValueLocal(output);
|
|
86
|
+
}
|
|
87
|
+
return orderPayloadLocal(payload, resultKeyOrder);
|
|
88
|
+
};
|
|
89
|
+
const normalizeActionDefinitionsLocal = (actionEntries) => {
|
|
90
|
+
if (!Array.isArray(actionEntries)) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
reasonCode: 'invalid_action_contract',
|
|
94
|
+
detail: 'action contract actions must resolve to an array',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const normalizedActions = [];
|
|
99
|
+
const seenIds = new Set();
|
|
100
|
+
|
|
101
|
+
for (let index = 0; index < actionEntries.length; index += 1) {
|
|
102
|
+
const entry = actionEntries[index];
|
|
103
|
+
if (!isPlainObjectLocal(entry)) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reasonCode: 'invalid_action_contract',
|
|
107
|
+
detail: `action at index ${index} must be an object`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const id = typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
112
|
+
if (!id) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
reasonCode: 'invalid_action_contract',
|
|
116
|
+
detail: `action at index ${index} requires a non-empty id`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (seenIds.has(id)) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
reasonCode: 'invalid_action_contract',
|
|
123
|
+
detail: `duplicate action id "${id}"`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (!isPlainObjectLocal(entry.inputSchema)) {
|
|
127
|
+
return {
|
|
128
|
+
ok: false,
|
|
129
|
+
reasonCode: 'invalid_action_contract',
|
|
130
|
+
detail: `action "${id}" requires inputSchema object`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
seenIds.add(id);
|
|
135
|
+
const normalizedAction = {
|
|
136
|
+
id,
|
|
137
|
+
inputSchema: sanitizeJsonValueLocal(entry.inputSchema),
|
|
138
|
+
};
|
|
139
|
+
if (typeof entry.title === 'string' && entry.title.trim()) {
|
|
140
|
+
normalizedAction.title = entry.title.trim();
|
|
141
|
+
}
|
|
142
|
+
if (typeof entry.description === 'string' && entry.description.trim()) {
|
|
143
|
+
normalizedAction.description = entry.description.trim();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
normalizedActions.push(orderPayloadLocal(normalizedAction, definitionKeyOrder));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
ok: true,
|
|
151
|
+
actions: normalizedActions,
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
const normalizeActionContractLocal = (contract) => {
|
|
155
|
+
if (!isPlainObjectLocal(contract)) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
reasonCode: 'invalid_action_contract',
|
|
159
|
+
detail: 'action contract must be an object',
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const actions = contract.actions;
|
|
164
|
+
if (!Array.isArray(actions) && typeof actions !== 'function') {
|
|
165
|
+
return {
|
|
166
|
+
ok: false,
|
|
167
|
+
reasonCode: 'invalid_action_contract',
|
|
168
|
+
detail: 'action contract requires actions array or function',
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
if (typeof contract.run !== 'function') {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
reasonCode: 'invalid_action_contract',
|
|
175
|
+
detail: 'action contract requires run(action, context)',
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (Array.isArray(actions)) {
|
|
180
|
+
const normalized = normalizeActionDefinitionsLocal(actions);
|
|
181
|
+
if (!normalized.ok) {
|
|
182
|
+
return normalized;
|
|
183
|
+
}
|
|
184
|
+
return {
|
|
185
|
+
ok: true,
|
|
186
|
+
contract: Object.freeze({
|
|
187
|
+
actions: Object.freeze(normalized.actions),
|
|
188
|
+
run: contract.run,
|
|
189
|
+
}),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
contract: Object.freeze({
|
|
196
|
+
actions,
|
|
197
|
+
run: contract.run,
|
|
198
|
+
}),
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
const resolveActionSchemaPayloadLocal = () => {
|
|
202
|
+
const contract = aura && typeof aura === 'object' ? aura[storageKey] : null;
|
|
203
|
+
if (!contract || typeof contract !== 'object') {
|
|
204
|
+
return {
|
|
205
|
+
ok: false,
|
|
206
|
+
reasonCode: 'action_contract_unavailable',
|
|
207
|
+
detail: 'game did not define aura.action contract',
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let actionEntries;
|
|
212
|
+
try {
|
|
213
|
+
actionEntries = typeof contract.actions === 'function'
|
|
214
|
+
? contract.actions({ aura })
|
|
215
|
+
: contract.actions;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
reasonCode: 'invalid_action_contract',
|
|
220
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const normalized = normalizeActionDefinitionsLocal(actionEntries);
|
|
225
|
+
if (!normalized.ok) {
|
|
226
|
+
return normalized;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
ok: true,
|
|
231
|
+
contract,
|
|
232
|
+
payload: orderPayloadLocal({
|
|
233
|
+
schemaVersion,
|
|
234
|
+
actions: normalized.actions,
|
|
235
|
+
}, schemaKeyOrder),
|
|
236
|
+
};
|
|
237
|
+
};
|
|
238
|
+
const validateActionRequestLocal = (request, actions) => {
|
|
239
|
+
if (!isPlainObjectLocal(request)) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
result: createActionResultLocal({
|
|
243
|
+
ok: false,
|
|
244
|
+
reasonCode: 'invalid_action_request',
|
|
245
|
+
detail: 'request must be an object',
|
|
246
|
+
}),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (request.schemaVersion !== requestSchemaVersion) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
result: createActionResultLocal({
|
|
253
|
+
ok: false,
|
|
254
|
+
reasonCode: 'schema_version_mismatch',
|
|
255
|
+
detail: `expected ${requestSchemaVersion}`,
|
|
256
|
+
}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
if (!isPlainObjectLocal(request.action)) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
result: createActionResultLocal({
|
|
263
|
+
ok: false,
|
|
264
|
+
reasonCode: 'invalid_action_request',
|
|
265
|
+
detail: 'request.action must be an object',
|
|
266
|
+
}),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const actionId = typeof request.action.id === 'string' ? request.action.id.trim() : '';
|
|
271
|
+
if (!actionId) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
result: createActionResultLocal({
|
|
275
|
+
ok: false,
|
|
276
|
+
reasonCode: 'invalid_action_request',
|
|
277
|
+
detail: 'request.action.id must be a non-empty string',
|
|
278
|
+
}),
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const action = Array.isArray(actions) ? actions.find((entry) => entry.id === actionId) : null;
|
|
283
|
+
if (!action) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
result: createActionResultLocal({
|
|
287
|
+
ok: false,
|
|
288
|
+
reasonCode: 'action_unknown_id',
|
|
289
|
+
actionId,
|
|
290
|
+
detail: `unknown action "${actionId}"`,
|
|
291
|
+
}),
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
request,
|
|
298
|
+
action: {
|
|
299
|
+
id: actionId,
|
|
300
|
+
args: sanitizeJsonValueLocal(request.action.args),
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
};
|
|
304
|
+
const normalizeActionRunResultLocal = (rawResult, actionId) => {
|
|
305
|
+
if (rawResult && typeof rawResult === 'object' && rawResult.schemaVersion === resultSchemaVersion) {
|
|
306
|
+
return rawResult;
|
|
307
|
+
}
|
|
308
|
+
if (rawResult === false) {
|
|
309
|
+
return createActionResultLocal({
|
|
310
|
+
ok: false,
|
|
311
|
+
actionId,
|
|
312
|
+
reasonCode: 'action_run_failed',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (!isPlainObjectLocal(rawResult)) {
|
|
316
|
+
return createActionResultLocal({
|
|
317
|
+
ok: true,
|
|
318
|
+
actionId,
|
|
319
|
+
reasonCode: 'action_run_ok',
|
|
320
|
+
output: rawResult,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (rawResult.ok === true) {
|
|
324
|
+
return createActionResultLocal({
|
|
325
|
+
ok: true,
|
|
326
|
+
actionId,
|
|
327
|
+
reasonCode: typeof rawResult.reasonCode === 'string' ? rawResult.reasonCode : 'action_run_ok',
|
|
328
|
+
output: rawResult.output,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
return createActionResultLocal({
|
|
332
|
+
ok: false,
|
|
333
|
+
actionId,
|
|
334
|
+
reasonCode: typeof rawResult.reasonCode === 'string' ? rawResult.reasonCode : 'action_run_failed',
|
|
335
|
+
detail: rawResult.detail || rawResult.error || null,
|
|
336
|
+
output: rawResult.output,
|
|
337
|
+
});
|
|
338
|
+
};
|
|
339
|
+
const normalizeThrownActionErrorLocal = (error, actionId) => createActionResultLocal({
|
|
340
|
+
ok: false,
|
|
341
|
+
actionId,
|
|
342
|
+
reasonCode: 'action_run_failed',
|
|
343
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
344
|
+
});
|
|
345
|
+
const defineActionContractLocal = (contract) => {
|
|
346
|
+
const normalized = normalizeActionContractLocal(contract);
|
|
347
|
+
if (!normalized.ok) {
|
|
348
|
+
return normalized;
|
|
349
|
+
}
|
|
350
|
+
aura[storageKey] = normalized.contract;
|
|
351
|
+
return {
|
|
352
|
+
ok: true,
|
|
353
|
+
reasonCode: 'action_contract_defined',
|
|
354
|
+
};
|
|
355
|
+
};
|
|
356
|
+
const exportCanonicalGameActionSchemaLocal = () => {
|
|
357
|
+
const resolved = resolveActionSchemaPayloadLocal();
|
|
358
|
+
if (!resolved.ok) {
|
|
359
|
+
return resolved;
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
ok: true,
|
|
363
|
+
reasonCode: 'action_schema_ok',
|
|
364
|
+
payload: resolved.payload,
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
const runCanonicalGameActionLocal = async (request) => {
|
|
368
|
+
const resolved = resolveActionSchemaPayloadLocal();
|
|
369
|
+
if (!resolved.ok) {
|
|
370
|
+
return createActionResultLocal({
|
|
371
|
+
ok: false,
|
|
372
|
+
reasonCode: resolved.reasonCode || 'action_contract_unavailable',
|
|
373
|
+
detail: resolved.detail || null,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const validated = validateActionRequestLocal(request, resolved.payload.actions);
|
|
378
|
+
if (!validated.ok) {
|
|
379
|
+
return validated.result;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const rawResult = await Promise.resolve(
|
|
384
|
+
resolved.contract.run(validated.action, {
|
|
385
|
+
aura,
|
|
386
|
+
request: validated.request,
|
|
387
|
+
schema: resolved.payload,
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
return normalizeActionRunResultLocal(rawResult, validated.action.id);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return normalizeThrownActionErrorLocal(error, validated.action.id);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const actionNamespace = aura && typeof aura.action === 'object' ? aura.action : {};
|
|
397
|
+
actionNamespace.schemaVersion = schemaVersion;
|
|
398
|
+
if (typeof actionNamespace.define !== 'function') {
|
|
399
|
+
actionNamespace.define = (contract) => defineActionContractLocal(contract);
|
|
400
|
+
}
|
|
401
|
+
if (typeof actionNamespace.schema !== 'function') {
|
|
402
|
+
actionNamespace.schema = () => {
|
|
403
|
+
const result = exportCanonicalGameActionSchemaLocal();
|
|
404
|
+
return result.ok === true ? result.payload : result;
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
if (typeof actionNamespace.getSchema !== 'function') {
|
|
408
|
+
actionNamespace.getSchema = () => exportCanonicalGameActionSchemaLocal();
|
|
409
|
+
}
|
|
410
|
+
if (typeof actionNamespace.run !== 'function') {
|
|
411
|
+
actionNamespace.run = (request) => runCanonicalGameActionLocal(request);
|
|
412
|
+
}
|
|
413
|
+
if (typeof actionNamespace.runAction !== 'function') {
|
|
414
|
+
actionNamespace.runAction = (request) => runCanonicalGameActionLocal(request);
|
|
415
|
+
}
|
|
416
|
+
aura.action = actionNamespace;
|
|
417
|
+
return actionNamespace;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function buildGameActionRuntimeBootstrapSource() {
|
|
421
|
+
return `(${installGameActionRuntimeBootstrap.toString()})(aura);`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function defineGameActionContract(aura, contract) {
|
|
425
|
+
if (!aura || typeof aura !== 'object') {
|
|
426
|
+
throw new Error('defineGameActionContract requires an aura runtime object.');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const normalized = normalizeActionContract(contract);
|
|
430
|
+
if (!normalized.ok) {
|
|
431
|
+
return normalized;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
aura[ACTION_CONTRACT_STORAGE_KEY] = normalized.contract;
|
|
435
|
+
return {
|
|
436
|
+
ok: true,
|
|
437
|
+
reasonCode: 'action_contract_defined',
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export function exportCanonicalGameActionSchema(aura) {
|
|
442
|
+
const resolved = resolveActionSchemaPayload(aura);
|
|
443
|
+
if (!resolved.ok) {
|
|
444
|
+
return resolved;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
ok: true,
|
|
449
|
+
reasonCode: 'action_schema_ok',
|
|
450
|
+
payload: resolved.payload,
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export async function runCanonicalGameAction(aura, request) {
|
|
455
|
+
const resolved = resolveActionSchemaPayload(aura);
|
|
456
|
+
if (!resolved.ok) {
|
|
457
|
+
return createActionResult({
|
|
458
|
+
ok: false,
|
|
459
|
+
reasonCode: resolved.reasonCode || 'action_contract_unavailable',
|
|
460
|
+
detail: resolved.detail || null,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const validated = validateActionRequest(request, resolved.payload.actions);
|
|
465
|
+
if (!validated.ok) {
|
|
466
|
+
return validated.result;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const contract = resolved.contract;
|
|
470
|
+
try {
|
|
471
|
+
const rawResult = await Promise.resolve(
|
|
472
|
+
contract.run(validated.action, {
|
|
473
|
+
aura,
|
|
474
|
+
request: validated.request,
|
|
475
|
+
schema: resolved.payload,
|
|
476
|
+
}),
|
|
477
|
+
);
|
|
478
|
+
return normalizeActionRunResult(rawResult, validated.action.id);
|
|
479
|
+
} catch (error) {
|
|
480
|
+
return normalizeThrownActionError(error, validated.action.id);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function normalizeActionContract(contract) {
|
|
485
|
+
if (!isPlainObject(contract)) {
|
|
486
|
+
return {
|
|
487
|
+
ok: false,
|
|
488
|
+
reasonCode: 'invalid_action_contract',
|
|
489
|
+
detail: 'action contract must be an object',
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const actions = contract.actions;
|
|
494
|
+
if (!Array.isArray(actions) && typeof actions !== 'function') {
|
|
495
|
+
return {
|
|
496
|
+
ok: false,
|
|
497
|
+
reasonCode: 'invalid_action_contract',
|
|
498
|
+
detail: 'action contract requires actions array or function',
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (typeof contract.run !== 'function') {
|
|
503
|
+
return {
|
|
504
|
+
ok: false,
|
|
505
|
+
reasonCode: 'invalid_action_contract',
|
|
506
|
+
detail: 'action contract requires run(action, context)',
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (Array.isArray(actions)) {
|
|
511
|
+
const normalizedActions = normalizeActionDefinitions(actions);
|
|
512
|
+
if (!normalizedActions.ok) {
|
|
513
|
+
return normalizedActions;
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
ok: true,
|
|
517
|
+
contract: Object.freeze({
|
|
518
|
+
actions: Object.freeze(normalizedActions.actions),
|
|
519
|
+
run: contract.run,
|
|
520
|
+
}),
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return {
|
|
525
|
+
ok: true,
|
|
526
|
+
contract: Object.freeze({
|
|
527
|
+
actions,
|
|
528
|
+
run: contract.run,
|
|
529
|
+
}),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function resolveActionSchemaPayload(aura) {
|
|
534
|
+
const contract = aura && typeof aura === 'object'
|
|
535
|
+
? aura[ACTION_CONTRACT_STORAGE_KEY]
|
|
536
|
+
: null;
|
|
537
|
+
if (!contract || typeof contract !== 'object') {
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
reasonCode: 'action_contract_unavailable',
|
|
541
|
+
detail: 'game did not define aura.action contract',
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let actionEntries;
|
|
546
|
+
try {
|
|
547
|
+
actionEntries = typeof contract.actions === 'function'
|
|
548
|
+
? contract.actions({ aura })
|
|
549
|
+
: contract.actions;
|
|
550
|
+
} catch (error) {
|
|
551
|
+
return {
|
|
552
|
+
ok: false,
|
|
553
|
+
reasonCode: 'invalid_action_contract',
|
|
554
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const normalized = normalizeActionDefinitions(actionEntries);
|
|
559
|
+
if (!normalized.ok) {
|
|
560
|
+
return normalized;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
ok: true,
|
|
565
|
+
contract,
|
|
566
|
+
payload: orderActionSchemaPayload({
|
|
567
|
+
schemaVersion: GAME_ACTION_SCHEMA_VERSION,
|
|
568
|
+
actions: normalized.actions,
|
|
569
|
+
}),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function normalizeActionDefinitions(actionEntries) {
|
|
574
|
+
if (!Array.isArray(actionEntries)) {
|
|
575
|
+
return {
|
|
576
|
+
ok: false,
|
|
577
|
+
reasonCode: 'invalid_action_contract',
|
|
578
|
+
detail: 'action contract actions must resolve to an array',
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const normalizedActions = [];
|
|
583
|
+
const seenIds = new Set();
|
|
584
|
+
|
|
585
|
+
for (let index = 0; index < actionEntries.length; index += 1) {
|
|
586
|
+
const entry = actionEntries[index];
|
|
587
|
+
if (!isPlainObject(entry)) {
|
|
588
|
+
return {
|
|
589
|
+
ok: false,
|
|
590
|
+
reasonCode: 'invalid_action_contract',
|
|
591
|
+
detail: `action at index ${index} must be an object`,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const id = typeof entry.id === 'string' ? entry.id.trim() : '';
|
|
596
|
+
if (!id) {
|
|
597
|
+
return {
|
|
598
|
+
ok: false,
|
|
599
|
+
reasonCode: 'invalid_action_contract',
|
|
600
|
+
detail: `action at index ${index} requires a non-empty id`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
if (seenIds.has(id)) {
|
|
604
|
+
return {
|
|
605
|
+
ok: false,
|
|
606
|
+
reasonCode: 'invalid_action_contract',
|
|
607
|
+
detail: `duplicate action id "${id}"`,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
if (!isPlainObject(entry.inputSchema)) {
|
|
612
|
+
return {
|
|
613
|
+
ok: false,
|
|
614
|
+
reasonCode: 'invalid_action_contract',
|
|
615
|
+
detail: `action "${id}" requires inputSchema object`,
|
|
616
|
+
};
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
seenIds.add(id);
|
|
620
|
+
|
|
621
|
+
const normalizedAction = {
|
|
622
|
+
id,
|
|
623
|
+
inputSchema: sanitizeJsonValue(entry.inputSchema),
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
if (typeof entry.title === 'string' && entry.title.trim()) {
|
|
627
|
+
normalizedAction.title = entry.title.trim();
|
|
628
|
+
}
|
|
629
|
+
if (typeof entry.description === 'string' && entry.description.trim()) {
|
|
630
|
+
normalizedAction.description = entry.description.trim();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
normalizedActions.push(orderActionDefinition(normalizedAction));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return {
|
|
637
|
+
ok: true,
|
|
638
|
+
actions: normalizedActions,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function validateActionRequest(request, actions) {
|
|
643
|
+
if (!isPlainObject(request)) {
|
|
644
|
+
return {
|
|
645
|
+
ok: false,
|
|
646
|
+
result: createActionResult({
|
|
647
|
+
ok: false,
|
|
648
|
+
reasonCode: 'invalid_action_request',
|
|
649
|
+
detail: 'request must be an object',
|
|
650
|
+
}),
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
if (request.schemaVersion !== GAME_ACTION_REQUEST_SCHEMA_VERSION) {
|
|
655
|
+
return {
|
|
656
|
+
ok: false,
|
|
657
|
+
result: createActionResult({
|
|
658
|
+
ok: false,
|
|
659
|
+
reasonCode: 'invalid_action_request',
|
|
660
|
+
detail: `expected ${GAME_ACTION_REQUEST_SCHEMA_VERSION}`,
|
|
661
|
+
}),
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (!isPlainObject(request.action)) {
|
|
666
|
+
return {
|
|
667
|
+
ok: false,
|
|
668
|
+
result: createActionResult({
|
|
669
|
+
ok: false,
|
|
670
|
+
reasonCode: 'invalid_action_request',
|
|
671
|
+
detail: 'action must be an object',
|
|
672
|
+
}),
|
|
673
|
+
};
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const actionId = typeof request.action.id === 'string' ? request.action.id.trim() : '';
|
|
677
|
+
if (!actionId) {
|
|
678
|
+
return {
|
|
679
|
+
ok: false,
|
|
680
|
+
result: createActionResult({
|
|
681
|
+
ok: false,
|
|
682
|
+
reasonCode: 'invalid_action_request',
|
|
683
|
+
detail: 'action.id must be a non-empty string',
|
|
684
|
+
}),
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const args = request.action.args == null ? {} : request.action.args;
|
|
689
|
+
if (!isPlainObject(args)) {
|
|
690
|
+
return {
|
|
691
|
+
ok: false,
|
|
692
|
+
result: createActionResult({
|
|
693
|
+
ok: false,
|
|
694
|
+
reasonCode: 'invalid_action_request',
|
|
695
|
+
actionId,
|
|
696
|
+
detail: 'action.args must be an object',
|
|
697
|
+
}),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const knownActionIds = new Set(actions.map((action) => action.id));
|
|
702
|
+
if (!knownActionIds.has(actionId)) {
|
|
703
|
+
return {
|
|
704
|
+
ok: false,
|
|
705
|
+
result: createActionResult({
|
|
706
|
+
ok: false,
|
|
707
|
+
reasonCode: 'invalid_action_request',
|
|
708
|
+
actionId,
|
|
709
|
+
detail: `action "${actionId}" is not declared in schema`,
|
|
710
|
+
}),
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const normalizedAction = {
|
|
715
|
+
id: actionId,
|
|
716
|
+
args: sanitizeJsonValue(args),
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
ok: true,
|
|
721
|
+
action: normalizedAction,
|
|
722
|
+
request: {
|
|
723
|
+
schemaVersion: GAME_ACTION_REQUEST_SCHEMA_VERSION,
|
|
724
|
+
action: normalizedAction,
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function normalizeActionRunResult(rawResult, actionId) {
|
|
730
|
+
if (rawResult === false) {
|
|
731
|
+
return createActionResult({
|
|
732
|
+
ok: false,
|
|
733
|
+
reasonCode: 'action_run_failed',
|
|
734
|
+
actionId,
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (rawResult == null || rawResult === true) {
|
|
739
|
+
return createActionResult({
|
|
740
|
+
ok: true,
|
|
741
|
+
reasonCode: 'action_run_ok',
|
|
742
|
+
actionId,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (isPlainObject(rawResult) && typeof rawResult.ok === 'boolean') {
|
|
747
|
+
return createActionResult({
|
|
748
|
+
ok: rawResult.ok,
|
|
749
|
+
reasonCode: typeof rawResult.reasonCode === 'string' && rawResult.reasonCode
|
|
750
|
+
? rawResult.reasonCode
|
|
751
|
+
: rawResult.ok === true
|
|
752
|
+
? 'action_run_ok'
|
|
753
|
+
: 'action_run_failed',
|
|
754
|
+
actionId,
|
|
755
|
+
detail: typeof rawResult.detail === 'string' ? rawResult.detail : null,
|
|
756
|
+
output: Object.prototype.hasOwnProperty.call(rawResult, 'output') ? rawResult.output : undefined,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return createActionResult({
|
|
761
|
+
ok: true,
|
|
762
|
+
reasonCode: 'action_run_ok',
|
|
763
|
+
actionId,
|
|
764
|
+
output: rawResult,
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function normalizeThrownActionError(error, actionId) {
|
|
769
|
+
if (error && typeof error === 'object' && typeof error.reasonCode === 'string' && error.reasonCode) {
|
|
770
|
+
return createActionResult({
|
|
771
|
+
ok: false,
|
|
772
|
+
reasonCode: error.reasonCode,
|
|
773
|
+
actionId,
|
|
774
|
+
detail: typeof error.detail === 'string'
|
|
775
|
+
? error.detail
|
|
776
|
+
: typeof error.message === 'string'
|
|
777
|
+
? error.message
|
|
778
|
+
: null,
|
|
779
|
+
output: Object.prototype.hasOwnProperty.call(error, 'output') ? error.output : undefined,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
return createActionResult({
|
|
784
|
+
ok: false,
|
|
785
|
+
reasonCode: 'action_run_failed',
|
|
786
|
+
actionId,
|
|
787
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function createActionResult({ ok, reasonCode, actionId = null, detail = null, output = undefined }) {
|
|
792
|
+
const payload = {
|
|
793
|
+
schemaVersion: GAME_ACTION_RESULT_SCHEMA_VERSION,
|
|
794
|
+
ok: ok === true,
|
|
795
|
+
reasonCode: typeof reasonCode === 'string' && reasonCode
|
|
796
|
+
? reasonCode
|
|
797
|
+
: ok === true
|
|
798
|
+
? 'action_run_ok'
|
|
799
|
+
: 'action_run_failed',
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
if (typeof actionId === 'string' && actionId) {
|
|
803
|
+
payload.actionId = actionId;
|
|
804
|
+
}
|
|
805
|
+
if (typeof detail === 'string' && detail.trim()) {
|
|
806
|
+
payload.detail = detail.trim();
|
|
807
|
+
}
|
|
808
|
+
if (output !== undefined) {
|
|
809
|
+
payload.output = sanitizeJsonValue(output);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
return orderActionResultPayload(payload);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function orderActionSchemaPayload(payload) {
|
|
816
|
+
const ordered = {};
|
|
817
|
+
for (const key of ACTION_SCHEMA_KEY_ORDER) {
|
|
818
|
+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
819
|
+
ordered[key] = payload[key];
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return ordered;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function orderActionDefinition(action) {
|
|
826
|
+
const ordered = {};
|
|
827
|
+
for (const key of ACTION_DEFINITION_KEY_ORDER) {
|
|
828
|
+
if (Object.prototype.hasOwnProperty.call(action, key)) {
|
|
829
|
+
ordered[key] = action[key];
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
return ordered;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function orderActionResultPayload(payload) {
|
|
836
|
+
const ordered = {};
|
|
837
|
+
for (const key of ACTION_RESULT_KEY_ORDER) {
|
|
838
|
+
if (Object.prototype.hasOwnProperty.call(payload, key)) {
|
|
839
|
+
ordered[key] = payload[key];
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return ordered;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
function sanitizeJsonValue(value) {
|
|
846
|
+
if (value === null) return null;
|
|
847
|
+
if (typeof value === 'string' || typeof value === 'boolean') return value;
|
|
848
|
+
if (typeof value === 'number') {
|
|
849
|
+
return Number.isFinite(value) ? Number(value) : null;
|
|
850
|
+
}
|
|
851
|
+
if (Array.isArray(value)) {
|
|
852
|
+
return value.map((entry) => sanitizeJsonValue(entry));
|
|
853
|
+
}
|
|
854
|
+
if (isPlainObject(value)) {
|
|
855
|
+
const out = {};
|
|
856
|
+
const keys = Object.keys(value).sort((a, b) => a.localeCompare(b));
|
|
857
|
+
for (const key of keys) {
|
|
858
|
+
const next = sanitizeJsonValue(value[key]);
|
|
859
|
+
if (next === undefined) continue;
|
|
860
|
+
out[key] = next;
|
|
861
|
+
}
|
|
862
|
+
return out;
|
|
863
|
+
}
|
|
864
|
+
return null;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function isPlainObject(value) {
|
|
868
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
869
|
+
}
|