Skip to content

PlacementController

Framework-agnostic placement lifecycle controller.

Encapsulates the same register → decide → impression-track → interact flow that the React usePlacement() hook provides, but without any framework dependency.

Subscribe to state changes with onChange and read the current state with state, or just await load() for a one-shot pattern.

const ctrl = new PlacementController(sdk, { surfaceSlot: { id: 'upsell_banner' } });
ctrl.onChange(() => updateUI(ctrl.state));
await ctrl.load();

get content(): RevTurbineDecisionContent | null

Current resolved content, or null if no decision has been loaded.

RevTurbineDecisionContent | null


get decision(): RevTurbinePlacementDecision | null

Current decision, or null if not yet loaded.

RevTurbinePlacementDecision | null


get placementId(): string

Registered placement ID, or empty string before first load().

string


get state(): PlacementControllerState

Current state snapshot.

PlacementControllerState


get visible(): boolean

Convenience: true when the current decision says the placement is visible.

boolean

ctaClick(ctaTarget?): Promise<void>

Record a CTA click interaction.

string

Promise<void>


ctaComplete(ctaTarget?): Promise<void>

Record a CTA completion interaction and hide the placement.

string

Promise<void>


dismiss(cooldownMs?): Promise<void>

Record a dismiss interaction and hide the placement.

number = ...

Promise<void>


load(): Promise<RevTurbinePlacementDecision | null>

Register the placement (if needed) and fetch a decision.

When autoTrackImpression is true (default), a visible decision automatically records an impression event.

Promise<RevTurbinePlacementDecision | null>


markRendered(): void

Called when the placement’s visual root renders (plan 144 TASK-11). Emits placement_rendered once, and under placementExposure: 'render' credits the presentation now. Idempotent.

void


markVisible(basis?): void

Called by the viewport-exposure substrate when the placement’s visual root first enters the viewport — or immediately with 'render_fallback' when IntersectionObserver is unavailable (plan 144 TASK-9/11 / AC-9, AC-10). Emits placement_exposed once with the basis, and under placementExposure: 'viewport' a viewport exposure credits the presentation here (Q-8). Idempotent.

ExposureBasis = 'viewport'

how exposure was established; defaults to 'viewport'

void


onChange(listener): () => void

Subscribe to state changes. Returns an unsubscribe function.

ChangeListener

() => void

const unsub = ctrl.onChange(() => console.log(ctrl.state));
// later...
unsub();

refresh(): Promise<RevTurbinePlacementDecision | null>

Re-fetch the placement decision (clears impression + render + exposure tracking).

Promise<RevTurbinePlacementDecision | null>


remindMeLater(seconds?): Promise<void>

Alias for snooze.

number = 3600

Promise<void>


reset(): void

Reset the controller state (clears decision, placement ID, etc.).

void


snooze(seconds?): Promise<void>

Record a snooze/remind-me-later interaction and hide the placement.

number = 3600

Promise<void>