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

The Games Hub fires every public event as a CustomEvent on window named gamesHubEvent. The actual payload sits on event.detail.

window.addEventListener('gamesHubEvent', (event) => {
  const { type, timestamp, data, metadata, timer } = event.detail;
  // forward to GA, e.g.:
  window.dataLayer.push({ event: `gameshub_${type}`, ...event.detail });
});

On non-web platforms (iOS/Android wrapper apps, where config.platform !== 'web') events are also sent via window.parent.postMessage(JSON.stringify(detail), '*'). For browser-based analytics you only need the gamesHubEvent listener.

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.

Common envelope

Every event has the shape:

{
  type: string;              // event name (see tables below)
  timestamp: number;         // Date.now() at emission
  data?: object;             // event-specific payload (see each event)
  metadata?: {               // varies by event; commonly { userId } or {}
    userId?: string;
  };
  timer?: { ms: number };    // present on game-lifecycle events
}

Event allowlist

Once the SDK is initialized, the bridge emitter filters every outgoing event against the OutgoingMessage enum (apps/games-hub-nrc/src/lib/types/bridge.ts:86). Anything not in that enum 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 OutgoingMessage.

This is the complete list of events you can listen for:

type Origin Section
boot Games Hub Lifecycle
ready Games Hub Lifecycle
initialized Games Hub Lifecycle
startScreenOpened Games Hub Navigation & UI
modalOpened Games Hub Navigation & UI
modalClosed Games Hub Navigation & UI
showFullScreen Games Hub Navigation & UI
hideFullScreen Games Hub Navigation & UI
gameData Games Hub Game metadata
started OnePlayer (bubbled) Gameplay
paused OnePlayer (bubbled) Gameplay
completed OnePlayer (bubbled) Gameplay
share OnePlayer (bubbled) User actions
visitUrl OnePlayer (bubbled) / Games Hub User actions
actionClicked OnePlayer (bubbled) User actions
userCheated Games Hub Solution reveal

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 window will 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:43
  • 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:
    {
      type: 'started',
      timestamp: number,
      data: { ms: number },
      metadata: { userId, puzzleId, gameName, gameType, ... },
      timer: { ms: number }
    }
    

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, with 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:
    {
      type: 'completed',
      timestamp: number,
      data: GameResultData[],   // always contains { unit: 'milliseconds', value: <ms> }
      metadata: { userId, puzzleId, gameName, gameType, ... },
      timer: { ms: number }
    }
    

    Where GameResultData is one of:

    { unit: 'solution'    | 'rank' | 'points' | 'attempts' | 'milliseconds',
      value: string | number }
    

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
  • Payload:
    {
      type: 'modalOpened' | 'modalClosed',
      timestamp: number,
      data: { name: ModalName },
      metadata: {}
    }
    
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 via modalService), copyConfirmation, feedback, onePlayerPrompt (opened via dialogService), and menu (opened via menuService) all emit the same modalOpened / modalClosed events as the fullscreen modals. Filter on data.name in your analytics provider if you need to distinguish “page-like” overlays from transient ones (e.g. copyConfirmation is a short toast-style confirmation).

Pairing with showFullScreen / hideFullScreen: Only the fullscreen variants (startScreen, endScreen) are paired with showFullScreen / hideFullScreen — the other overlays do not change the fullscreen state.

The full union of modal names is the ModalName type in apps/games-hub-nrc/src/lib/types/bridge.ts. If new overlays are added to the Games Hub later, they’ll appear here as additional name values — 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.

  • Source: apps/games-hub-nrc/src/lib/webcomponents/Player.svelte:343
  • 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:631
  • Payload:
    {
      type: 'share',
      timestamp: number,
      data: { text: string },
      metadata: { userId: string }
    }
    

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:606
  • Payload:
    {
      type: 'visitUrl',
      timestamp: number,
      data: { url: string, target?: '_self' | '_blank' },
      metadata: { userId: string }
    }
    

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: ActionName in apps/games-hub-nrc/src/lib/types/bridge.ts
  • Payload:
    {
      type: 'actionClicked',
      timestamp: number,
      data: { name: ActionName },
      metadata: { userId, puzzleId, gameName, gameType, ... },
      timer: { ms: number }
    }
    

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

The full union of action names is the ActionName type in apps/games-hub-nrc/src/lib/types/bridge.ts, kept in sync with OnePlayerActionName in apps/oneplayer/src/player/external-events.ts. If new tracked actions are added later, they’ll appear here as additional name values — 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:479
  • Payload:
    {
      type: 'userCheated',
      timestamp: number,
      metadata: { userId: string },
      timer: { ms: number }
    }
    

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 calls bridge.externalEventEmitter.sendOnePlayerEvent(event) which checks messageIsAllowed(event.name) against OutgoingMessage — this is the gate that drops events not in the allowlist.

  • Inbound listener: apps/games-hub-nrc/src/lib/components/OnePlayer.svelte:218
  • Outbound filter: apps/games-hub-nrc/src/lib/services/externalEventEmitter.ts:190,228