Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 123 additions & 47 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,123 @@
# airconsole-appengine/static/api

This subtree is the AppEngine-served copy of the public AirConsole JavaScript API bundle.

## Verification Entry Points

- Browser regression harness: serve this subtree statically and open a versioned runner in `tests/`.
- Playwright verification: in `ci/`, use `npm run server` and `npm test`.

## Local Invariants

- Keep versioned root bundles backward compatible.
- Stage upcoming releases in `beta/` before promotion.
- Do not remove `deprecated/` assets.
- Never call `rm`, use `safe-rm` instead (brew install safe-rm).

## Read Next

- `tests/AGENTS.md`
- `ci/AGENTS.md`

## Memory Management

Always use the **Kratos MCP** to manage memory across sessions:

- Store relevant context, decisions, and learnings via `kratos_memory_save` before ending a session.
- Retrieve prior context at the start of a new session using `kratos_memory_search` or `kratos_memory_get_recent`.
- Use `kratos_memory_ask` for natural language queries against accumulated memory.
- Never rely solely on in-context state for information that should persist across sessions.

## Code Access

Always use the **Serena MCP** for reading and writing code:

- Use `serena_find_symbol`, `serena_get_symbols_overview`, and `serena_search_for_pattern` to navigate and understand code.
- Use `serena_find_referencing_symbols` to find all callers/references before refactoring, and `serena_rename_symbol` to rename a symbol consistently across the codebase.
- Use `serena_replace_symbol_body`, `serena_replace_content`, `serena_insert_after_symbol`, and `serena_insert_before_symbol` to make code changes.
- Prefer symbol-level tools over raw text replacement when the target is a named code entity.
- Always call `serena_check_onboarding_performed` after activating a project.

## Syntax and API Verification

Always use the **Context7 MCP** to verify correct syntax and API usage before writing or modifying code that depends on external libraries:

- Call `context7_resolve-library-id` first to obtain the correct library ID for any framework or package.
- Call `context7_query-docs` with a specific query to retrieve up-to-date documentation and code examples.
- Use Context7 before writing code that depends on external library APIs to avoid outdated or hallucinated usage patterns.
# airconsole-api

## OVERVIEW

- Static public AirConsole JavaScript API bundle.
- Versioned bundle family, current line is 1.10.0 plus variants.
- Served as plain browser JavaScript.
- Consumers load a fixed API version.
- Backward compatibility is enforced by versioned copies.
- Old games and store integrations must keep working.
- Regression coverage uses the Jasmine browser harness.
- CI drives that harness through Playwright.
- Treat this subtree as published API surface.
- Small edits can have long-lived compatibility cost.
- No per-feature behavior notes here.

## STRUCTURE

- `airconsole-X.Y.Z.js` contains versioned root bundles.
- `airconsole-1.10.0.js` is the current main bundle.
- `deprecated/` keeps older released API files.
- `beta/` holds experimental or pre-release API files.
- `tests/` owns regression assets.
- `tests/jasmine/` owns the manual Jasmine browser harness.
- `tests/spec/` and nearby JSON fixtures hold regression cases.
- `ci/` owns Playwright runner wiring when present.
- Generated docs and external developer docs are not source of truth.
- Preserve existing runner layout unless version policy changes.

## VERSION STRATEGY

- Strict semver.
- Backward-compatible fixes may stay on the current version line.
- Backward-incompatible changes require a version increment.
- Always increment for backward-incompatible changes.
- Never mutate old behavior in place for a breaking change.
- Copy forward, change the new version, leave old versions intact.
- `deprecated/` preserves old APIs for existing consumers.
- Deprecated does not mean deleted.
- Active and deprecated APIs stay separated.
- Version isolation is a contract.

## WHERE TO LOOK

- Main bundle: `airconsole-1.10.0.js`.
- Current versioned family: root `airconsole-X.Y.Z.js` files.
- Tests: `tests/jasmine/`.
- Regression JSON tests: `tests/`.
- Regression runner: local static server on port 9000.
- Deprecated APIs: `deprecated/`.
- Experimental APIs: `beta/`.
- Browser runner details: `tests/AGENTS.md`.
- CI runner details: `ci/AGENTS.md` if present.
- Root compatibility note: workspace `AGENTS.md`.

