Skip to content

Tercept Analytics Adapter: single request per auction, concurrent auction isolation & payload optimisation - #1

Closed
mdusmanalvi wants to merge 2 commits into
masterfrom
tercept-analytics-refactor
Closed

mdusmanalvi wants to merge 2 commits into
masterfrom
tercept-analytics-refactor

Conversation

@mdusmanalvi

Copy link
Copy Markdown
Owner

Type of change

  • Bugfix
  • Feature
  • New bidder adapter
  • Updated bidder adapter
  • Updated analytics adapter
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Does this change affect user-facing APIs or examples documented on http://prebid.org?
  • Other

Description of change

Refactored the Tercept Analytics Adapter to fix a data-loss bug with concurrent auctions, reduce request volume from ~4 per auction to 1, and trim payload size.

  1. Concurrent auction isolation (bug fix):

    • Replaced shared events object with a Map keyed by auctionId
    • Previously, a new AUCTION_INIT would reset shared state, silently dropping data from any in-flight auction; each auction is now fully isolated
  2. Single request per auction:

    • All bid events (BID_WON, AD_RENDER_SUCCEEDED, AD_RENDER_FAILED, BIDDER_ERROR) are now accumulated into the bids array and flushed in one POST ~1.5s after AUCTION_END
    • Reduces requests from ~4 per auction to 1, eliminating the need to join separate payloads server-side
  3. Page-exit reliability via sendBeacon:

    • visibilitychange handler flushes pending auctions using navigator.sendBeacon instead of ajax, guaranteeing delivery when the user navigates away before the 1.5s timer fires
  4. Payload size reduction:

    • Removed ad (full creative markup, 10–50 KB per bid), native payload, and adUrl from bid objects
    • auctionInit.bidderRequests trimmed to first entry only — device and site data is identical across all bidder requests
  5. Page-load detection:

    • Added is_pl boolean field: true on the first bid of the first auction per page load, false on all others
  6. Resilience and cleanup:

    • Added try/catch around track() so any unexpected error does not disrupt the publisher page
    • Implemented disableAnalytics to cancel pending timers and clear maps on teardown (prevents state leaking across SPA navigations and test runs)

Other information

Metric Before After
Requests per auction ~4 1
Concurrent auction data loss Yes Fixed
Creative markup in payload Yes (10–50 KB/bid) Removed
Page-exit delivery ajax (cancellable) sendBeacon

- Replace shared events object with per-auctionId Map to fix data loss
  when multiple auctions run concurrently
- Accumulate BID_WON, AD_RENDER_SUCCEEDED, AD_RENDER_FAILED and
  BIDDER_ERROR into the bids array; flush once 1.5s after AUCTION_END
- Use navigator.sendBeacon on visibilitychange so data survives page exit
- Add disableAnalytics to clear pending timers and maps on teardown
- Add try/catch around track() to prevent adapter errors disrupting page
- Remove ad, native and adUrl fields from bid payloads to reduce size
- Limit auctionInit.bidderRequests to first entry (device/site data
  is identical across all bidder requests)
- Add is_pl flag (true on first bid of first auction per page load)
Replace tests that assumed multiple requests per auction and separate
top-level keys (bidWon, adRenderSucceeded, etc.) with tests matching
the new single-request-per-auction design. Covers:

- Request timing: no send before 1.5s timer, one request per auction
- Payload structure: top-level shape, bidderRequests trimmed to first,
  host/path/search attached at send time, ad/native/adUrl absent
- All event handlers: BID_REQUESTED, BID_RESPONSE, BID_TIMEOUT, NO_BID,
  BID_WON, AD_RENDER_SUCCEEDED, AD_RENDER_FAILED, BIDDER_ERROR
- is_pl flag: true on first bid of first auction only, reset on disable
- Concurrent auction isolation: separate bids and auctionInit per id
- visibilitychange flush via sendBeacon
- disableAnalytics: cancels timers, clears maps, resets firstSent
- Error resilience: unknown auctionIds and undefined args do not throw
@github-actions

github-actions Bot commented Apr 3, 2026

Copy link
Copy Markdown

Tread carefully! This PR adds 81 linter errors (possibly disabled through directives):

  • modules/terceptAnalyticsAdapter.js (+81 errors)

@mdusmanalvi mdusmanalvi closed this Apr 3, 2026
mdusmanalvi pushed a commit that referenced this pull request Sep 15, 2026
…15379)

