Skip to main content

App lifecycle

Lifecycle hooks report when the app document is hidden or shown and when the user asks to leave through Jest's platform controls.

Register listeners

Register hooks after SDK initialization. Every registration returns an unsubscribe function:

await JestSDK.init();

const unsubscribeHide = JestSDK.lifecycle.onHide(() => {
game.pause();
audio.pause();
});

const unsubscribeShow = JestSDK.lifecycle.onShow(() => {
game.resume();
audio.resume();
});

const unsubscribeExit = JestSDK.lifecycle.onExitRequested(async () => {
await saveProgress();
});

function destroyGame() {
unsubscribeHide();
unsubscribeShow();
unsubscribeExit();
}

Each method supports multiple listeners and returns a function that removes only that registration. Keep subscriptions active for the app lifetime, then remove them during teardown. Calling the removal function more than once is safe.

Hide

Runs when the browser changes the app document from visible to hidden, such as when the user switches tabs, backgrounds the browser, or locks the device. It does not run for the document's initial visibility state.

Use it to pause the game loop, physics, animation, and audio.

The hide and show hooks are the SDK's surface for the document's visibilitychange event, and Jest adds nothing to when they fire. An app that also observes browser visibility another way sees every transition twice, so subscribe one way or the other, not both.

JestSDK.lifecycle.onHide(listener)

Show

Runs when the app document changes from hidden back to visible. It does not run on initial startup; SDK initialization is the startup boundary.

Use it to resume work stopped by the hide hook, refresh time-sensitive state, and reconcile elapsed time.

JestSDK.lifecycle.onShow(listener)

Exit requested

Runs when the platform begins an exit flow, including its exit control and browser Back or mobile swipe-back navigation that Jest can intercept. It runs at the start of that flow — usually as the exit confirmation appears — and always before the user has answered it.

The user can still choose to stay, so this is an opportunity to save and not a shutdown notice. Do not tear down state, release resources, or stop the game loop from the listener; the session frequently continues. It also runs more than once in a session whenever a user asks to leave, backs out, and asks again, so keep the listener idempotent and expect to save again later.

The listener cannot cancel the exit or hold the app open past the user's confirmation. What it does get is the interval between the request and that confirmation, which is ordinary runtime rather than a teardown window. Start the save when the event runs.

This event only represents an exit flow the platform can intercept. Closing the tab, terminating the browser, or an operating-system shutdown may not produce a final event. Back navigation that leaves the Jest document directly, such as returning to an external referral page, may not produce one either. A few in-platform flows also navigate without a confirmation, in which case the event runs with no interval behind it.

JestSDK.lifecycle.onExitRequested(listener)

Do not defer the save to pagehide or beforeunload, where the document is already going away.

Ordering

The hooks are independent signals, not steps in a teardown sequence. Visibility comes from the browser and the exit request comes from the platform, so there is no guaranteed ordering between them and no pair is mutually exclusive. A platform exit runs the exit request with no visibility change; backgrounding the browser runs the hide hook with no exit request; locking the device while the exit confirmation is open runs both, in whichever order the browser reports. A hook that does not run is not evidence that the other one failed.

Saving on exit

Treat the exit event as the last of several saves rather than the only one. Data written through the player data API is sent to the Jest page, which performs the storage write. That page outlives the app document, so an update already on its way is not lost when the app is torn down. An update is not sent immediately while an earlier one is still unacknowledged — the SDK coalesces it into the next message — so a write issued at the very end of the exit interval can still go down with the document. Persist progress as the player earns it, and use this event to capture whatever has changed since the last save.

JestSDK.data.set(key, value)

JestSDK.lifecycle.onExitRequested(() => {
JestSDK.data.set("checkpoint", currentCheckpoint);
});

Asynchronous listeners

Listeners may return a promise. The SDK starts every listener immediately and reports synchronous errors and promise rejections without interrupting the other listeners. Awaiting inside a listener still sequences that listener's work, but it does not delay the exit confirmation or navigation. If navigation unloads the document first, the promise may not settle and code after an await may not run.

Start essential work before the first await.

JestSDK.lifecycle.onExitRequested(async () => {
JestSDK.data.set("checkpoint", currentCheckpoint);
await analytics.flush();
});

Testing locally

Running outside Jest.com puts the SDK in mock mode. Open the JestSDK debug menu and use Request Exit under App Lifecycle to send the platform exit request to your listeners. onHide and onShow come from real browser visibility, so switch tabs or lock the device to trigger them.