QiAds
Developer guide

Changelog — QiAds Web SDK

Live release: 1.3.1

Every change to the embed tag qiads.js and to what it agrees on with the server (attributes, events, methods, beacon fields). Versions follow Semantic Versioning: only a major release removes or changes, a minor release adds, a patch fixes. /qiads.js follows the latest 1.x; every pinned /sdk/<version>/qiads.js stays published. The format follows Keep a Changelog.

[1.3.1] — 2026-10-12

Fixed

  • Rotating banner: pressing play with a mouse now resumes the rotation as soon as the pointer leaves the banner. The button's icon is changed in place instead of being replaced under the pointer, which in Chromium cost the banner its pointerleave, so it stayed paused as if still hovered.

[1.3.0] — 2026-10-09

The measurement release: the tag now reports what the IAB/MRC guidelines count (ad requests and fill, rendered and viewable impressions, server-side clicks), gives host apps a click and lifecycle contract for WebViews, and ships as an immutable, SRI-pinnable file. Every 1.2.1 snippet keeps working unchanged.

Added

  • Events. QiAds.on(type, fn) / QiAds.off(type, fn) and bubbling DOM events on the slot: qiads:request, qiads:render, qiads:empty, qiads:error, qiads:impression, qiads:click, qiads:close. One detail object for both: {type, placement, creative: {id, booking_id, lang}, impressionId, url, landing, slot, reason}.
  • Click handoff. qiads:click is cancelable (event.preventDefault(), detail.preventDefault(), returning false from a listener or from onClick in QiAds.init / window.qiadsConfig): the host app opens detail.url in Custom Tabs or SFSafariViewController and the click is still counted.
  • Methods. QiAds.refresh(target?) (a new slot instance: new impression ids, fresh ads), QiAds.setVisible(visible) (the host's own visibility, e.g. a native screen covering the WebView), QiAds.getSlots(), QiAds.getVersion(), and the GPT-style queue QiAds.cmd beside window.qiads.
  • Test mode (data-test, ?qiads_test=1, QiAds.init({test: true})): renders normally, beacons carry test:1 and click links t=1, nothing is counted.
  • Debug mode (data-debug, ?qiads_debug=1, QiAds.init({debug: true})): console.info diagnostics, outlined ads, and the server's X-QiAds-Result verdict for every beacon.
  • Slot attributes: data-aspect (reserve the box while loading, with a padding-top fallback where CSS aspect-ratio is missing), data-collapse (auto or never), data-page (a logical page name). Prefixed aliases for every slot attribute except data-qiads and data-lang: data-qiads-format, data-qiads-limit, data-qiads-rotate, data-qiads-target, data-qiads-fallback, data-qiads-dir, data-qiads-cap, data-qiads-aspect, data-qiads-collapse, data-qiads-page.
  • Script attributes: data-test, data-debug, data-observe (fill [data-qiads] elements added later), data-page.
  • Measurement: ad requests (request, with the number of creatives returned) and rendered impressions (render, with the creative's size and mb:1 when viewability can be measured) as aggregate counters; the SDK honours the server's measure directive, so they can be paused without a new release.
  • Rotating banner: a visible pause/play button (WCAG 2.2.2), touch swipe that follows the language's direction, carousel semantics (aria-roledescription carousel/slide, live region off while rotating), targets of at least 24×24 px.
  • Delivery: /sdk/1.3.0/qiads.js (immutable, cached a year, SRI-pinnable, with a source map) next to the evergreen /qiads.js (5 minutes, revalidated in the background); /sdk/1.2.1/qiads.js is the previous release, unchanged, for rollback. Every request carries the SDK version (sv).

Changed

  • Clicks are counted by the server. An ad's link is now the signed click redirect click_href (/api/ads/click/<token>): the click is counted as the user is redirected — whichever way the link is opened (new tab, long-press, host app) — and only real redirects count. Creatives without click_href (an older server) keep the 1.2.1 beacon click.
  • Beacon v2: one batched POST /api/ads/track ({v:2, sv, site, page, ref, pv, now, events:[…]}) flushed within a second, on page hide, split at 3.5 KB and 20 events. Debug and test mode send it with fetch and keepalive so the response header can be read; otherwise sendBeacon as before.
  • Impression ids: one impression = one slot instance × one creative. Rotation coming back to the same creative counts nothing new; a refresh (SPA navigation, QiAds.refresh, 30 minutes hidden, a new Baghdad day) is a new impression — returning to a long-lived home screen now counts again.
  • Viewability hardening: counting starts after the slide's fade-in; a zero-area slot, visibility:hidden or opacity:0 never counts; the fallback poll without IntersectionObserver runs every 100 ms (was 250 ms); QiAds.setVisible(false) stops the timers. A render carries mb:1 (measured) only where IntersectionObserver is available, or the poll runs in the top-level window: inside a cross-origin frame the poll cannot see the visitor's screen.
  • Automation on clicks: a click link carries wd=1 when navigator.webdriver is true, as the beacons carry wd:1, so the redirect files it as automation.
  • Data minimisation: the page is reported as origin + path (no query string, no fragment) or a logical data-page; the referrer as its origin only.
  • Images: the visible slide loads eagerly with high priority; the others lazily.
  • Pausing: a touch tap no longer freezes rotation for the rest of the visit; keyboard focus (:focus-visible) and a mouse hover still pause it. With "reduce motion" the banner keeps rotating, without the cross-fade.
  • Kurdish content is tagged ckb (was ku); the «Ad» badge is at least 11 px.
  • Build: the SDK is written as modules in sdk/src and bundled (ES5, minified, ASCII).

Deprecated

  • data-popup on the script tag: still honoured, but popups belong at natural transition points, opened by the host with QiAds.popup / qiads.push(["popup", …]) — never on app open.
  • creatives[].booking_id in the serve answer: unused since 1.3; removed in 2.0.

Fixed

  • Clicks from a script (el.click(), isTrusted false) no longer navigate or count.
  • A WebView resumed hours later no longer files yesterday's queued events under today (events older than 15 minutes when sent are dropped).
  • The space below a slot no longer jumps when the ad arrives (data-aspect, style="aspect-ratio:…").
  • :focus-visible and MediaQueryList.addEventListener are feature-tested, so old WebViews (iOS before 15.4, WebView before 86) no longer throw.

Security

  • The click destination always comes from the database, never from the link: the redirect cannot be used as an open redirect, answers with Referrer-Policy: strict-origin-when-cross-origin, and stops working for cancelled or rejected bookings.
  • In-app guidance: never data-target="_self" (the advertiser's page would load next to the app's JavaScript bridge); hand clicks to the app instead.

[1.2.1] — 2026-10-08

Changed

  • A banner rotates through up to 7 creatives by default (was 3), the 7 campaigns a day the SuperQi slider sells. (A behaviour change released as a patch; under the policy above it would have been a minor release.)

[1.2.0] — 2026-09-23

Added

  • Languages: data-lang on the script tag or the slot (ar, en, ku, else <html lang>, else Arabic), with the fallback rule (a campaign without a translation shows its Arabic creative), data-dir, and data-qiads-lang written with the language actually shown.

[1.1.0] — 2026-09-23

Changed

  • Each ad renders in Shadow DOM; fair rotation between advertisers sharing a placement; impressions only after the image loads, in a visible tab.

Fixed

  • Beacons are sent as text/plain: the JSON type needed a CORS preflight the wildcard answer could not pass, and Chrome dropped partner beacons silently.
  • The popup frequency cap works without DOM storage (Android WebView); leak, timeout, old-browser and prototype-pollution fixes.

[1.0.0] — 2026-09-21

Added

  • First release: <script src="/qiads.js"> with data-site, data-host, data-popup, data-cap, data-rotate; [data-qiads] slots in three formats (banner, inline, popup); viewable impressions (50% for one second) and clicks sent with sendBeacon; QiAds.init, render, popup, close, destroy and the window.qiads command queue.