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:clickis cancelable (event.preventDefault(),detail.preventDefault(), returningfalsefrom a listener or fromonClickinQiAds.init/window.qiadsConfig): the host app opensdetail.urlin 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 queueQiAds.cmdbesidewindow.qiads. - Test mode (
data-test,?qiads_test=1,QiAds.init({test: true})): renders normally, beacons carrytest:1and click linkst=1, nothing is counted. - Debug mode (
data-debug,?qiads_debug=1,QiAds.init({debug: true})):console.infodiagnostics, outlined ads, and the server'sX-QiAds-Resultverdict for every beacon. - Slot attributes:
data-aspect(reserve the box while loading, with apadding-topfallback where CSSaspect-ratiois missing),data-collapse(autoornever),data-page(a logical page name). Prefixed aliases for every slot attribute exceptdata-qiadsanddata-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 andmb:1when viewability can be measured) as aggregate counters; the SDK honours the server'smeasuredirective, 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-roledescriptioncarousel/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.jsis 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 withoutclick_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 withfetchandkeepaliveso the response header can be read; otherwisesendBeaconas 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:hiddenoropacity:0never counts; the fallback poll withoutIntersectionObserverruns every 100 ms (was 250 ms);QiAds.setVisible(false)stops the timers. A render carriesmb:1(measured) only whereIntersectionObserveris 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=1whennavigator.webdriveris true, as the beacons carrywd: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(wasku); the «Ad» badge is at least 11 px. - Build: the SDK is written as modules in
sdk/srcand bundled (ES5, minified, ASCII).
Deprecated
data-popupon the script tag: still honoured, but popups belong at natural transition points, opened by the host withQiAds.popup/qiads.push(["popup", …])— never on app open.creatives[].booking_idin the serve answer: unused since 1.3; removed in 2.0.
Fixed
- Clicks from a script (
el.click(),isTrustedfalse) 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-visibleandMediaQueryList.addEventListenerare 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-langon 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, anddata-qiads-langwritten 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">withdata-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 withsendBeacon;QiAds.init,render,popup,close,destroyand thewindow.qiadscommand queue.