## ENTRY POINT

- Public entry point is global `AirConsole()` constructor.
- Constructor lives on the browser global object.
- Use constructor and prototype pattern.
- Do not rewrite public API shape as classes.
- Public methods hang from the versioned bundle behavior.
- Backward compatibility comes from versioned copies.
- Consumers expect old constructors to behave exactly as before.
- New versions may add behavior without changing old files.
- Avoid hidden cross-version coupling.
- Keep bundle behavior self-contained.

## FORBIDDEN

- NO hardcoding controller device IDs.
- NO edits to removed API folder.
- Removed API folder is off-limits.
- NEVER break backward compatibility without a version bump.
- Do not delete files from `deprecated/`.
- Do not collapse beta, deprecated, and active APIs together.
- Do not replace the public constructor with module-only exports.
- Do not add test-only behavior to production bundles.
- Do not assume only current store users exist.
- Do not bypass regression tests for API behavior changes.

## TEST HARNESS

- Jasmine browser suite is the primary regression harness.
- Playwright drives the browser suite in CI.
- `npm test` starts or runs the harness on port 9000.
- Manual browser harness lives in `tests/jasmine/`.
- Regression JSON tests live under `tests/`.
- Keep versioned runners aligned with bundle behavior.
- Add regression coverage before changing public behavior.
- Prefer extending existing specs and fixtures.
- Keep manual and CI expectations aligned.
- Port 9000 is the expected local runner port.

## CONVENTIONS

- Constructor/prototype pattern, not classes.
- Static bundle, browser-first assumptions.
- Strict version isolation.
- Deprecated APIs stay separate from active APIs.
- Beta APIs stay separate from released APIs.
- Public behavior changes need visible version intent.
- Small compatibility shims are better than consumer breakage.
- Keep tests close to versioned behavior.
- Prefer explicit fixtures over implicit browser state.
- Keep this guide telegraphic.

## COMMANDS

- `npm test`: run Jasmine harness through the port 9000 runner.
- `npm run version`: bundle versioning flow.
- If commands live in a nested runner package, run them there.
- Use the narrowest relevant validation for touched files.
- Stop after the first successful relevant verification.

## NOTES

