WikifitaGitHub live67e8de5
projeto · hacks/kickbacks-banner

Kickbacks.ai — Banner Surface

Status bar ad banner with billing, click tracking, gradient and proactive token refresh

Baixar raw

Kickbacks.ai — Banner Surface Implementation

Status bar ad banner independent of the overlay, with its own billing, click tracking, color gradient and proactive token refresh. Tags: kickbacks banner statusbar billing performance


Architecture

The banner is an independent surface from Claude Code's overlay. Each has its own slot in the ad rotation queue, its own sessionNonce, and separate billing — no double billing.

Portfolio refresh (60s) → adRotation queue
  ├── [rotationIdx]     → overlay (activeAd)
  └── [(idx+1) % N]     → banner (getBannerAd)

Two Items in the Status Bar

adItem     = Right → Left, priority -100  // banner, left side
earnings   = Right, priority 999           // "Kickbacks ($X today)"
  • AdItem was moved to the left (Left, priority -100) because it's more stable than the right (less extension competition)
  • EarningsItem stays on the right (VS Code ecosystem default)

Billing (Window Focus Only)

Drastically simplified compared to the overlay. No idle decay, no editor events, no countdown:

private startTicking(): void {
    this.tickInterval = setInterval(() => {
        const billable = this._windowFocused;
        this.adItem.color = billable ? this._adColor(this._bannerVisibleMs) : AD_PAUSED;
        this.adItem.show();
        if (billable && this._onTick) this._onTick(5000);
    }, 5000);
}
StateColorBilling
Window focusedGradient (gray→amber)✅ view_tick every 5s
Alt+Tab / lost focusDark gray (#888)⏸️ Paused

Why this matters for anti-fraud: Cutting billing on alt+tab creates natural pauses in ticks, producing a high CV (Coefficient of Variation) (~0.48). Low CV (close to 0) = bot pattern = 🚩 Fraud detection.


Color Gradient (Gray → White → Amber)

The banner text color evolves as the accumulated visibleMs approaches the 15s threshold (anti-misclick):

visibleMsColorMeaning
0s#888 (dark gray)Just appeared
5sBeige (#ccc8aa)Accumulating
10sLight gold (#f0e8bb)Almost there
12s#f0ecd0 (warm white)"Ready to click"
15s+#dba110 (amber)✅ Threshold reached, click enabled

Implemented via _adColor() with static COLOR_STOPS (4 stops, linear interpolation between segments).


Marquee (Continuous Scroll)

  • Interval: 500ms (2 steps/s) — balance between lively and performance
  • Always running — all text scrolls regardless of length
  • No .show() on tick — the billing tick (5s) already maintains visibility. Removing .show() from marquee reduced IPC by ~33%
  • Total IPC: ~4 calls/s (vs 7.5/s before optimization)
Before: 3 props × 2.5/s = 7.5 IPC calls/s
After: 2 props × 2/s = 4 IPC calls/s

Click Tracking

Complete flow (mirroring the overlay):

Click on banner
  → canServeAds() — serving gate check
  → sb.bannerVisibleMs < 15,000ms? → anti-misclick (early click discarded)
  → metrics.send("click", { adId, campaignId, sessionToken, surface:"banner", visibleMs, eventUuid })
  → vscode.env.openExternal(url) — opens advertiser URL
  → sb.resetAdSession() — visibleMs = 0, color reverts to gray
  → refreshAdNow() — forces portfolio refresh + banner re-read

Anti-Misclick

Same threshold as the overlay: 15s of accumulated visibleMs. Clicking before the banner turns amber → event discarded with dlog("banner.click.early").


Proactive Token Refresh

Before: token expired silently → metrics.send() received 403 for HOURS until the next portfolio refresh (60s).

Now: the JWT is decoded (without verifying signature) to read exp (expiry timestamp):

msUntilExpiry(): number | null {
    const b64 = token.split(".")[1]
        .replace(/-/g, "+").replace(/_/g, "/");
    // ... decode base64url → JSON
    return payload.exp ? (payload.exp * 1000 - Date.now()) : null;
}

Timer every 2 minutes checks if the token expires in less than 5 min. If so → proactive auth.refresh(). Zero 403 window.


Logs and Debug

All banner events have dlog in ~/.vibe-ads/debug.log:

EventWhen
banner.fetchEvery 30s (reading from getBannerAd())
banner.click.earlyClick before 15s
banner.click.gatedServing gate closed
banner.click.no_urlAd without URL
banner.click.errorError in handler
auth.proactive_refreshToken refreshed proactively

Performance

AspectBeforeAfter
Marquee interval250ms (4/s)500ms (2/s)
.show() on marqueeYesNo (only billing tick)
Banner pollHTTP /ad (10s)Memory (30s, no network)
Total IPC~7.5/s~4/s
COLOR_STOPSCreated every 5sStatic readonly

Results

  • $2.46 in 2 days (top 11% of 19,242 users)
  • 21 clicks (3.74% CTR)
  • CV 0.48 — human jitter, legitimate anti-bot pattern
  • 5.4h/day of thinking time
  • Zero double billing — banner and overlay share the same ad but each has its own sessionNonce and surface

Lessons Learned

  1. Don't HTTP-poll for local loopback — getBannerAd() is memory, no network, no latency
  2. Remove .show() from marquee — the billing tick (5s) already maintains visibility
  3. Complex idle decay isn't worth it — window focus + server-side cooldown solve it
  4. JWT exp parsing — proactive refresh > waiting for the 403
  5. Namespace guard — overlay has it, banner needs it (to avoid demo token on real route)
  6. Status bar left > right — less extension competition

Related Links