Services

Services are background modules that maintain persistent state and logic. Unlike inline widget code (which reruns on field changes), services survive re-renders and only stop when the widget unloads or the overlay disconnects.

When to Use Services

  • Accumulators that track data over time (leaderboards, combo counters)
  • Polling loops that fetch external data
  • State machines with complex transition logic
  • Any logic that shouldn’t reset when widget fields change

Registering a Service

SBCanvas.service.register('countdown', {
  init() {
    this.remaining = 300;
    this.interval = SBCanvas.setInterval(() => {
      this.remaining--;
      SBCanvas.vars.set('countdown', this.remaining);
      if (this.remaining <= 0) {
        SBCanvas.clearTimer(this.interval);
        SBCanvas.emit('countdown-done', {});
      }
    }, 1000);
  },

  dispose() {
    SBCanvas.clearTimer(this.interval);
  },

  onEvent(event, data) {
    if (event === 'activity:tip') {
      this.remaining += data.amount * 10;
    }
  }
});

Getting a Service Instance

const svc = SBCanvas.service.get('countdown');
console.log(svc.remaining);

Disposing Services

SBCanvas.service.dispose('countdown');
SBCanvas.service.disposeAll();

Service Module Interface

A service module is a plain object with lifecycle methods:
MethodRequiredDescription
init()YesCalled once after registration. Set up state and timers.
dispose()YesCalled on cleanup. Release resources.
onEvent(event, data)NoReceives all overlay events while active.

Example: Tip Leaderboard Service

SBCanvas.service.register('leaderboard', {
  async init() {
    this.data = (await SBCanvas.store.get('leaderboard')) || [];
    this.render();
  },

  onEvent(event, data) {
    if (event === 'activity:tip') {
      const existing = this.data.find(e => e.username === data.username);
      if (existing) {
        existing.total += data.amount;
      } else {
        this.data.push({ username: data.username, total: data.amount });
      }
      this.data.sort((a, b) => b.total - a.total);
      this.data = this.data.slice(0, 10);
      this.render();
      SBCanvas.store.set('leaderboard', this.data);
    }
  },

  dispose() {
    SBCanvas.store.set('leaderboard', this.data);
  },

  render() {
    const el = document.getElementById('leaderboard');
    el.innerHTML = '';
    for (const entry of this.data) {
      const div = document.createElement('div');
      div.textContent = `${entry.username}: $${entry.total}`;
      el.appendChild(div);
    }
  }
});

API Reference

MethodDescription
service.register(name, module)Register and initialize a service
service.get(name)Get service instance by name
service.dispose(name)Stop and remove a service
service.disposeAll()Stop and remove all services