- Backward compatibility is non-negotiable.
- Released games may pin old AirConsole versions indefinitely.
- Version mismatch with store-v3 is tracked in root `AGENTS.md`.
- Known mismatch: store-v3 has 1.0.24 while workspace uses `workspace:*`.
- Keep this file high-level.
- No per-function details.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Release notes follow the [keep a changelog](https://keepachangelog.com/en/1.0.0/

### Removed

- Added `AirConsole.getConfiguration()` to expose the platform capability configuration from the `ready` event on screens.
- Removed `MEDIA_PERMISSION_DENIED` enum (`temporary`/`permanent` distinction). Platform denials now reject with `AirConsoleUserMediaError("PermissionDenied")`; browser denials pass through the original `DOMException`.

## [1.10.0] - 2026-02-17
Expand Down
35 changes: 35 additions & 0 deletions beta/airconsole-1.11.0.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,40 @@ AirConsole.prototype.arePlayersSilenced = function () {
&& (this.devices[AirConsole.SCREEN]["players"] !== undefined && this.devices[AirConsole.SCREEN]["players"].length > 0);
}

/**
* Configuration Object
* @typedef {object} AirConsole~Configuration
* @property {string[]} supportedVideoFormats - Supported video codecs in order of priority e.g. ["vp9","h264","vp8"]
* @property {boolean} transparentVideoSupported - true, if transparent videos are supported.
* @property {boolean} unityVideoSupported - true, if Unity is allowed to play videos.
* @property {string} graphicsQualityTier - graphics quality tier, approximation of available hardware resources CPU / GPU wise, e.g. "low", "medium", or "high"
*/

/**
* Returns the platform capability configuration delivered in the ready event.
* Use this to branch on device capabilities instead of platform or partner
* names. Only available on the screen; throws on controllers.
* Can only be called after onReady.
* @return {AirConsole~Configuration|{}} - Resolved game configuration
* Returns an empty object on the screen if the platform did not send a configuration payload.
* @throws {string} "getConfiguration is only supported on AirConsole.SCREEN."
* when called from a controller.
* @throws {string} "getConfiguration is available only after onReady."
* when called before the onReady has been invoked on the screen.
* @since 1.11.0
*/
AirConsole.prototype.getConfiguration = function () {
if (this.device_id === undefined) {
throw "getConfiguration is available only after onReady.";
}

if (this.device_id === AirConsole.SCREEN) {
return this.configuration || {};
}

throw "getConfiguration is only supported on AirConsole.SCREEN.";
}

/**
* Dictionary of silenced update messages queued during a running game session.
* @private
Expand Down Expand Up @@ -1576,6 +1610,7 @@ AirConsole.prototype.onPostMessage_ = function(event) {
}

me.gameSafeArea = data.gameSafeArea;
me.configuration = data.configuration;
if (data.translations) {
me.translations = data.translations;
var elements = document.querySelectorAll("[data-translation]");
Expand Down
5 changes: 3 additions & 2 deletions tests/airconsole-1.11.0-spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@
<script src="spec/methods/spec-pause.js"></script>
<script src="spec/methods/spec-immersive.js"></script>
<script src="spec/methods/spec-player-silencing.js"></script>
<script src="spec/methods/spec-usermedia-permissions.js"></script>
<script src="spec/methods/spec-usermedia-permissions.js"></script>
<script src="spec/methods/spec-configuration.js"></script>

<!-- include spec files here... -->
<!-- include spec files here... -->
<script src="spec/airconsole-1.11.0-spec.js"></script>

</head>
Expand Down
17 changes: 17 additions & 0 deletions tests/spec/airconsole-1.11.0-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,4 +397,21 @@ describe("AirConsole 1.11.0", function () {

testUserMediaPermissions();
});

/**
======================================================================================
TEST CONFIGURATION FUNCTIONALITY
*/

describe("Configuration", function () {
beforeEach(function () {
initAirConsole();
});

afterEach(function () {
tearDown();
});

testGetConfiguration();
});
});
55 changes: 55 additions & 0 deletions tests/spec/methods/spec-configuration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
function testGetConfiguration() {

it ("Should store configuration from ready event", function() {
const configuration = {
supportedVideoFormats: ["vp9", "h264", "vp8"],
transparentVideoSupported: true,
unityVideoSupported: true,
graphicsQualityTier: "high"
};
dispatchCustomMessageEvent({
action: "ready",
code: 1237,
device_id: 0,
devices: [{}, undefined, airconsole.devices[DEVICE_ID]],
configuration: configuration
});
expect(airconsole.getConfiguration()).toEqual(configuration);
});

it ("Should return `{}` when not provided in ready event", function() {
dispatchCustomMessageEvent({
action: "ready",
code: 1237,
device_id: 0,
devices: [{}, undefined, airconsole.devices[DEVICE_ID]]
});

expect(airconsole.getConfiguration()).toEqual({});
});

it("Should throw before onReady fires", function () {
airconsole.device_id = undefined;

expect(airconsole.getConfiguration.bind(airconsole)).toThrow("getConfiguration is available only after onReady.");
});

it("Should throw when getConfiguration is called on controller", function () {
const configuration = {
supportedVideoFormats: ["vp9", "h264", "vp8"],
transparentVideoSupported: true,
unityVideoSupported: true,
graphicsQualityTier: "high"
};
dispatchCustomMessageEvent({
action: "ready",
code: 1237,
device_id: DEVICE_ID,
devices: [{}, undefined, airconsole.devices[DEVICE_ID]],
configuration
});

expect(airconsole.getConfiguration.bind(airconsole)).toThrow("getConfiguration is only supported on AirConsole.SCREEN.");
});

}