# StreamBot SBCanvas SDK > Source of truth for AI-assisted widget development. > Version: 0.1.0 > Platforms: Kick, Rumble, Blaze ## What is SBCanvas? SBCanvas is a client-side JavaScript SDK that runs inside OBS browser sources. It provides a global `SBCanvas` object for building interactive stream overlay widgets. SBCanvas normalizes supported events from Kick, Rumble, and Blaze into the same Activity object shape, so widget code works identically across all three platforms. ## AI Assistant Instructions When generating SBCanvas widget code: - Always wrap runtime code in `SBCanvas.ready(() => { ... })`. - Prefer `SBCanvas.activities.on(type, handler)` for activity events. - Use `SBCanvas.setTimeout` and `SBCanvas.setInterval` instead of raw browser timers. - Use `textContent` or `SBCanvas.sanitize.html()` for user-generated text. Never use innerHTML with unsanitized input. - Check `SBCanvas.platform.hasFeature(name)` before using platform-specific features. - Do not assume every platform supports every activity type. - Use `SBCanvas.preview.injectTestEvent('activity:type', data)` only for preview/testing examples. - Use `SBCanvas.store` for data that must survive disconnects. Use `SBCanvas.vars` for ephemeral runtime state. - Use `SBCanvas.channel(name)` for cross-widget communication. ## Event Naming Convention Preferred style — use for normal activity listeners: ``` SBCanvas.activities.on('follow', handler) SBCanvas.activities.on('tip', handler) SBCanvas.activities.on('*', handler) ``` Low-level style — use only when working directly with the event bus or non-activity events: ``` SBCanvas.on('activity:follow', handler) SBCanvas.on('session:ready', handler) SBCanvas.on('chat:message', handler) SBCanvas.on('store:update', handler) ``` `activities.on('follow', fn)` is a convenience wrapper around `on('activity:follow', fn)`. Both fire the same Activity object. ## Quick Start 1. Create an overlay in the StreamBot Dashboard (https://dev.strmbot.co) 2. Add the overlay URL as an OBS Browser Source (1920x1080, 60fps) 3. Write widget code against the `SBCanvas` global 4. Test with `?preview=studio` appended to the URL 5. Remove the preview param to go live ## Global API ### Event System ```javascript SBCanvas.ready(fn) // Run when runtime is initialized SBCanvas.on(event, fn) // Subscribe to event SBCanvas.once(event, fn) // Subscribe once SBCanvas.off(event, fn) // Unsubscribe SBCanvas.after(event, count, fn) // Fire after N occurrences SBCanvas.emit(eventName, data) // Emit custom widget:{eventName} event ``` ### State (read-only) ```javascript SBCanvas.state.goals // Current goal progress SBCanvas.state.chatMessages // Recent chat messages SBCanvas.state.isConnected // WebSocket status SBCanvas.session // Frozen session data from backend SBCanvas.version // "0.1.0" ``` ### Fields ```javascript SBCanvas.getFieldData(typeOrId?) // Get widget field values SBCanvas.setField(key, value, reload?)// Update field (re-renders by default) SBCanvas.injectFields(template, context?, typeOrId?) // Interpolate fields into string ``` ### Timers ```javascript SBCanvas.setTimeout(fn, ms) // Tracked setTimeout SBCanvas.setInterval(fn, ms) // Tracked setInterval SBCanvas.clearTimer(id) // Clear either ``` ### Effects ```javascript SBCanvas.sound(url, volume?) // Play audio (0-1 volume) SBCanvas.animate(el, animationName) // Apply CSS animation class SBCanvas.shake(el, intensity?) // Shake element (px) SBCanvas.toast(text, duration?) // Show toast notification (default 3000ms) SBCanvas.confetti({count?, spread?, duration?}) // Confetti burst ``` ### Queue Control ```javascript SBCanvas.holdQueue(seconds) // Pause alert queue SBCanvas.resumeQueue() // Resume alert queue ``` ### Networking ```javascript SBCanvas.http(url, {method?, headers?, body?}) // Returns: Promise<{ok: boolean, status_code: number, body: any}> ``` ### Channels (inter-widget pub/sub) ```javascript const ch = SBCanvas.channel('myChannel') ch.publish(data) // Send to all subscribers ch.subscribe(fn) // Receive publishes (+ lastValue on join) ch.unsubscribe(fn) // Stop receiving ch.lastValue // Most recent published value ``` ### Sanitization ```javascript SBCanvas.sanitize.html(str) // Escape HTML entities SBCanvas.sanitize.js(str) // Escape for JS string context SBCanvas.sanitize.url(str) // Escape URL SBCanvas.sanitize.attr(str) // Escape HTML attribute value ``` ### Status ```javascript SBCanvas.getOverlayStatus() // Returns: {connected, authenticated, width, height, scene, studio, debug, previewMode} ``` ## Namespaced APIs ### SBCanvas.activities ```javascript activities.on(type, fn) // Listen for activity type (* for all) activities.once(type, fn) // One-time listener activities.off(type, fn?) // Unsubscribe activities.latest(type) // Most recent activity of type activities.recent(limit?) // Recent activities (max 50) ``` Activity types: `follow`, `subscribe`, `gift_sub`, `rant`, `tip`, `raid`, `host` Activity object shape: ```typescript interface Activity { id: string type: 'follow' | 'subscribe' | 'gift_sub' | 'rant' | 'tip' | 'raid' | 'host' provider: 'kick' | 'rumble' | 'blaze' username: string amount?: number currency?: string message?: string tier?: string gifted?: boolean recipient?: string viewers?: number timestamp: number raw: Record } ``` ### SBCanvas.platform ```javascript platform.provider // 'kick' | 'rumble' | 'blaze' | null platform.channelId // Channel ID string platform.channelName // Display name platform.channelSlug // URL slug (if available) platform.features // Array of supported features platform.hasFeature(name) // Check if feature supported ``` Platform features by provider: - Kick: emotes, badges, subscriptions, followers, raids, hosts, tips, clips - Rumble: emotes, badges, rants, subscriptions, followers, raids, clips - Blaze: emotes, badges, subscriptions, followers, tips ### SBCanvas.preview ```javascript preview.isActive // Boolean: is preview mode on preview.mode // 'live' | 'editor' | 'studio' preview.injectTestEvent(type, data) // Inject fake activity for testing // type format: 'activity:follow', 'activity:tip', etc. ``` In preview mode, `SBCanvas.ready()` fires immediately without waiting for a backend WebSocket connection. This means test events can be injected as soon as the page loads. ### SBCanvas.bind ```javascript bind.bind(el, expression) // Bind element to data expression bind.unbindAll(root?) // Remove all bindings under root bind.scan(root?) // Auto-bind elements with data-sbc-bind attribute ``` Binding expressions: `session.follower_count`, `goals.0.current`, `vars.myVar`, `fields.widgetId.fieldKey` ### SBCanvas.media ```javascript media.preload(url) // Preload image/video/audio media.playSequence(steps) // Execute ordered media sequence ``` Sequence step actions: `wait`, `show`, `hide`, `animate`, `play_sound` ```javascript SBCanvas.media.playSequence([ { action: 'show', target: '#alert', animation: 'slide_up' }, { action: 'play_sound', url: '/sounds/ding.mp3', volume: 0.8 }, { action: 'wait', duration_ms: 5000 }, { action: 'hide', target: '#alert', animation: 'fade' } ]) ``` ### SBCanvas.service ```javascript service.register(name, module) // Register background service service.get(name) // Get service instance service.dispose(name) // Stop and remove service service.disposeAll() // Stop all services ``` Service module shape: ```javascript SBCanvas.service.register('myService', { init() { /* called on register */ }, dispose() { /* called on cleanup */ }, onEvent(event, data) { /* receives all overlay events */ } }) ``` ### SBCanvas.panel ```javascript panel.register({fields, actions}) // Register dashboard control panel panel.onAction(name, fn) // Handle action button press panel.offAction(name, fn?) // Remove action handler ``` Panel field changes arrive via event bus: ```javascript SBCanvas.on('panel:field_change', ({key, value}) => { }) ``` ### SBCanvas.queue ```javascript queue.getLength() // Current queue size queue.peek() // View next alert without removing queue.setPriority(alertType, level) // Set priority (0-10) for alert type queue.clear(filter?) // Clear queue (optional {type} filter) queue.getConfig() // Get queue config queue.setConfig(config) // Update: maxSize, defaultPriority, throttle, priorities ``` ### SBCanvas.chat ```javascript chat.send(message) // Send message to chat // Max 500 characters // Rate limit: 5 messages per minute // Returns: Promise ``` ### SBCanvas.emotes ```javascript emotes.resolve(code) // Get emote URL by code (or null) emotes.getAll() // All loaded emotes {code: {code, url, provider}} emotes.loadSet(emotes) // Load array of {code, url, provider} ``` ### SBCanvas.vars (runtime memory, not persisted) ```javascript vars.get(key) // Get variable vars.set(key, value) // Set variable vars.delete(key) // Delete variable vars.getAll() // Get all as object vars.clear() // Clear all ``` ### SBCanvas.store (persistent, server-backed) ```javascript store.set(key, value) // Store value (async, JSON-serializable) store.get(key) // Retrieve value (async, null if missing) store.delete(key) // Delete key (async) store.getAll() // Get all stored data (async) ``` ## Events Reference ### Activity Events - `activity:follow` - `activity:subscribe` - `activity:gift_sub` - `activity:rant` - `activity:tip` - `activity:raid` - `activity:host` - `activity:*` (wildcard, fires for all) ### System Events - `ready` — Runtime initialized - `session:ready` — Backend connected - `session:update` — Session data changed - `goal:update` — Goal progress changed - `chat:message` — New chat message - `layout:change` — Widget layout updated - `emotes:load` — New emote set loaded - `alert:trigger` — New alert (legacy format) - `store:update` — Store key written, payload: `{key, value}` - `panel:field_change` — Dashboard panel field changed, payload: `{key, value}` - `widget:field_change` — `setField()` called, payload: `{key, value, fieldData, widgetId}` - `channel:{name}` — Named channel received publish ## Example: Follow Alert Widget ```html ``` ## Example: Combo Counter with Persistence ```javascript SBCanvas.ready(async () => { let combo = (await SBCanvas.store.get('combo')) || 0; const display = document.getElementById('count'); display.textContent = combo; SBCanvas.activities.on('subscribe', async () => { combo++; display.textContent = combo; await SBCanvas.store.set('combo', combo); if (combo % 10 === 0) { SBCanvas.confetti({ count: 100 }); } }); }); ``` ## Example: Platform-Adaptive Widget ```javascript SBCanvas.ready(() => { // Tips exist on Kick and Blaze if (SBCanvas.platform.hasFeature('tips')) { SBCanvas.activities.on('tip', (a) => { showDonation(a.username, a.amount, a.currency, a.message); }); } // Rants are Rumble's equivalent of tips if (SBCanvas.platform.hasFeature('rants')) { SBCanvas.activities.on('rant', (a) => { showDonation(a.username, a.amount, a.currency, a.message); }); } }); ``` ## Docs Full documentation: https://docs.streambot.co Dashboard: https://dev.strmbot.co