* New RTD Module: Unicorn Viewability (slot position + viewability signals) (#1)

* New RTD Module: Unicorn Viewability (slot position + viewability signals)

- Add unicornViewabilityRtdProvider: measure each ad slot's on-screen
  position and visibility ratio on the client, inject into
  ortb2Imp.ext.data.unicorn and the standard ortb2Imp.banner.pos.
- unicornBidAdapter: forward the measured signal to the wire as
  imp.ext.unicorn (flat vendor key), and pass through imp.banner.pos.
- Add integration example page (integrationExamples/gpt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Unicorn Viewability RTD Module: add docs and unit tests

- modules/unicornViewabilityRtdProvider.md: module overview, integration,
  configuration, slot element resolution, injected-field table.
- test/spec/modules/unicornViewabilityRtdProvider_spec.js: cover init,
  getBidRequestData injection (ver 1 / banner.pos), missing-slot and
  empty-adUnits cases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Unicorn RTD Module: rename to unicornRtdProvider, address review feedback

- Rename unicornViewabilityRtdProvider -> unicornRtdProvider (module, md,
  spec); submodule name 'unicornViewability' -> 'unicorn'; export
  unicornViewabilitySubmodule -> unicornSubmodule.
- unicornBidAdapter: rename local var unicornViewability -> unicornSignal.
- Drop the integration example page from the PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Unicorn RTD Module: name the signal namespace imp.ext.adslot

Rename the injected/wire key from the vendor name `unicorn` to `adslot`,
reflecting that it carries the ad slot's position/geometry (x/y/w/h/fixed)
plus visibility ratio. Internal ortb2Imp.ext.data.adslot, wire imp.ext.adslot.
Also rename the adapter local var unicornSignal -> adslotSignal, and update
the module doc and unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Unicorn RTD Module: map fixed/sticky slots to banner.pos=2 (AdCOM Locked)

Per AdCOM 1.0 Placement Positions (used by prebid/openrtb v20, which alicorn
follows), position 2 = Locked (fixed position). Emit banner.pos=2 for
fixed/sticky slots instead of only 1/3; keep the adslot.fixed flag as the raw
signal. Update the module doc and add a unit test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>

* Unicorn RTD Provider: address PR review feedback

- Source the visibility ratio from Prebid's shared `percentInView` helper and
  drop the module's own IntersectionObserver. This removes the DOM-id-keyed map
  and observer lifecycle flagged in review (prototype pollution / observer
  leak) by removal rather than patching.
- Read getBoundingClientRect directly for geometry so the shared cached helper
  can't return a prior-auction rectangle here.
- Register unicornRtdProvider under rtdModule in modules/.submodules.json so
  `--modules=unicornRtdProvider` pulls in rtdModule.
- Docs: consistent "RTD" capitalization; note the visibility-ratio source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Drop unicornRtdProvider, measure adslot signals inline in the bid adapter

Per review feedback (patmmccann, dgirardi), moves slot position/geometry/
viewability measurement out of the RTD module and into unicornBidAdapter's
buildRequests. This resolves the structural issues that came from writing to
shared FPD (ortb2Imp), which affected every bidder on the page, not just
Unicorn:

- ext.data.adslot collided with an established key (a GAM slot path string
  read by beopBidAdapter/rubiconBidAdapter) — gone, since imp.ext.unicorn is
  now built only inside this adapter's own OpenRTB payload.
- banner.pos was written unconditionally regardless of mediaType, corrupting
  ortb2Imp for video/native-only ad units — gone, same reason.
- The RTD timeout/hidden-tab/stale-ad-unit-mutation issues (RTD ignored the
  timeout argument, requestAnimationFrame stalls in background tabs, and
  ad unit objects were mutated in place) no longer apply: measurement is a
  single synchronous read per bid request, the same pattern other adapters
  use with percentInView (33across, marsmedia, oms, ...).

Also fixes, in the new inline implementation:
- banner.pos now compares document-relative y (rect + scroll) against
  viewport height, instead of viewport-relative rect.top, so it no longer
  flips after the user scrolls and a refresh auction runs.
- Drops the AdCOM "Locked" pos=2 value — OpenRTB 2.5 marks 2 DEPRECATED, and
  it would have reached bidders that relay OpenRTB. Fixed/sticky is now only
  carried in ext.unicorn's own fixed/sticky flags (kept as two flags, since
  `position: sticky` alone doesn't mean "currently stuck").
- Detects fixed/sticky by walking ancestors, not just the slot element,
  since anchor/sticky ad units are usually a fixed/sticky wrapper around a
  statically positioned ad div.
- percentInView/getViewability are now called with the ad unit's size, so an
  unrendered (0x0) GPT slot still gets a real ratio/w/h via the size
  override, instead of ratio:0, h:0.
- getViewportOffset compensates for measuring from inside a friendly iframe,
  consistent with how percentInView itself handles that case.
- A publisher-declared ortb2Imp.banner.pos, if present, is used instead of
  the measured value.

Removes modules/unicornRtdProvider.{js,md}, its spec, and the
modules/.submodules.json entry. Documents the new imp.banner.pos/imp.ext.unicorn
behavior in modules/unicornBidAdapter.md, with adapter spec coverage for the
fold-vs-scroll fix, the size-override fix, fixed vs sticky detection, GPT slot
mapping, the ortb2Imp.banner.pos override, and the no-matching-element case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Patrick McCann <patmmccann@gmail.com>
Co-authored-by: Demetrio Girardi <dgirardi@prebid.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant