Analytics events for the NRC Games Hub
This document lists every event the NRC Games Hub emits to the parent page. Use it as the source of truth for hooking up Google Analytics (or any other analytics provider).
Scope: this reference applies to the NRC Games Hub only. Other Games Hub variants (e.g. Mediahuis) emit a different set of events and are documented separately.
How to listen
On web, the Games Hub fires every event — analytics and otherwise — as a CustomEvent on window named gamesHubEvent. On non-web platforms (iOS/Android wrapper webviews) most events are dispatched to the native wrapper via window.parent.postMessage(JSON.stringify(detail), '*') instead; the analytics tracking* events are the exception and always use the web CustomEvent transport regardless of platform — see the two notes below. Analytics events use a dedicated tracking* prefix on the top-level type (e.g. trackingModal, trackingGameshub) so consumers filter by type alone:
window.addEventListener('gamesHubEvent', (event) => {
if (!event.detail.type.startsWith('tracking')) return;
const { type, timestamp, data, metadata, timer } = event.detail;
// forward to GA, e.g.:
window.dataLayer.push({
event: `gameshub_${type}`,
type,
timestamp,
data,
metadata,
timer,
});
});
Analytics events always use
dispatchEvent, even inside the iOS/Android webview. They are emitted as CustomEvents onwindowfor the host page to consume, never viapostMessageto the native wrapper. (Other events still postMessage to the wrapper on non-web — that contract is unchanged.)
Lifecycle events are not
tracking*-prefixed.boot,ready, andinitializedfire under their originaltype. If your analytics integration needs lifecycle timing — or needsmetadata.userIdfrom theinitializedevent — listen for thosetypes directly.
The boot event is dispatched directly on window from the entry script before the SDK has loaded. On non-web platforms it is also sent via window.parent.postMessage(...), so consumers listening outside the browser can receive it through the same bridge as other events.
Two parallel event sets, distinguished by type
Every event uses the same payload shape ({ type, timestamp, data, metadata, timer? }); the type value tells consumers which set an event belongs to:
tracking*types (trackingModal,trackingDialog,trackingAction,trackingGameshub) — analytics-only events. CarrygameTitle/variantTitlein theirmetadata. Always dispatched onwindowas aCustomEvent('gamesHubEvent'), including on iOS/Android — never viapostMessageto the native wrapper. Future changes requested by analytics engineers land on thesetypes only.- All other
types — legacy/functional events. Every event the Games Hub has historically emitted still fires under its original name, so functional consumers — the mobile-app wrapper, custom integrations that act onshowFullScreen/visitUrl, etc. — keep working unchanged. Their transport follows the platform default:CustomEventon web,postMessageto the native wrapper on iOS/Android.
If you are wiring up analytics, filter on type.startsWith('tracking') — plus separately handle any lifecycle types you also need (see the allowlist). If you are an existing integration that consumes events for functional purposes, ignore any event whose type starts with tracking — that’s the contract that will keep evolving.
Common envelope
Every event — analytics and otherwise — uses the same shape. Analytics events differ only by their type value (tracking* prefix) and by carrying gameTitle / variantTitle in metadata:
{
type: string; // event name (see tables below); `tracking*` for analytics
timestamp: number; // Date.now() at emission
data?: object; // event-specific payload (see each event)
metadata?: { // see "Game identification" below
userId?: string; // populated from the `initialized` event onward
gameTitle?: string; // analytics events only; added while a puzzle is open, plus on tracking modal events (see "Modal attribution")
variantTitle?: string; // analytics events only; added while a puzzle is open, plus on tracking modal events (see "Modal attribution")
puzzleId?: string; // present on events bubbled from OnePlayer
gameName?: string; // present on events bubbled from OnePlayer
gameType?: string; // present on events bubbled from OnePlayer
gameId?: string; // present on events bubbled from OnePlayer
};
timer?: { ms: number }; // present on game-lifecycle events
}
See Game identification for what each metadata field means, and in particular how gameTitle differs from gameName.
Game identification
Every event emitted while a puzzle is open carries the active game’s title in its metadata, so you can attribute any event — started, completed, share, actionClicked, userCheated, etc. — to a specific game variant without joining against a separate gameData event.
In addition, the modalOpened / modalClosed events carry attribution even when emitted outside an active game: see Modal attribution for the per-modal rules (e.g. startScreen emitted from the overview page carries only gameTitle, and the archive modal updates its attribution when the user switches variant tab).
| Field | Added by | Source |
|---|---|---|
gameTitle |
Games Hub | Hub config GameConfig.title (brand-configured, from the config API) |
variantTitle |
Games Hub | Hub config VariantConfig.title — only set when the game has more than one variant (otherwise omitted to avoid duplicating gameTitle) |
gameType |
OnePlayer | The puzzle renderer (e.g. wordle, sudoku) |
gameName |
OnePlayer | OnePlayer i18n t('<renderer>.name') — generic, variant-agnostic |
puzzleId |
OnePlayer | Unique id of the specific puzzle being played |
gameId |
OnePlayer | The OnePlayer playerId |
gameTitle / variantTitle are injected centrally for every tracking* analytics event by AnalyticsService.withGameContext (apps/games-hub-nrc/src/lib/services/analyticsService.ts). The active title is kept current by the player (apps/games-hub-nrc/src/lib/webcomponents/Player.svelte) on ExternalEventEmitter.gameContext — which AnalyticsService reads — and cleared when no puzzle is open, so events fired before a puzzle opens (boot, ready, initialized, and overview-level navigation) do not carry them. They are added regardless of the externalGameTitle feature flag (that flag only gates the separate gameData event). Non-tracking* events do not carry these fields — listen for the tracking* variant if you need analytics attribution.
gameName vs gameTitle
These are not the same value and frequently differ. For analytics that should match what the user actually sees on the page, use gameTitle (+ variantTitle).
gameName |
gameTitle |
|
|---|---|---|
| Set by | OnePlayer | Games Hub config |
| Source | t('<renderer>.name') in OnePlayer’s i18n bundle |
GameConfig.title from the hub config API (editable in Studio) |
| Variant-aware? | No — every variant of a renderer shares one name | Paired with variantTitle, which distinguishes variants |
| Brand-specific? | No — OnePlayer’s canonical name | Yes — whatever the brand calls the game |
They line up for some games (e.g. both report "Sudoku") but diverge whenever the brand titles a game differently, or for hub-specific variants. Two concrete cases:
- Hub-specific variant: a 5×5 Sudoku is
gameName: "Sudoku"(the renderer issudoku), but the hub classifies it asgameType: "cinco"with its own configuredgameTitle(e.g."Cinco"). - Same renderer, different variants: a 5-letter and a 6-letter Wordle both report
gameName: "Het Woord"; onlyvariantTitletells them apart.
Event allowlist
Every dispatched event’s type must be in OutgoingMessage (apps/games-hub-nrc/src/lib/types/bridge.ts). Anything else is dropped before reaching the parent.
The one exception is boot, which is a special-case loader event dispatched directly by the entry script (apps/games-hub-nrc/src/lib/games-hub-entry.ts:18) before the SDK has loaded. It bypasses the bridge emitter and is therefore not part of the enum.
This is the complete list of events and the tracking* variant (if any) that ships the analytics version:
Legacy type |
Origin | Analytics type + data.type (if umbrella) |
Section |
|---|---|---|---|
boot |
Games Hub | — | Lifecycle |
ready |
Games Hub | — | Lifecycle |
initialized |
Games Hub | — | Lifecycle |
startScreenOpened |
Games Hub | — (tracked via trackingModal with name: 'startScreen') |
Navigation & UI |
modalOpened |
Games Hub | trackingModal + data.type: 'modalOpened' — or trackingDialog + data.type: 'dialogOpened' for dialogs |
Navigation & UI |
modalClosed |
Games Hub | trackingModal + data.type: 'modalClosed' — or trackingDialog + data.type: 'dialogClosed' for dialogs |
Navigation & UI |
showFullScreen |
Games Hub | — | Navigation & UI |
hideFullScreen |
Games Hub | — | Navigation & UI |
gameData |
Games Hub | — | Game metadata |
started |
OnePlayer (bubbled) | trackingGameshub + data.type: 'started' |
Gameplay |
paused |
OnePlayer (bubbled) | trackingGameshub + data.type: 'paused' |
Gameplay |
completed |
OnePlayer (bubbled) | trackingGameshub + data.type: 'completed' |
Gameplay |
share |
OnePlayer (bubbled) / Games Hub | trackingAction + data.type: 'share' |
User actions |
visitUrl |
OnePlayer (bubbled) / Games Hub | — | User actions |
actionClicked |
OnePlayer (bubbled) | trackingAction + data.type: 'actionClicked' |
User actions |
userCheated |
Games Hub | trackingAction + data.type: 'userCheated' |
Solution reveal |
Four umbrella analytics events. Every analytics event lands under one of four outer
types, withdata.typediscriminating the specific event inside:
trackingModal— modal/overlay/menu lifecycle:'modalOpened','modalClosed'(names:startScreen,endScreen,archive,streakInfo,freezeInfo,gameOnboarding,menu). ThestartScreenmodal-open already covers the legacystartScreenOpeneduse case, so there is no separate analytics event for it.trackingDialog— dialog lifecycle:'dialogOpened','dialogClosed'(names:copyConfirmation,feedback,onePlayerPrompt).trackingAction— user-initiated actions:'actionClicked','share','userCheated'.trackingGameshub— bubbled gameplay events from OnePlayer:'started','paused','completed'. The “gameshub” namespace lives on the outertypename, so the innerdata.typeis the bare OnePlayer event name.
OnePlayer events that are NOT bubbled are listed at the bottom under Filtered OnePlayer events. If you need any of those tracked, the Games Hub code needs to be changed first. Listening on
windowwill not see them.
Lifecycle
boot
Fired by the entry script the moment the Games Hub loader runs, before the main bundle is imported. Useful as a “page contains a Games Hub” signal.
- Source:
apps/games-hub-nrc/src/lib/games-hub-entry.ts:18 - Payload:
{ type: 'boot', timestamp: number, metadata: {} }
ready
Fired right after the Games Hub bundle finishes constructing the bridge. The hub is now ready to receive initialize from the host. Sent on all platforms (it is the only event guaranteed to fire before the host has identified itself).
- Source:
apps/games-hub-nrc/src/lib/services/externalEventEmitter.ts:68 - Payload:
{ type: 'ready', timestamp: number, metadata: {} }
initialized
Fired after the host has sent the initialize message and the hub has loaded its config from the API. From this point on, metadata.userId is populated on subsequent events.
- Source:
apps/games-hub-nrc/src/lib/services/bridge.ts:525 - Payload:
{ type: 'initialized', timestamp: number, metadata: { userId: string } }
Gameplay
These three are bubbled from OnePlayer. They carry a timer.ms reflecting elapsed game time at the moment of emission.
started
Fires when the player makes their first move in a puzzle (transition from Ready/Paused -> Playing).
- OnePlayer source:
apps/oneplayer/src/games/event-center.ts:58 - Payload (legacy
gamesHubEvent):{ type: 'started', timestamp: number, data: { ms: number }, metadata: { userId, puzzleId, gameName, gameType, gameId }, timer: { ms: number } } - Analytics variant: also fires as
type: 'trackingGameshub'withdata: { type: 'started', ms }andgameTitle/variantTitleadded tometadata.
paused
Fires when the player pauses (manually or because an overlay opened).
- OnePlayer source:
apps/oneplayer/src/games/event-center.ts:71 - Payload: same shape as
started, withtype: 'paused'. Analytics variant:trackingGameshubwithdata.type: 'paused'.
completed
Fires when the player solves the puzzle. Games Hub augments the payload to always include a milliseconds entry in data, even if OnePlayer didn’t add one.
- OnePlayer source:
apps/oneplayer/src/games/event-center.ts:84 - Augmentation:
apps/games-hub-nrc/src/lib/components/OnePlayer.svelte:218 - Payload (legacy
gamesHubEvent):{ type: 'completed', timestamp: number, data: GameResultData[], // always contains { unit: 'milliseconds', value: <ms> } metadata: { userId, puzzleId, gameName, gameType, gameId }, timer: { ms: number } } - Analytics variant: also fires as
type: 'trackingGameshub'withdata: { type: 'completed', results: GameResultData[] }-style payload andgameTitle/variantTitleadded tometadata. WhereGameResultDatais one of:{ unit: 'solution' | 'rank' | 'points' | 'attempts' | 'milliseconds', value: string | number }
Navigation & UI
startScreenOpened
Fires when the start-screen modal has finished loading and is visible.
- Source:
apps/games-hub-nrc/src/lib/components/modals/StartScreenModal/StartScreenModal.svelte:124 - Payload:
{ type: 'startScreenOpened', timestamp: number, data: { variant: 'singleGame' | 'multiGame' }, metadata: {} }
modalOpened / modalClosed
Fires when any overlay opens or closes inside the Games Hub: fullscreen modals, smaller centered modals, dialogs, or the player menu. The data.name field identifies which overlay, so they can be converted into virtual page views in analytics.
- Sources:
- Fullscreen modals:
apps/games-hub-nrc/src/lib/components/modals/Fullscreen.svelte:16,22 - Modals:
apps/games-hub-nrc/src/lib/components/modals/Modal.svelte:14 - Dialogs:
apps/games-hub-nrc/src/lib/components/modals/Dialog.svelte:21 - Player menu:
apps/games-hub-nrc/src/lib/components/player/Menu.svelte:21
- Fullscreen modals:
- Payload (legacy
gamesHubEvent):{ type: 'modalOpened' | 'modalClosed', timestamp: number, data: { name: ModalName }, metadata: {} } - Analytics variants: dialogs fire as
trackingDialog+data.type: 'dialogOpened'/'dialogClosed'; everything else fires astrackingModal+data.type: 'modalOpened'/'modalClosed'. Both addgameTitle/variantTitletometadataper the rules in “Modal attribution” below.
Modal attribution
The trackingModal and trackingDialog analytics events for game-related overlays carry gameTitle (and, where it makes sense, variantTitle) in their metadata, so each open/close can be attributed to a specific game variant without joining against a separate event. The legacy modalOpened / modalClosed events do not carry these fields — listen on the tracking* variants if you need analytics attribution. Per-modal rules:
data.name |
gameTitle |
variantTitle |
Notes |
|---|---|---|---|
startScreen |
✓ | — | Set from the hub config by the opener (game tile / showStartScreen host message). No variant — the screen presents multiple variants at once. |
endScreen |
✓ | ✓ | Inherited from the active player. |
archive |
✓ | ✓ | Reflects the currently selected variant tab. See “Variant switching” below. |
gameOnboarding |
✓ | ✓ | Inherited from the active player. |
feedback |
✓ | ✓ | Opened from the end screen; inherited from the active player. |
streakInfo |
✓ / — | ✓ / — | Inherits the active player’s attribution. Carries both when opened from the end screen; carries nothing when opened from the overview-page streak indicator (no active game). |
freezeInfo |
✓ / — | ✓ / — | Same as streakInfo — present when an active game’s context is set, absent otherwise. |
menu |
✓ | ✓ | Inherited from the active player. |
copyConfirmation, onePlayerPrompt |
✓ / — | ✓ / — | Inherits the active player’s attribution. |
Internally this is implemented two ways and they don’t differ from the outside:
- Player-driven attribution (most modals above): the active player keeps the
ExternalEventEmitter’sgameContextin sync (apps/games-hub-nrc/src/lib/webcomponents/Player.svelte), whichAnalyticsServicereads when enriching analytics events. Anything that opens while a game is active picks upgameTitle/variantTitleautomatically. - Modal-driven attribution (
startScreen,archive): the opener passes an explicitModalMetato the modal service;AnalyticsService.emitModalOpened/emitDialogOpeneduses that meta and ignores the player’sgameContextfor that single event. Used for modals that can be opened with no active player (start screen) or that own their own variant selection (archive).
Variant switching (archive)
The archive modal lets the user switch between variant tabs (e.g. 5-letter vs 6-letter Wordle). Each switch emits a modalClosed for the previously selected variant followed by a modalOpened for the newly selected one (and the matching trackingModal analytics pair) — so the time spent on each variant remains analytically distinct, even though the modal stays mounted. The archive also emits the same corrective pair the first time its own data arrives, in the case where the initial modalOpened was emitted before the variant was known (e.g. the archive was opened from the start screen, which doesn’t yet know which variant the user will look at).
- Source:
apps/games-hub-nrc/src/lib/components/modals/ArchiveModal/ArchiveModal.svelte(syncMetaFromResponse)
Modal names
name |
Component | Triggered by |
|---|---|---|
startScreen |
StartScreenModal |
Game tile clicked, or showStartScreen received from host |
endScreen |
EndScreenModal |
Puzzle completed |
archive |
ArchiveModal |
User opens the puzzle archive (from start screen, end screen or player menu) |
streakInfo |
StreakInfoModal |
User opens the streak explanation popup |
freezeInfo |
FreezeInfoModal |
User opens the streak-freeze explanation popup, or the freeze-onboarding notification |
gameOnboarding |
PagedModal |
First-time per-game onboarding modal shown when the player loads a new game |
copyConfirmation |
CopiedDialog |
“Copied to clipboard” confirmation after a share |
feedback |
FeedbackDialog |
User opens the feedback form from the end screen |
onePlayerPrompt |
OnePlayerDialog |
OnePlayer requests a prompt (e.g. paused-timer warning, confirm-restart, etc.) |
menu |
Player menu drawer | User taps the menu icon in the player |
Coverage:
gameOnboarding,archive,streakInfo,freezeInfo(all opened viamodalService),copyConfirmation,feedback,onePlayerPrompt(opened viadialogService), andmenu(opened viamenuService) all emit the samemodalOpened/modalClosedevents as the fullscreen modals on the legacy channel. Filter ondata.namein your analytics provider if you need to distinguish “page-like” overlays from transient ones (e.g.copyConfirmationis a short toast-style confirmation).Analytics split: on the
tracking*side, thedialogServicedialogs fire undertrackingDialog(data.type: 'dialogOpened'/'dialogClosed') instead oftrackingModal. The other names in this table fire undertrackingModal.Pairing with
showFullScreen/hideFullScreen: Only the fullscreen variants (startScreen,endScreen) are paired withshowFullScreen/hideFullScreen— the other overlays do not change the fullscreen state.
The full union of modal names is the
ModalNametype inapps/games-hub-nrc/src/lib/types/bridge.ts. If new overlays are added to the Games Hub later, they’ll appear here as additionalnamevalues — the Games Hub team keeps the list in sync via the type system, so any new modal will surface in your analytics automatically with a stable identifier.
showFullScreen / hideFullScreen
Companion events to modalOpened / modalClosed, intended for hosts that need to react to full-screen mode (e.g. native wrappers hiding chrome). Fires from the same Fullscreen modal lifecycle.
- Source:
apps/games-hub-nrc/src/lib/components/modals/Fullscreen.svelte:16,21 - Payload:
{ type: 'showFullScreen' | 'hideFullScreen', timestamp: number, metadata: {} }
Game metadata
gameData
Fires when the active game/variant/date changes, only if the host has opted into the externalGameTitle feature flag. Used by hosts that want to render the title outside the hub.
If you only need the game/variant title for analytics, prefer the
gameTitle/variantTitlefields now present on every in-game event (see Game identification) — they require no feature flag.gameDataremains the dedicated event for hosts that render the title in their own chrome.
- Source:
apps/games-hub-nrc/src/lib/webcomponents/Player.svelte:358 - Feature flag:
config.features.externalGameTitle.enabled - Payload:
{ type: 'gameData', timestamp: number, data: { title: string; variant?: string; date: string; // yyyy-MM-dd relativeDate: string; // e.g. 'today', 'yesterday' }, metadata: {} }
User actions
share
Fires when the user taps the share button. Bubbled from OnePlayer and also emitted directly by the Games Hub when the share happens at the hub level.
- OnePlayer source:
apps/oneplayer/src/games/event-center.ts:186 - Games Hub source:
apps/games-hub-nrc/src/lib/services/bridge.ts:636 - Payload (legacy
gamesHubEvent):{ type: 'share', timestamp: number, data: { text: string, url?: string, title?: string }, // bubbled-from-OnePlayer variants also include puzzleId, gameName, gameType metadata: { userId } } - Analytics variant: also fires as
type: 'trackingAction'withdata: { type: 'share', text, url?, title? }andgameTitle/variantTitleadded tometadata.
visitUrl
Fires when an in-game link should be opened (e.g. an article link in an RSS-driven puzzle, or any external link in the hub).
On the web platform the hub already opens the URL itself; it still emits the event so analytics can record it. On native platforms the host is expected to handle navigation.
- OnePlayer source:
apps/oneplayer/src/games/event-center.ts:208 - Games Hub source:
apps/games-hub-nrc/src/lib/services/bridge.ts:610 - Payload (legacy
gamesHubEvent):{ type: 'visitUrl', timestamp: number, data: { url: string, target?: '_self' | '_blank' }, // bubbled-from-OnePlayer variants also include puzzleId, gameName, gameType metadata: { userId } } - No analytics variant.
visitUrlis purely functional (hosts navigate on it); it has notracking*counterpart.
actionClicked
Fires when the user clicks one of the tracked action buttons inside the player (the player menu and game-specific toolbars). Use this to build engagement funnels for individual in-game interactions — distinct from the lifecycle paused event, which fires for any pause (including ones induced by overlays).
The data.name field identifies which action was clicked, so they can be filtered or grouped in analytics. Debug-only actions (reload, clean) and any other unknown action IDs are deliberately not emitted.
- OnePlayer source:
apps/oneplayer/src/components/ActionButton.svelte(single click chokepoint) →apps/oneplayer/src/games/event-center.ts(emitActionClicked) - Allowlist type:
ActionNameinapps/games-hub-nrc/src/lib/types/bridge.ts - Payload (legacy
gamesHubEvent):{ type: 'actionClicked', timestamp: number, data: { name: ActionName }, metadata: { userId, puzzleId, gameName, gameType, gameId }, timer: { ms: number } } - Analytics variant: also fires as
type: 'trackingAction'withdata: { type: 'actionClicked', name: ActionName }andgameTitle/variantTitleadded tometadata.
Action names
name |
Origin |
|---|---|
restart |
Player menu — restart the current puzzle |
quit |
Player menu — quit the current puzzle |
back |
Player menu — back to the previous screen |
help |
Player menu — open the help/onboarding screen |
feedback |
Player menu — open the feedback dialog |
fullscreen |
Player menu — toggle fullscreen mode |
hint |
Game-specific (crossword, sudoku, chess, ohh1, 0hn0, tentstrees, cijferblok, …) |
undo |
Game-specific (crossword, sudoku, chess, …) |
check |
Crossword — check the current solution |
focus |
Crossword — toggle focus mode (portrait only) |
print |
Crossword, cijferblok, tentstrees, knightmove — print the puzzle |
shuffle |
Connections, spellwise — shuffle the tiles/letters |
showWords |
Wordfind — toggle the word list (the “Open” button) |
The full union of action names is the
ActionNametype inapps/games-hub-nrc/src/lib/types/bridge.ts, kept in sync withOnePlayerActionNameinapps/oneplayer/src/player/external-events.ts. If new tracked actions are added later, they’ll appear here as additionalnamevalues — the Games Hub team keeps the list in sync via the type system, so any new tracked action will surface in your analytics automatically with a stable identifier.
Solution reveal
userCheated
Fires when the user chooses to reveal the puzzle’s solution from the menu. The Games Hub also uses this event internally to switch to a different end screen reflecting that the puzzle was revealed rather than solved. The timer.ms reflects elapsed play time at reveal.
- Source:
apps/games-hub-nrc/src/lib/webcomponents/Player.svelte:495 - Payload (legacy
gamesHubEvent):{ type: 'userCheated', timestamp: number, metadata: { userId }, timer: { ms: number } } - Analytics variant: also fires as
type: 'trackingAction'withdata: { type: 'userCheated' }andgameTitle/variantTitleadded tometadata.
Filtered OnePlayer events
The following events are emitted by OnePlayer but never reach the page because they are not in the OutgoingMessage allowlist (apps/games-hub-nrc/src/lib/types/bridge.ts:86) and/or are explicitly intercepted in OnePlayer.svelte. They are listed here so you know what data is theoretically available but would require a code change in the hub before it can be tracked.
| OnePlayer event | Why it’s not visible | OnePlayer source (event-center.ts unless noted) |
|---|---|---|
move |
Not in OutgoingMessage allowlist |
:280 |
progress |
Not in OutgoingMessage allowlist |
:269 |
closed |
Not in OutgoingMessage allowlist |
:121 |
aborted |
Not in OutgoingMessage allowlist |
:134 |
requireLogin |
Not in OutgoingMessage allowlist |
:160 |
requireSubscription |
Not in OutgoingMessage allowlist |
:173 |
adBreak |
Not in OutgoingMessage allowlist |
:147 |
track |
Not in OutgoingMessage allowlist |
:197 |
endScreenData |
Consumed internally by Games Hub (drives the end-screen modal) | :109 |
timerUpdate |
Explicit early return in OnePlayer.svelte:299 |
:340 |
promptOpened |
Consumed internally (opens a hub dialog) | :297 |
promptClosed |
Consumed internally (closes hub dialog) | :312 |
hint / hintCleared |
Consumed internally (rendered as toast or banner) | :219, :230 |
gameWarning / gameWarningCleared |
Consumed internally (rendered as toast) | :241, :252 |
userThemePreference |
Not in OutgoingMessage allowlist |
:326 |
onStateChange |
Not in OutgoingMessage allowlist |
(one-player-element.ts:433) |
Quick reference: event flow
┌──────────────┐ ┌────────────┐ ┌──────────────────────────┐
│ OnePlayer │ events │ Games Hub │ filter │ window dispatch / │
│ (puzzle UI) │ ─────▶ │ Bridge │ ─────▶ │ postMessage to host │
└──────────────┘ └────────────┘ └──────────────────────────┘
│ │
│ also emits its own │
│ lifecycle / UI events │
└──────────────────────────────┘
The OnePlayer.svelte listener in the hub processes every OnePlayer event, then forwards it to both bridge.externalEventEmitter.sendOnePlayerEvent(event) (legacy path — checks the OutgoingMessage allowlist and ships under the original event name) and bridge.analyticsService.sendOnePlayerEvent(event) (analytics path — checks the TRACKED_PLAYER_EVENTS mapping in AnalyticsService and ships under the mapped tracking* umbrella with the original name in data.type). Just before dispatching, AnalyticsService enriches the metadata with the active gameTitle / variantTitle via withGameContext (see Game identification).
- Inbound listener:
apps/games-hub-nrc/src/lib/components/OnePlayer.svelte - Outbound filters:
apps/games-hub-nrc/src/lib/services/externalEventEmitter.ts(legacy),apps/games-hub-nrc/src/lib/services/analyticsService.ts(analytics) - Game-title injection:
apps/games-hub-nrc/src/lib/services/analyticsService.ts