--- title: "Advertising cards" description: "Configure approved Advertising screens, show cards, renew bounded leases, and handle clear and expiry." canonical: "https://mojoware.net/docs/kiosk/advertising-cards/" --- # Advertising cards A WebApp can temporarily replace the playlist on an **approved Advertising screen on the same kiosk**. The operator chooses the media and allowed keys; the WebApp requests a key. [Start here](/docs/kiosk/home/) · [Bridge reference](/docs/kiosk/bridge-reference/) · [Complete Markdown](/docs/kiosk/llms-full.txt) ## Configure the receiving screen first For example, a lower touchscreen runs your game while the upper display runs Advertising. 1. Assign your external WebApp to its screen and an Advertising playlist to the receiving screen. 2. In **Monitors**, select the **Advertising screen**, then its **Advertising** tab. 3. Open **Playback → Edit playback → WebApp cards**. 4. Under **Allow a WebApp to show cards**, select the assigned source WebApp. Default is **Off**. 5. Select the **Allowed cards** keys and save. 6. In the receiving screen's **Cards** view, configure the fleet default or screen override media for a custom key such as `jackpot`. Wait for the kiosk to download/apply that content before testing. The source WebApp must run on another monitor. Its audience/origin approval and the target's card permission are separate requirements. Standard keys are `loading`, `webapp_error`, `offline`, `needs_service`, and `paused`; they have built-in fallback visuals. Custom keys need configured media. Keys must match `[a-z][a-z0-9_]{1,31}`: 2–32 characters, starting with a lowercase letter, then lowercase letters, digits, or underscores. ## Show and clear a card: showCard / clearCard ```js const bridge = await getKioskBridge(); // Helper from Start here. const result = await bridge.showCard({ key: "jackpot", seconds: 60 }); // result: { screens: [""], seconds: 60, key: "jackpot" } // When this WebApp's card is no longer needed: await bridge.clearCard(); ``` `seconds` defaults to 300 and must be an integer from **1 to 300**. Zero, fractions, larger durations, and extra option fields are rejected. `key` is required. The page cannot pass a target monitor, URL, file, HTML, or image. Native code derives the source and targets from the live browser session and operator configuration. These are local bridge calls, not cloud requests. `clearCard()` releases only this runtime's own card lease. It does not clear an operator's portal override or another runtime's lease. Success returns `{ screens: [...], seconds: 0, key: null }`; an empty screen list can mean there was no owned lease left to clear. ## Pause and resume the playlist: pauseAds / resumeAds ```js await bridge.pauseAds(); // "paused" card for 300 seconds await bridge.pauseAds({ seconds: 120 }); // Explicit bounded duration await bridge.resumeAds(); // Clear this runtime's lease ``` `pauseAds()` is the `paused` card; the receiving screen must allow that key. `resumeAds()` and `clearCard()` clear the same owned lease. There is one active WebApp card lease per target, not a stack of independent pause and card requests. ## Keep a card up during a longer activity Renew before expiry while the activity is still running. This example uses one serialized renewal loop and a single cleanup path: ```js async function holdCardWhile(key, runActivity, onCardError) { const bridge = await getKioskBridge(); let active = true; let timer; let pending = Promise.resolve(); const renew = () => { pending = bridge.showCard({ key, seconds: 300 }) .then(() => { if (active) timer = setTimeout(renew, 60_000); }) .catch(error => { active = false; // Stop renewing; the bounded lease will expire. onCardError(error); }); }; // Fail before starting the activity if the initial request is denied. await bridge.showCard({ key, seconds: 300 }); timer = setTimeout(renew, 60_000); try { return await runActivity(); } finally { active = false; clearTimeout(timer); await pending; // Finish any renewal before clearing. try { await bridge.clearCard(); } catch (error) { onCardError(error); // Do not silently claim that playback resumed. } } } ``` Use one owner/controller in your app so two activities do not clear each other's lease. Handle cancellation by ending `runActivity` and running cleanup. Do not use a rapid retry loop when native access is denied. ## Expiry, priority, and confirmation - The lease uses the kiosk's monotonic clock and expires without renewal. - It does not survive an agent restart or reboot. Configuration, assignment, or runtime revocation can remove it sooner. - Same-origin navigation can retain the browser runtime; stopping renewal leaves the old card until clear or bounded expiry. Do not depend on an unload handler completing an async clear. - An operator's portal card has priority over a WebApp card. Clearing your lease may therefore leave the operator's card visible. - Offline bridge access remains subject to the source WebApp's valid business lease. It does not grant indefinite offline authority. - A resolved Promise confirms that native control accepted the request. It is not proof of physical pixels. Use the target's reported card state or an explicit screenshot when checking actual display. - Card transitions appear as `kiosk:card_shown` and `kiosk:card_cleared` in **Observations → Events**, with the screen, key, and source. General show/clear controls belong to Advertising. The system's own WebApp error/recovery pages are separate and do not grant arbitrary card control over external WebApp screens.