@bloopbot/overlay-sdk 0.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bloopbot contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # Bloopbot Overlay SDK
2
+
3
+ Typed client for custom OBS Browser Source overlays. The SDK handles the
4
+ connection and data. Your page renders the overlay.
5
+
6
+ The full SDK reference is served at `/developers/overlays` on Bloopbot.
7
+
8
+ ## Use it in a page
9
+
10
+ ```ts
11
+ import { createOverlay } from '@bloopbot/overlay-sdk';
12
+
13
+ const overlay = createOverlay({
14
+ events: ['follow', 'stage.media'],
15
+ variables: ['hype'],
16
+ records: [{ kind: 'widget', id: 'your-widget-id' }],
17
+ });
18
+
19
+ overlay.onEvent('follow', (frame) => {
20
+ document.querySelector('#name')!.textContent = String(frame.name ?? 'Someone');
21
+ });
22
+ overlay.onState((state) => {
23
+ document.body.dataset.connection = state.status;
24
+ });
25
+ overlay.onDiagnostic((diagnostic) => console.warn(diagnostic.message));
26
+
27
+ await overlay.start();
28
+ ```
29
+
30
+ `start()` resolves after the server has acknowledged subscriptions and finished
31
+ the initial state replay. Event handlers may receive replay frames while the
32
+ connection state is `connecting`; register them before calling `start()`.
33
+ Granted named records are read after `start()` and refreshed every 30 seconds;
34
+ `onState` reports changed revisions. Pass `recordRefreshMs` to choose an interval
35
+ between one second and one hour. `getRecord(kind, id)` reads one requested record
36
+ immediately. A missing or revoked record is removed from `state.records` on the
37
+ next refresh.
38
+
39
+ The OBS Browser Source URL supplies `?ws=`. During local development, pass
40
+ `{ url: 'ws://localhost:3000/overlay/...' }` as the second argument to
41
+ `createOverlay`. Keep that URL private: it contains the overlay credential.
42
+ Subscriptions are fixed when the connection starts. The server intersects them
43
+ with the grants chosen for the named overlay; a requested name does not grant
44
+ access by itself.
45
+
46
+ For a browser page without a build tool, load the versioned script from
47
+ Bloopbot (`/assets/overlay-sdk-0.1.0.js`) and use
48
+ `BloopbotOverlay.createOverlay(...)`. The browser and npm builds use the same
49
+ source and version.
50
+
51
+ ## Simulate locally
52
+
53
+ ```ts
54
+ import { createSimulation } from '@bloopbot/overlay-sdk';
55
+
56
+ const overlay = createSimulation({ events: ['follow'] });
57
+ overlay.onEvent('follow', (frame) => console.log(frame.name));
58
+ await overlay.start();
59
+ overlay.emit({ type: 'follow', name: 'Ada' });
60
+ overlay.stop();
61
+ ```
62
+
63
+ Simulation never opens a socket. It uses the same event and state interface as
64
+ the live client. Use `setVariables` and `setRecord` to exercise state reads.
65
+
66
+ ## Build and test
67
+
68
+ ```sh
69
+ npm ci
70
+ npm run typecheck
71
+ npm run lint
72
+ npm test
73
+ npm run test:coverage
74
+ npm run build
75
+ npm pack --dry-run
76
+ ```
77
+
78
+ The package has no runtime dependency on the Bloopbot application. The
79
+ `dist/browser.global.js` file and npm import are built from this source tree.
80
+
81
+ ## Separate repository
82
+
83
+ The package directory is self-contained: it has its own lockfile, build,
84
+ typecheck, lint, WebSocket integration tests, coverage gate, and GitHub Actions
85
+ workflow. No package source or build step imports the Bloopbot application.
86
+ The canonical package repository is [BloopSoftware/overlay-sdk](https://github.com/BloopSoftware/overlay-sdk). To sync it from this monorepo, export `packages/overlay-sdk` as its root:
87
+
88
+ ```sh
89
+ git subtree split --prefix=packages/overlay-sdk -b overlay-sdk-release
90
+ git push git@github.com:BloopSoftware/overlay-sdk.git overlay-sdk-release:main
91
+ ```
92
+
93
+ The first push has already established the package repository. Later updates use the
94
+ same subtree split without `-b` (it prints the new commit ID), then push that
95
+ commit ID to the package repository's `main` without force. Review its `main` branch before
96
+ releasing. The `Package` workflow runs the complete package gate for pushes and
97
+ pull requests. A published GitHub release whose tag exactly matches
98
+ `v<package.json version>` runs the gate and publishes the npm tarball. Configure
99
+ the npm trusted publisher for `@bloopbot/overlay-sdk` to authorize that
100
+ repository's `.github/workflows/package.yml` workflow with direct publishing.
101
+ The publish job uses GitHub's OIDC identity; it needs no stored npm token.
102
+ The initial package claim and trusted-publisher configuration require an npm
103
+ owner of the `@bloopbot` scope. No release is published by extracting the tree.
104
+
105
+ Protocol major versions remain supported for at least 12 months after a replacement is introduced. Keep each version change and browser artifact from the same source commit. The
106
+ app stages that artifact at `/assets/overlay-sdk-<version>.js`. Publishing the
107
+ package and deploying the app are separate steps; deploy a compatible V2 server
108
+ before directing overlay authors to a new SDK version.
@@ -0,0 +1,2 @@
1
+ export * from './index.js';
2
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ "use strict";var BloopbotOverlay=(()=>{var S=Object.defineProperty;var $=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var N=(e,i)=>{for(var t in i)S(e,t,{get:i[t],enumerable:!0})},C=(e,i,t,r)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of P(i))!U.call(e,s)&&s!==t&&S(e,s,{get:()=>i[s],enumerable:!(r=$(i,s))||r.enumerable});return e};var H=e=>C(S({},"__esModule",{value:!0}),e);var Z={};N(Z,{EVENT_FAMILIES:()=>M,createOverlay:()=>A,createSimulation:()=>K});var x=/^[a-zA-Z_][a-zA-Z0-9_.-]{0,95}$/,L=/^[a-zA-Z0-9_:.-]{1,96}$/,F=/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/,V=["widget","giveaway","nowplaying","alert_design","theme"];function l(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function b(e,i,t,r=x){if(e.length>t||e.some(s=>typeof s!="string"||!r.test(s)))throw new TypeError(`Invalid ${i} subscription`);return[...new Set(e)]}function f(e){if(!e||!Array.isArray(e.events))throw new TypeError("Events must be an array");let i=b(e.events,"event",64),t=b(e.variables??[],"variable",32),r=b(e.messages??[],"message",64),s=b(e.boards??[],"board",32,L),n=e.records??[];if(!Array.isArray(n)||n.length>64||n.some(a=>!a||typeof a.kind!="string"||!x.test(a.kind)||typeof a.id!="string"||!F.test(a.id)))throw new TypeError("Invalid record subscription");let c=[...new Map(n.map(a=>[`${a.kind}:${a.id}`,a])).values()];return{hello:"overlay-sdk",version:2,subscribe:{events:i,variables:t,messages:r,boards:s,records:c}}}function R(e){return Array.isArray(e)&&e.every(i=>typeof i=="string")}function q(e){return l(e)&&typeof e.kind=="string"&&typeof e.id=="string"}function O(e){return V.includes(e)}function u(e,i){return`${e}:${i}`}function y(e){return!l(e)||typeof e.kind!="string"||!O(e.kind)||typeof e.id!="string"||!F.test(e.id)||typeof e.revision!="number"||!Number.isSafeInteger(e.revision)||e.revision<0||!Object.hasOwn(e,"data")?null:{kind:e.kind,id:e.id,revision:e.revision,data:e.data}}function z(e){return l(e)&&["event","variable","message","board","record"].includes(String(e.kind))&&typeof e.name=="string"&&(e.reason==="unknown"||e.reason==="not_granted")}function D(e){return!l(e)||e.type!=="sdk.subscribed"||e.version!==2||typeof e.revision!="number"||!Number.isSafeInteger(e.revision)||e.revision<1||!l(e.accepted)||!R(e.accepted.events)||!R(e.accepted.variables)||!R(e.accepted.messages)||!R(e.accepted.boards)||!Array.isArray(e.accepted.records)||!e.accepted.records.every(q)||!Array.isArray(e.rejected)||!e.rejected.every(z)?null:e}function j(e){return l(e)&&e.type==="sdk.ready"&&e.version===2&&typeof e.revision=="number"&&Number.isSafeInteger(e.revision)&&e.revision>0?e:null}function g(e){return l(e)&&typeof e.type=="string"&&e.type.length>0?{...e,type:e.type}:null}function W(e){return e.type!=="stage"?e.type:e.kind==="media"?"stage.media":e.kind==="minigame"?"stage.minigame":"stage.alert"}var m=class{current={status:"idle",revision:null,variables:{},records:{},lastEventAt:null};events=new Map;stateHandlers=new Set;diagnosticHandlers=new Set;get state(){return this.current}onEvent(i,t){let r=this.events.get(i)??new Set;return r.add(t),this.events.set(i,r),()=>{r.delete(t),r.size===0&&this.events.delete(i)}}onState(i){return this.stateHandlers.add(i),()=>{this.stateHandlers.delete(i)}}onDiagnostic(i){return this.diagnosticHandlers.add(i),()=>{this.diagnosticHandlers.delete(i)}}acceptEvent(i){if(i.type==="sdk.record"){let t=y(i);if(!t){this.emitDiagnostic({code:"invalid_frame",message:"Ignored malformed record update"});return}this.applyRecord(t);return}i.type==="vars"&&typeof i.values=="object"&&i.values!==null&&!Array.isArray(i.values)&&this.setState({variables:{...this.current.variables,...i.values}}),this.setState({lastEventAt:Date.now()});for(let t of this.events.get(W(i))??[])try{t(i)}catch{this.emitDiagnostic({code:"handler_error",message:"Overlay event handler failed"})}}applyRecord(i){let t=u(i.kind,i.id);(this.current.records[t]?.revision??-1)>=i.revision||this.setState({records:{...this.current.records,[t]:i}})}removeRecord(i,t){let r=u(i,t);if(!(r in this.current.records))return;let s={...this.current.records};delete s[r],this.setState({records:s})}setState(i){this.current={...this.current,...i};for(let t of this.stateHandlers)try{t(this.current)}catch{this.emitDiagnostic({code:"handler_error",message:"Overlay state handler failed"})}}emitDiagnostic(i){for(let t of this.diagnosticHandlers)try{t(i)}catch{}}};function I(e){let i=e??(typeof location>"u"?null:new URL(location.href).searchParams.get("ws"));if(!i)throw new TypeError("Missing overlay WebSocket URL");let t;try{t=new URL(i)}catch{throw new TypeError("Invalid overlay WebSocket URL")}if(t.protocol!=="ws:"&&t.protocol!=="wss:")throw new TypeError("Overlay URL must use ws or wss");return t.href}var k=class extends m{constructor(t,r){super();this.subscriptions=t;this.options=r;if(f(t),r.handshakeTimeoutMs!==void 0&&(!Number.isSafeInteger(r.handshakeTimeoutMs)||r.handshakeTimeoutMs<1||r.handshakeTimeoutMs>6e4))throw new TypeError("Handshake timeout must be 1\u201360000 ms");if(r.recordRefreshMs!==void 0&&(!Number.isSafeInteger(r.recordRefreshMs)||r.recordRefreshMs<1e3||r.recordRefreshMs>36e5))throw new TypeError("Record refresh interval must be 1000\u20133600000 ms")}subscriptions;options;socket=null;active=!1;attempt=0;retryTimer=null;handshakeTimer=null;recordTimer=null;pending=null;pendingStart=null;grantedRecords=[];start(){if(this.state.status==="ready")return Promise.resolve();if(this.pendingStart)return this.pendingStart;let t;try{t=I(this.options.url)}catch(r){return Promise.reject(r)}return typeof WebSocket>"u"?Promise.reject(new Error("WebSocket is unavailable")):(this.active=!0,this.pendingStart=new Promise((r,s)=>{this.pending={resolve:r,reject:s},this.open(t)}).finally(()=>{this.pendingStart=null}),this.pendingStart)}stop(){this.active=!1,this.clearTimers();try{this.socket?.close(1e3,"Overlay stopped")}catch{}this.socket=null,this.pending?.reject(new Error("Overlay stopped")),this.pending=null,this.grantedRecords=[],this.setState({status:"stopped"})}async getRecord(t,r){return this.readRecord(t,r,()=>!0)}async readRecord(t,r,s){if(!O(t)||!/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(r))throw new TypeError("Invalid overlay record");if(!this.subscriptions.records?.some(d=>d.kind===t&&d.id===r))throw new Error("Record was not subscribed");if(typeof fetch>"u")throw new Error("Fetch is unavailable");let n=new URL(I(this.options.url)),c=n.pathname.split("/").at(-1);if(!c)throw new Error("Overlay credential is missing");let a=new URL(`/overlay-sdk/v2/records/${encodeURIComponent(c)}/${t}/${encodeURIComponent(r)}`,n);a.protocol=n.protocol==="wss:"?"https:":"http:";let h=await fetch(a,{cache:"no-store",credentials:"omit"});if(!s()||h.status===404)return null;if(!h.ok)throw new Error("Overlay record unavailable");let o=y(await h.json());if(!o||o.kind!==t||o.id!==r)throw new Error("Invalid overlay record response");return this.applyRecord(o),this.state.records[u(t,r)]??o}open(t){if(!this.active)return;this.setState({status:this.attempt===0?"connecting":"stale"});let r;try{r=new WebSocket(t)}catch(o){this.fail(o instanceof Error?o:new Error("Could not open overlay socket"));return}this.socket=r;let s=[],n=!1,c=!1,a=null,h=this.options.handshakeTimeoutMs??5e3;this.handshakeTimer=setTimeout(()=>{c||this.socket!==r||(this.emitDiagnostic({code:"unsupported_server",message:"The overlay server did not finish SDK V2 setup"}),r.close(1002,"SDK V2 required"))},h),r.onopen=()=>{try{r.send(JSON.stringify(f(this.subscriptions)))}catch(o){this.fail(o instanceof Error?o:new Error("Could not send overlay hello"))}},r.onmessage=o=>{if(this.socket!==r)return;let d;try{d=JSON.parse(String(o.data))}catch{this.emitDiagnostic({code:"invalid_frame",message:"Ignored malformed overlay frame"});return}if(!n){let v=D(d);if(v){n=!0,a=v.revision,this.grantedRecords=v.accepted.records,this.setState({revision:v.revision});for(let p of v.rejected)this.emitDiagnostic({code:"subscription_rejected",message:`${p.kind} ${p.name}: ${p.reason}`,subscription:p});for(let p of s)this.acceptEvent(p);return}let w=g(d);w&&w.type!=="sdk.subscribed"&&s.length<256?s.push(w):this.emitDiagnostic({code:"invalid_frame",message:"Ignored frame before SDK V2 acknowledgement"});return}let T=j(d);if(T){if(T.revision!==a){this.emitDiagnostic({code:"invalid_frame",message:"Ignored mismatched SDK ready frame"});return}c||(c=!0,this.clearHandshakeTimer(),this.attempt=0,this.setState({status:"ready"}),this.pending?.resolve(),this.pending=null,this.startRecordRefresh(r));return}if(typeof d=="object"&&d!==null&&"type"in d&&(d.type==="sdk.ready"||d.type==="sdk.subscribed")){this.emitDiagnostic({code:"invalid_frame",message:"Ignored invalid SDK control frame"});return}let _=g(d);_?this.acceptEvent(_):this.emitDiagnostic({code:"invalid_frame",message:"Ignored malformed overlay frame"})},r.onerror=()=>this.emitDiagnostic({code:"connection_error",message:"Overlay connection error"}),r.onclose=o=>{if(this.socket!==r||(this.socket=null,this.clearHandshakeTimer(),this.clearRecordTimer(),!this.active))return;if(this.setState(o.code===1008?{status:"expired",variables:{},records:{}}:{status:"stale"}),this.emitDiagnostic({code:"connection_closed",message:`Overlay connection closed (${o.code})`}),o.code===1008&&this.emitDiagnostic({code:"access_expired",message:"Overlay access expired or was revoked"}),c||(this.pending?.reject(new Error("Overlay SDK V2 handshake failed")),this.pending=null),o.code===1008||this.options.reconnect===!1){this.active=!1;return}let d=Math.min(3e4,500*2**Math.min(this.attempt++,6));this.retryTimer=setTimeout(()=>this.open(t),d)}}fail(t){this.pending?.reject(t),this.pending=null,this.stop()}clearHandshakeTimer(){this.handshakeTimer&&clearTimeout(this.handshakeTimer),this.handshakeTimer=null}clearTimers(){this.clearHandshakeTimer(),this.clearRecordTimer(),this.retryTimer&&clearTimeout(this.retryTimer),this.retryTimer=null}startRecordRefresh(t){if(this.clearRecordTimer(),this.grantedRecords.length===0)return;let r=!1,s=()=>{r||this.socket!==t||this.state.status!=="ready"||(r=!0,Promise.all(this.grantedRecords.map(async n=>{try{!await this.readRecord(n.kind,n.id,()=>this.socket===t)&&this.socket===t&&this.removeRecord(n.kind,n.id)}catch{this.socket===t&&this.emitDiagnostic({code:"record_unavailable",message:`Could not refresh ${n.kind} ${n.id}`})}})).finally(()=>{r=!1}))};s(),this.recordTimer=setInterval(s,this.options.recordRefreshMs??3e4)}clearRecordTimer(){this.recordTimer&&clearInterval(this.recordTimer),this.recordTimer=null}};function A(e,i={}){return new k(e,i)}var E=class extends m{constructor(t){super();this.subscriptions=t;f(t)}subscriptions;running=!1;async start(){this.running=!0,this.setState({status:"ready",revision:0})}stop(){this.running=!1,this.setState({status:"stopped"})}emit(t){if(!this.running)throw new Error("Start the overlay simulation before emitting events");let r=g(t);if(!r)throw new TypeError("Invalid overlay frame");let s=r.type==="stage"?r.kind==="media"?"stage.media":r.kind==="minigame"?"stage.minigame":"stage.alert":r.type;if(s!=="vars"&&!new Set(this.subscriptions.events).has(s))throw new Error(`Event ${s} was not subscribed`);this.acceptEvent(r)}setVariables(t){let r=Object.fromEntries(Object.entries(t).filter(([s])=>this.subscriptions.variables?.includes(s)));this.emit({type:"vars",values:r})}async getRecord(t,r){if(!this.subscriptions.records?.some(s=>s.kind===t&&s.id===r))throw new Error("Record was not subscribed");return this.state.records[u(t,r)]??null}setRecord(t){if(!this.running)throw new Error("Start the overlay simulation before setting records");let r=y(t);if(!r)throw new TypeError("Invalid overlay record");if(!this.subscriptions.records?.some(s=>s.kind===r.kind&&s.id===r.id))throw new Error("Record was not subscribed");this.applyRecord(r)}};function K(e){return new E(e)}var M=["adbreak","charity","chat","chathighlight","cheer","contributions","countdown","credits","custom","emotewall","follow","gift","giveaway","goal","hypetrain","jar","leaderboard","minigames","poll","prediction","queue","raid","resub","slideshow","stage.alert","stage.media","stage.minigame","sub","tickerline","wheel"];return H(Z);})();
2
+ //# sourceMappingURL=browser.global.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/browser.ts", "../src/protocol.ts", "../src/runtime.ts", "../src/client.ts", "../src/simulation.ts", "../src/types.ts"],
4
+ "sourcesContent": ["export * from './index.js';\n", "import type { AcceptedSubscriptions, OverlayFrame, OverlayRecord, RecordKind, RecordRequest, RejectedSubscription, Subscriptions } from './types.js';\n\nexport const PROTOCOL_VERSION = 2;\n\nexport interface SubscribedFrame {\n type: 'sdk.subscribed';\n version: 2;\n revision: number;\n accepted: AcceptedSubscriptions;\n rejected: RejectedSubscription[];\n}\n\nexport interface ReadyFrame {\n type: 'sdk.ready';\n version: 2;\n revision: number;\n}\n\nconst NAME = /^[a-zA-Z_][a-zA-Z0-9_.-]{0,95}$/;\nconst BOARD = /^[a-zA-Z0-9_:.-]{1,96}$/;\nconst RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/;\nconst RECORD_KINDS: readonly string[] = ['widget', 'giveaway', 'nowplaying', 'alert_design', 'theme'];\n\nfunction isObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction uniqueNames(values: readonly string[], label: string, max: number, pattern = NAME): string[] {\n if (values.length > max || values.some((value) => typeof value !== 'string' || !pattern.test(value))) {\n throw new TypeError(`Invalid ${label} subscription`);\n }\n return [...new Set(values)];\n}\n\n/** A malformed request never reaches the socket; the server decides grants. */\nexport function helloFrame(subscriptions: Subscriptions): object {\n if (!subscriptions || !Array.isArray(subscriptions.events)) throw new TypeError('Events must be an array');\n const events = uniqueNames(subscriptions.events, 'event', 64);\n const variables = uniqueNames(subscriptions.variables ?? [], 'variable', 32);\n const messages = uniqueNames(subscriptions.messages ?? [], 'message', 64);\n const boards = uniqueNames(subscriptions.boards ?? [], 'board', 32, BOARD);\n const records = subscriptions.records ?? [];\n if (!Array.isArray(records) || records.length > 64 || records.some((record) =>\n !record || typeof record.kind !== 'string' || !NAME.test(record.kind) ||\n typeof record.id !== 'string' || !RECORD_ID.test(record.id))) {\n throw new TypeError('Invalid record subscription');\n }\n const uniqueRecords = [...new Map(records.map((record) => [`${record.kind}:${record.id}`, record])).values()];\n return { hello: 'overlay-sdk', version: PROTOCOL_VERSION, subscribe: { events, variables, messages, boards, records: uniqueRecords } };\n}\n\nfunction isStringArray(value: unknown): value is string[] {\n return Array.isArray(value) && value.every((item) => typeof item === 'string');\n}\n\nfunction isRecordRequest(value: unknown): value is RecordRequest {\n return isObject(value) && typeof value.kind === 'string' && typeof value.id === 'string';\n}\n\nexport function isRecordKind(value: string): value is RecordKind {\n return RECORD_KINDS.includes(value);\n}\n\nexport function recordKey(kind: RecordKind, id: string): string {\n return `${kind}:${id}`;\n}\n\nexport function readRecord(value: unknown): OverlayRecord | null {\n if (!isObject(value) || typeof value.kind !== 'string' || !isRecordKind(value.kind) ||\n typeof value.id !== 'string' || !RECORD_ID.test(value.id) ||\n typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision) || value.revision < 0 ||\n !Object.hasOwn(value, 'data')) return null;\n return { kind: value.kind, id: value.id, revision: value.revision, data: value.data };\n}\n\nfunction isRejection(value: unknown): value is RejectedSubscription {\n return isObject(value) &&\n ['event', 'variable', 'message', 'board', 'record'].includes(String(value.kind)) &&\n typeof value.name === 'string' &&\n (value.reason === 'unknown' || value.reason === 'not_granted');\n}\n\nexport function readSubscribedFrame(value: unknown): SubscribedFrame | null {\n if (!isObject(value) || value.type !== 'sdk.subscribed' || value.version !== PROTOCOL_VERSION ||\n typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision) || value.revision < 1 ||\n !isObject(value.accepted) || !isStringArray(value.accepted.events) ||\n !isStringArray(value.accepted.variables) || !isStringArray(value.accepted.messages) ||\n !isStringArray(value.accepted.boards) || !Array.isArray(value.accepted.records) ||\n !value.accepted.records.every(isRecordRequest) || !Array.isArray(value.rejected) ||\n !value.rejected.every(isRejection)) return null;\n return value as unknown as SubscribedFrame;\n}\n\nexport function readReadyFrame(value: unknown): ReadyFrame | null {\n return isObject(value) && value.type === 'sdk.ready' && value.version === PROTOCOL_VERSION &&\n typeof value.revision === 'number' && Number.isSafeInteger(value.revision) && value.revision > 0\n ? value as unknown as ReadyFrame : null;\n}\n\nexport function readEventFrame(value: unknown): OverlayFrame | null {\n return isObject(value) && typeof value.type === 'string' && value.type.length > 0\n ? { ...value, type: value.type }\n : null;\n}\n", "import { readRecord, recordKey } from './protocol.js';\nimport type {\n Diagnostic, EventFamily, OverlayFrame, OverlayReader, OverlayRecord, OverlayState,\n RecordKind, Unsubscribe,\n} from './types.js';\n\nfunction familyOf(frame: OverlayFrame): string {\n if (frame.type !== 'stage') return frame.type;\n if (frame.kind === 'media') return 'stage.media';\n if (frame.kind === 'minigame') return 'stage.minigame';\n return 'stage.alert';\n}\n\n/** Shared state and handler rules for live sockets and explicit simulation. */\nexport abstract class OverlayRuntime implements OverlayReader {\n private current: OverlayState = { status: 'idle', revision: null, variables: {}, records: {}, lastEventAt: null };\n private readonly events = new Map<string, Set<(frame: OverlayFrame) => void>>();\n private readonly stateHandlers = new Set<(state: OverlayState) => void>();\n private readonly diagnosticHandlers = new Set<(diagnostic: Diagnostic) => void>();\n\n get state(): OverlayState { return this.current; }\n abstract start(): Promise<void>;\n abstract stop(): void;\n abstract getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null>;\n\n onEvent(family: EventFamily, handler: (frame: OverlayFrame) => void): Unsubscribe {\n const handlers = this.events.get(family) ?? new Set<(frame: OverlayFrame) => void>();\n handlers.add(handler);\n this.events.set(family, handlers);\n return () => { handlers.delete(handler); if (handlers.size === 0) this.events.delete(family); };\n }\n\n onState(handler: (state: OverlayState) => void): Unsubscribe {\n this.stateHandlers.add(handler);\n return () => { this.stateHandlers.delete(handler); };\n }\n\n onDiagnostic(handler: (diagnostic: Diagnostic) => void): Unsubscribe {\n this.diagnosticHandlers.add(handler);\n return () => { this.diagnosticHandlers.delete(handler); };\n }\n\n protected acceptEvent(frame: OverlayFrame): void {\n if (frame.type === 'sdk.record') {\n const record = readRecord(frame);\n if (!record) {\n this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed record update' });\n return;\n }\n this.applyRecord(record);\n return;\n }\n if (frame.type === 'vars' && typeof frame.values === 'object' && frame.values !== null && !Array.isArray(frame.values)) {\n this.setState({ variables: { ...this.current.variables, ...frame.values } });\n }\n this.setState({ lastEventAt: Date.now() });\n for (const handler of this.events.get(familyOf(frame)) ?? []) {\n try { handler(frame); }\n catch { this.emitDiagnostic({ code: 'handler_error', message: 'Overlay event handler failed' }); }\n }\n }\n\n protected applyRecord(record: OverlayRecord): void {\n const key = recordKey(record.kind, record.id);\n if ((this.current.records[key]?.revision ?? -1) >= record.revision) return;\n this.setState({ records: { ...this.current.records, [key]: record } });\n }\n\n protected removeRecord(kind: RecordKind, id: string): void {\n const key = recordKey(kind, id);\n if (!(key in this.current.records)) return;\n const records = { ...this.current.records };\n delete records[key];\n this.setState({ records });\n }\n\n protected setState(update: Partial<OverlayState>): void {\n this.current = { ...this.current, ...update };\n for (const handler of this.stateHandlers) {\n try { handler(this.current); }\n catch { this.emitDiagnostic({ code: 'handler_error', message: 'Overlay state handler failed' }); }\n }\n }\n\n protected emitDiagnostic(diagnostic: Diagnostic): void {\n for (const handler of this.diagnosticHandlers) {\n try { handler(diagnostic); } catch { /* Diagnostics cannot recurse. */ }\n }\n }\n}\n", "import { helloFrame, isRecordKind, readEventFrame, readReadyFrame, readRecord, readSubscribedFrame, recordKey } from './protocol.js';\nimport { OverlayRuntime } from './runtime.js';\nimport type { OverlayFrame, OverlayReader, OverlayRecord, RecordKind, RecordRequest, Subscriptions } from './types.js';\n\nexport interface OverlayOptions {\n /** The OBS Browser Source URL normally supplies `?ws=`. Override for local development. */\n url?: string;\n /** Defaults to 5 seconds. */\n handshakeTimeoutMs?: number;\n /** Defaults to true. */\n reconnect?: boolean;\n /** How often granted records are refreshed; defaults to 30 seconds. */\n recordRefreshMs?: number;\n}\n\nfunction socketUrl(override?: string): string {\n const candidate = override ?? (typeof location === 'undefined' ? null : new URL(location.href).searchParams.get('ws'));\n if (!candidate) throw new TypeError('Missing overlay WebSocket URL');\n let parsed: URL;\n try { parsed = new URL(candidate); } catch { throw new TypeError('Invalid overlay WebSocket URL'); }\n if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:') throw new TypeError('Overlay URL must use ws or wss');\n return parsed.href;\n}\n\nclass LiveOverlay extends OverlayRuntime {\n private socket: WebSocket | null = null;\n private active = false;\n private attempt = 0;\n private retryTimer: ReturnType<typeof setTimeout> | null = null;\n private handshakeTimer: ReturnType<typeof setTimeout> | null = null;\n private recordTimer: ReturnType<typeof setInterval> | null = null;\n private pending: { resolve: () => void; reject: (error: Error) => void } | null = null;\n private pendingStart: Promise<void> | null = null;\n private grantedRecords: RecordRequest[] = [];\n\n constructor(private readonly subscriptions: Subscriptions, private readonly options: OverlayOptions) {\n super();\n // Validate synchronously so the author sees a configuration error before\n // opening a socket. Grants are still decided only by the server.\n helloFrame(subscriptions);\n if (options.handshakeTimeoutMs !== undefined &&\n (!Number.isSafeInteger(options.handshakeTimeoutMs) || options.handshakeTimeoutMs < 1 || options.handshakeTimeoutMs > 60_000)) {\n throw new TypeError('Handshake timeout must be 1\u201360000 ms');\n }\n if (options.recordRefreshMs !== undefined &&\n (!Number.isSafeInteger(options.recordRefreshMs) || options.recordRefreshMs < 1000 || options.recordRefreshMs > 3_600_000)) {\n throw new TypeError('Record refresh interval must be 1000\u20133600000 ms');\n }\n }\n\n start(): Promise<void> {\n if (this.state.status === 'ready') return Promise.resolve();\n if (this.pendingStart) return this.pendingStart;\n let url: string;\n try { url = socketUrl(this.options.url); }\n catch (error) { return Promise.reject(error); }\n if (typeof WebSocket === 'undefined') return Promise.reject(new Error('WebSocket is unavailable'));\n this.active = true;\n this.pendingStart = new Promise<void>((resolve, reject) => {\n this.pending = { resolve, reject };\n this.open(url);\n }).finally(() => { this.pendingStart = null; });\n return this.pendingStart;\n }\n\n stop(): void {\n this.active = false;\n this.clearTimers();\n try { this.socket?.close(1000, 'Overlay stopped'); }\n catch { /* A connecting socket may reject close; the callback is detached below. */ }\n this.socket = null;\n this.pending?.reject(new Error('Overlay stopped'));\n this.pending = null;\n this.grantedRecords = [];\n this.setState({ status: 'stopped' });\n }\n\n async getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null> {\n return this.readRecord(kind, id, () => true);\n }\n\n private async readRecord(kind: RecordKind, id: string, isCurrent: () => boolean): Promise<OverlayRecord | null> {\n if (!isRecordKind(kind) || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(id)) {\n throw new TypeError('Invalid overlay record');\n }\n if (!this.subscriptions.records?.some((record) => record.kind === kind && record.id === id)) {\n throw new Error('Record was not subscribed');\n }\n if (typeof fetch === 'undefined') throw new Error('Fetch is unavailable');\n const socket = new URL(socketUrl(this.options.url));\n const token = socket.pathname.split('/').at(-1);\n if (!token) throw new Error('Overlay credential is missing');\n const endpoint = new URL(`/overlay-sdk/v2/records/${encodeURIComponent(token)}/${kind}/${encodeURIComponent(id)}`, socket);\n endpoint.protocol = socket.protocol === 'wss:' ? 'https:' : 'http:';\n const response = await fetch(endpoint, { cache: 'no-store', credentials: 'omit' });\n if (!isCurrent() || response.status === 404) return null;\n if (!response.ok) throw new Error('Overlay record unavailable');\n const record = readRecord(await response.json());\n if (!record || record.kind !== kind || record.id !== id) throw new Error('Invalid overlay record response');\n this.applyRecord(record);\n return this.state.records[recordKey(kind, id)] ?? record;\n }\n\n private open(url: string): void {\n if (!this.active) return;\n this.setState({ status: this.attempt === 0 ? 'connecting' : 'stale' });\n let socket: WebSocket;\n try { socket = new WebSocket(url); }\n catch (error) {\n this.fail(error instanceof Error ? error : new Error('Could not open overlay socket'));\n return;\n }\n this.socket = socket;\n const replay: OverlayFrame[] = [];\n let acknowledged = false;\n let ready = false;\n let subscribedRevision: number | null = null;\n const timeoutMs = this.options.handshakeTimeoutMs ?? 5000;\n this.handshakeTimer = setTimeout(() => {\n if (ready || this.socket !== socket) return;\n this.emitDiagnostic({ code: 'unsupported_server', message: 'The overlay server did not finish SDK V2 setup' });\n socket.close(1002, 'SDK V2 required');\n }, timeoutMs);\n\n socket.onopen = () => {\n try { socket.send(JSON.stringify(helloFrame(this.subscriptions))); }\n catch (error) { this.fail(error instanceof Error ? error : new Error('Could not send overlay hello')); }\n };\n socket.onmessage = (message) => {\n if (this.socket !== socket) return;\n let raw: unknown;\n try { raw = JSON.parse(String(message.data)); }\n catch { this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed overlay frame' }); return; }\n if (!acknowledged) {\n const subscribed = readSubscribedFrame(raw);\n if (subscribed) {\n acknowledged = true;\n subscribedRevision = subscribed.revision;\n this.grantedRecords = subscribed.accepted.records;\n this.setState({ revision: subscribed.revision });\n for (const rejection of subscribed.rejected) {\n this.emitDiagnostic({ code: 'subscription_rejected', message: `${rejection.kind} ${rejection.name}: ${rejection.reason}`, subscription: rejection });\n }\n for (const frame of replay) this.acceptEvent(frame);\n return;\n }\n const frame = readEventFrame(raw);\n if (frame && frame.type !== 'sdk.subscribed' && replay.length < 256) replay.push(frame);\n else this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored frame before SDK V2 acknowledgement' });\n return;\n }\n const done = readReadyFrame(raw);\n if (done) {\n if (done.revision !== subscribedRevision) {\n this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored mismatched SDK ready frame' });\n return;\n }\n if (!ready) {\n ready = true;\n this.clearHandshakeTimer();\n this.attempt = 0;\n this.setState({ status: 'ready' });\n this.pending?.resolve();\n this.pending = null;\n this.startRecordRefresh(socket);\n }\n return;\n }\n if (typeof raw === 'object' && raw !== null && 'type' in raw &&\n (raw.type === 'sdk.ready' || raw.type === 'sdk.subscribed')) {\n this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored invalid SDK control frame' });\n return;\n }\n const frame = readEventFrame(raw);\n if (frame) this.acceptEvent(frame);\n else this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed overlay frame' });\n };\n socket.onerror = () => this.emitDiagnostic({ code: 'connection_error', message: 'Overlay connection error' });\n socket.onclose = (event) => {\n if (this.socket !== socket) return;\n this.socket = null;\n this.clearHandshakeTimer();\n this.clearRecordTimer();\n if (!this.active) return;\n this.setState(event.code === 1008\n ? { status: 'expired', variables: {}, records: {} }\n : { status: 'stale' });\n this.emitDiagnostic({ code: 'connection_closed', message: `Overlay connection closed (${event.code})` });\n if (event.code === 1008) this.emitDiagnostic({ code: 'access_expired', message: 'Overlay access expired or was revoked' });\n if (!ready) {\n this.pending?.reject(new Error('Overlay SDK V2 handshake failed'));\n this.pending = null;\n }\n if (event.code === 1008 || this.options.reconnect === false) { this.active = false; return; }\n const delay = Math.min(30_000, 500 * 2 ** Math.min(this.attempt++, 6));\n this.retryTimer = setTimeout(() => this.open(url), delay);\n };\n }\n\n private fail(error: Error): void {\n this.pending?.reject(error);\n this.pending = null;\n this.stop();\n }\n\n private clearHandshakeTimer(): void {\n if (this.handshakeTimer) clearTimeout(this.handshakeTimer);\n this.handshakeTimer = null;\n }\n\n private clearTimers(): void {\n this.clearHandshakeTimer();\n this.clearRecordTimer();\n if (this.retryTimer) clearTimeout(this.retryTimer);\n this.retryTimer = null;\n }\n\n private startRecordRefresh(socket: WebSocket): void {\n this.clearRecordTimer();\n if (this.grantedRecords.length === 0) return;\n let refreshing = false;\n const refresh = () => {\n if (refreshing || this.socket !== socket || this.state.status !== 'ready') return;\n refreshing = true;\n void Promise.all(this.grantedRecords.map(async (record) => {\n try {\n const found = await this.readRecord(record.kind, record.id, () => this.socket === socket);\n if (!found && this.socket === socket) this.removeRecord(record.kind, record.id);\n } catch {\n if (this.socket === socket) this.emitDiagnostic({\n code: 'record_unavailable', message: `Could not refresh ${record.kind} ${record.id}`,\n });\n }\n })).finally(() => { refreshing = false; });\n };\n refresh();\n this.recordTimer = setInterval(refresh, this.options.recordRefreshMs ?? 30_000);\n }\n\n private clearRecordTimer(): void {\n if (this.recordTimer) clearInterval(this.recordTimer);\n this.recordTimer = null;\n }\n}\n\n/** Create a connection. Register handlers, then call start(). */\nexport function createOverlay(subscriptions: Subscriptions, options: OverlayOptions = {}): OverlayReader {\n return new LiveOverlay(subscriptions, options);\n}\n", "import { helloFrame, readEventFrame, readRecord, recordKey } from './protocol.js';\nimport { OverlayRuntime } from './runtime.js';\nimport type { OverlayFrame, OverlayReader, OverlayRecord, RecordKind, Subscriptions } from './types.js';\n\nexport interface OverlaySimulation extends OverlayReader {\n emit(frame: OverlayFrame): void;\n setVariables(values: Record<string, unknown>): void;\n setRecord(record: OverlayRecord): void;\n}\n\nclass LocalSimulation extends OverlayRuntime implements OverlaySimulation {\n private running = false;\n\n constructor(private readonly subscriptions: Subscriptions) {\n super();\n helloFrame(subscriptions);\n }\n\n async start(): Promise<void> {\n this.running = true;\n this.setState({ status: 'ready', revision: 0 });\n }\n\n stop(): void {\n this.running = false;\n this.setState({ status: 'stopped' });\n }\n\n emit(frame: OverlayFrame): void {\n if (!this.running) throw new Error('Start the overlay simulation before emitting events');\n const valid = readEventFrame(frame);\n if (!valid) throw new TypeError('Invalid overlay frame');\n const family = valid.type === 'stage'\n ? valid.kind === 'media' ? 'stage.media' : valid.kind === 'minigame' ? 'stage.minigame' : 'stage.alert'\n : valid.type;\n if (family !== 'vars' && !new Set<string>(this.subscriptions.events).has(family)) {\n throw new Error(`Event ${family} was not subscribed`);\n }\n this.acceptEvent(valid);\n }\n\n setVariables(values: Record<string, unknown>): void {\n const granted = Object.fromEntries(Object.entries(values).filter(([key]) => this.subscriptions.variables?.includes(key)));\n this.emit({ type: 'vars', values: granted });\n }\n\n async getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null> {\n if (!this.subscriptions.records?.some((record) => record.kind === kind && record.id === id)) {\n throw new Error('Record was not subscribed');\n }\n return this.state.records[recordKey(kind, id)] ?? null;\n }\n\n setRecord(value: OverlayRecord): void {\n if (!this.running) throw new Error('Start the overlay simulation before setting records');\n const record = readRecord(value);\n if (!record) throw new TypeError('Invalid overlay record');\n if (!this.subscriptions.records?.some((request) => request.kind === record.kind && request.id === record.id)) {\n throw new Error('Record was not subscribed');\n }\n this.applyRecord(record);\n }\n}\n\n/** Explicit, local-only test source. It never opens a network connection. */\nexport function createSimulation(subscriptions: Subscriptions): OverlaySimulation {\n return new LocalSimulation(subscriptions);\n}\n", "/** Names of live frames available to normal Bloopbot widgets. */\nexport const EVENT_FAMILIES = [\n 'adbreak', 'charity', 'chat', 'chathighlight', 'cheer', 'contributions',\n 'countdown', 'credits', 'custom', 'emotewall', 'follow', 'gift', 'giveaway',\n 'goal', 'hypetrain', 'jar', 'leaderboard', 'minigames', 'poll', 'prediction',\n 'queue', 'raid', 'resub', 'slideshow', 'stage.alert', 'stage.media',\n 'stage.minigame', 'sub', 'tickerline', 'wheel',\n] as const;\n\nexport type EventFamily = (typeof EVENT_FAMILIES)[number];\nexport type RecordKind = 'widget' | 'giveaway' | 'nowplaying' | 'alert_design' | 'theme';\nexport type BoardKey = 'loyalty' | `contrib:${'cheers' | 'gifts' | 'total'}:${'stream' | 'alltime'}` | `var:${string}`;\n\nexport interface RecordRequest {\n kind: RecordKind;\n id: string;\n}\n\nexport interface Subscriptions {\n events: readonly EventFamily[];\n variables?: readonly string[];\n messages?: readonly string[];\n boards?: readonly BoardKey[];\n records?: readonly RecordRequest[];\n}\n\nexport interface AcceptedSubscriptions {\n events: string[];\n variables: string[];\n messages: string[];\n boards: string[];\n records: RecordRequest[];\n}\n\nexport interface RejectedSubscription {\n kind: 'event' | 'variable' | 'message' | 'board' | 'record';\n name: string;\n reason: 'unknown' | 'not_granted';\n}\n\nexport interface OverlayFrame {\n type: string;\n [field: string]: unknown;\n}\n\nexport interface OverlayRecord {\n kind: RecordKind;\n id: string;\n revision: number;\n data: unknown;\n}\n\nexport type ConnectionStatus = 'idle' | 'connecting' | 'ready' | 'stale' | 'expired' | 'stopped';\n\nexport interface OverlayState {\n status: ConnectionStatus;\n revision: number | null;\n variables: Readonly<Record<string, unknown>>;\n records: Readonly<Record<string, OverlayRecord>>;\n lastEventAt: number | null;\n}\n\nexport interface Diagnostic {\n code: 'subscription_rejected' | 'invalid_frame' | 'connection_closed' | 'connection_error' | 'handler_error' | 'unsupported_server' | 'access_expired' | 'record_unavailable';\n message: string;\n subscription?: RejectedSubscription;\n}\n\nexport type Unsubscribe = () => void;\n\nexport interface OverlayReader {\n readonly state: OverlayState;\n start(): Promise<void>;\n stop(): void;\n onEvent(family: EventFamily, handler: (frame: OverlayFrame) => void): Unsubscribe;\n onState(handler: (state: OverlayState) => void): Unsubscribe;\n onDiagnostic(handler: (diagnostic: Diagnostic) => void): Unsubscribe;\n getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null>;\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["browser_exports", "__export", "EVENT_FAMILIES", "createOverlay", "createSimulation", "NAME", "BOARD", "RECORD_ID", "RECORD_KINDS", "isObject", "value", "uniqueNames", "values", "label", "max", "pattern", "helloFrame", "subscriptions", "events", "variables", "messages", "boards", "records", "record", "uniqueRecords", "isStringArray", "item", "isRecordRequest", "isRecordKind", "recordKey", "kind", "id", "readRecord", "isRejection", "readSubscribedFrame", "readReadyFrame", "readEventFrame", "familyOf", "frame", "OverlayRuntime", "family", "handler", "handlers", "record", "readRecord", "key", "recordKey", "kind", "id", "records", "update", "diagnostic", "socketUrl", "override", "candidate", "parsed", "LiveOverlay", "OverlayRuntime", "subscriptions", "options", "helloFrame", "url", "error", "resolve", "reject", "kind", "id", "isCurrent", "isRecordKind", "record", "socket", "token", "endpoint", "response", "readRecord", "recordKey", "replay", "acknowledged", "ready", "subscribedRevision", "timeoutMs", "message", "raw", "subscribed", "readSubscribedFrame", "rejection", "frame", "readEventFrame", "done", "readReadyFrame", "event", "delay", "refreshing", "refresh", "createOverlay", "LocalSimulation", "OverlayRuntime", "subscriptions", "helloFrame", "frame", "valid", "readEventFrame", "family", "values", "granted", "key", "kind", "id", "record", "recordKey", "value", "readRecord", "request", "createSimulation", "EVENT_FAMILIES"]
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './index.js';
2
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { OverlayReader, Subscriptions } from './types.js';
2
+ export interface OverlayOptions {
3
+ /** The OBS Browser Source URL normally supplies `?ws=`. Override for local development. */
4
+ url?: string;
5
+ /** Defaults to 5 seconds. */
6
+ handshakeTimeoutMs?: number;
7
+ /** Defaults to true. */
8
+ reconnect?: boolean;
9
+ /** How often granted records are refreshed; defaults to 30 seconds. */
10
+ recordRefreshMs?: number;
11
+ }
12
+ /** Create a connection. Register handlers, then call start(). */
13
+ export declare function createOverlay(subscriptions: Subscriptions, options?: OverlayOptions): OverlayReader;
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAgB,aAAa,EAA4C,aAAa,EAAE,MAAM,YAAY,CAAC;AAEvH,MAAM,WAAW,cAAc;IAC7B,2FAA2F;IAC3F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wBAAwB;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAwOD,iEAAiE;AACjE,wBAAgB,aAAa,CAAC,aAAa,EAAE,aAAa,EAAE,OAAO,GAAE,cAAmB,GAAG,aAAa,CAEvG"}
package/dist/client.js ADDED
@@ -0,0 +1,281 @@
1
+ import { helloFrame, isRecordKind, readEventFrame, readReadyFrame, readRecord, readSubscribedFrame, recordKey } from './protocol.js';
2
+ import { OverlayRuntime } from './runtime.js';
3
+ function socketUrl(override) {
4
+ const candidate = override ?? (typeof location === 'undefined' ? null : new URL(location.href).searchParams.get('ws'));
5
+ if (!candidate)
6
+ throw new TypeError('Missing overlay WebSocket URL');
7
+ let parsed;
8
+ try {
9
+ parsed = new URL(candidate);
10
+ }
11
+ catch {
12
+ throw new TypeError('Invalid overlay WebSocket URL');
13
+ }
14
+ if (parsed.protocol !== 'ws:' && parsed.protocol !== 'wss:')
15
+ throw new TypeError('Overlay URL must use ws or wss');
16
+ return parsed.href;
17
+ }
18
+ class LiveOverlay extends OverlayRuntime {
19
+ subscriptions;
20
+ options;
21
+ socket = null;
22
+ active = false;
23
+ attempt = 0;
24
+ retryTimer = null;
25
+ handshakeTimer = null;
26
+ recordTimer = null;
27
+ pending = null;
28
+ pendingStart = null;
29
+ grantedRecords = [];
30
+ constructor(subscriptions, options) {
31
+ super();
32
+ this.subscriptions = subscriptions;
33
+ this.options = options;
34
+ // Validate synchronously so the author sees a configuration error before
35
+ // opening a socket. Grants are still decided only by the server.
36
+ helloFrame(subscriptions);
37
+ if (options.handshakeTimeoutMs !== undefined &&
38
+ (!Number.isSafeInteger(options.handshakeTimeoutMs) || options.handshakeTimeoutMs < 1 || options.handshakeTimeoutMs > 60_000)) {
39
+ throw new TypeError('Handshake timeout must be 1–60000 ms');
40
+ }
41
+ if (options.recordRefreshMs !== undefined &&
42
+ (!Number.isSafeInteger(options.recordRefreshMs) || options.recordRefreshMs < 1000 || options.recordRefreshMs > 3_600_000)) {
43
+ throw new TypeError('Record refresh interval must be 1000–3600000 ms');
44
+ }
45
+ }
46
+ start() {
47
+ if (this.state.status === 'ready')
48
+ return Promise.resolve();
49
+ if (this.pendingStart)
50
+ return this.pendingStart;
51
+ let url;
52
+ try {
53
+ url = socketUrl(this.options.url);
54
+ }
55
+ catch (error) {
56
+ return Promise.reject(error);
57
+ }
58
+ if (typeof WebSocket === 'undefined')
59
+ return Promise.reject(new Error('WebSocket is unavailable'));
60
+ this.active = true;
61
+ this.pendingStart = new Promise((resolve, reject) => {
62
+ this.pending = { resolve, reject };
63
+ this.open(url);
64
+ }).finally(() => { this.pendingStart = null; });
65
+ return this.pendingStart;
66
+ }
67
+ stop() {
68
+ this.active = false;
69
+ this.clearTimers();
70
+ try {
71
+ this.socket?.close(1000, 'Overlay stopped');
72
+ }
73
+ catch { /* A connecting socket may reject close; the callback is detached below. */ }
74
+ this.socket = null;
75
+ this.pending?.reject(new Error('Overlay stopped'));
76
+ this.pending = null;
77
+ this.grantedRecords = [];
78
+ this.setState({ status: 'stopped' });
79
+ }
80
+ async getRecord(kind, id) {
81
+ return this.readRecord(kind, id, () => true);
82
+ }
83
+ async readRecord(kind, id, isCurrent) {
84
+ if (!isRecordKind(kind) || !/^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/.test(id)) {
85
+ throw new TypeError('Invalid overlay record');
86
+ }
87
+ if (!this.subscriptions.records?.some((record) => record.kind === kind && record.id === id)) {
88
+ throw new Error('Record was not subscribed');
89
+ }
90
+ if (typeof fetch === 'undefined')
91
+ throw new Error('Fetch is unavailable');
92
+ const socket = new URL(socketUrl(this.options.url));
93
+ const token = socket.pathname.split('/').at(-1);
94
+ if (!token)
95
+ throw new Error('Overlay credential is missing');
96
+ const endpoint = new URL(`/overlay-sdk/v2/records/${encodeURIComponent(token)}/${kind}/${encodeURIComponent(id)}`, socket);
97
+ endpoint.protocol = socket.protocol === 'wss:' ? 'https:' : 'http:';
98
+ const response = await fetch(endpoint, { cache: 'no-store', credentials: 'omit' });
99
+ if (!isCurrent() || response.status === 404)
100
+ return null;
101
+ if (!response.ok)
102
+ throw new Error('Overlay record unavailable');
103
+ const record = readRecord(await response.json());
104
+ if (!record || record.kind !== kind || record.id !== id)
105
+ throw new Error('Invalid overlay record response');
106
+ this.applyRecord(record);
107
+ return this.state.records[recordKey(kind, id)] ?? record;
108
+ }
109
+ open(url) {
110
+ if (!this.active)
111
+ return;
112
+ this.setState({ status: this.attempt === 0 ? 'connecting' : 'stale' });
113
+ let socket;
114
+ try {
115
+ socket = new WebSocket(url);
116
+ }
117
+ catch (error) {
118
+ this.fail(error instanceof Error ? error : new Error('Could not open overlay socket'));
119
+ return;
120
+ }
121
+ this.socket = socket;
122
+ const replay = [];
123
+ let acknowledged = false;
124
+ let ready = false;
125
+ let subscribedRevision = null;
126
+ const timeoutMs = this.options.handshakeTimeoutMs ?? 5000;
127
+ this.handshakeTimer = setTimeout(() => {
128
+ if (ready || this.socket !== socket)
129
+ return;
130
+ this.emitDiagnostic({ code: 'unsupported_server', message: 'The overlay server did not finish SDK V2 setup' });
131
+ socket.close(1002, 'SDK V2 required');
132
+ }, timeoutMs);
133
+ socket.onopen = () => {
134
+ try {
135
+ socket.send(JSON.stringify(helloFrame(this.subscriptions)));
136
+ }
137
+ catch (error) {
138
+ this.fail(error instanceof Error ? error : new Error('Could not send overlay hello'));
139
+ }
140
+ };
141
+ socket.onmessage = (message) => {
142
+ if (this.socket !== socket)
143
+ return;
144
+ let raw;
145
+ try {
146
+ raw = JSON.parse(String(message.data));
147
+ }
148
+ catch {
149
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed overlay frame' });
150
+ return;
151
+ }
152
+ if (!acknowledged) {
153
+ const subscribed = readSubscribedFrame(raw);
154
+ if (subscribed) {
155
+ acknowledged = true;
156
+ subscribedRevision = subscribed.revision;
157
+ this.grantedRecords = subscribed.accepted.records;
158
+ this.setState({ revision: subscribed.revision });
159
+ for (const rejection of subscribed.rejected) {
160
+ this.emitDiagnostic({ code: 'subscription_rejected', message: `${rejection.kind} ${rejection.name}: ${rejection.reason}`, subscription: rejection });
161
+ }
162
+ for (const frame of replay)
163
+ this.acceptEvent(frame);
164
+ return;
165
+ }
166
+ const frame = readEventFrame(raw);
167
+ if (frame && frame.type !== 'sdk.subscribed' && replay.length < 256)
168
+ replay.push(frame);
169
+ else
170
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored frame before SDK V2 acknowledgement' });
171
+ return;
172
+ }
173
+ const done = readReadyFrame(raw);
174
+ if (done) {
175
+ if (done.revision !== subscribedRevision) {
176
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored mismatched SDK ready frame' });
177
+ return;
178
+ }
179
+ if (!ready) {
180
+ ready = true;
181
+ this.clearHandshakeTimer();
182
+ this.attempt = 0;
183
+ this.setState({ status: 'ready' });
184
+ this.pending?.resolve();
185
+ this.pending = null;
186
+ this.startRecordRefresh(socket);
187
+ }
188
+ return;
189
+ }
190
+ if (typeof raw === 'object' && raw !== null && 'type' in raw &&
191
+ (raw.type === 'sdk.ready' || raw.type === 'sdk.subscribed')) {
192
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored invalid SDK control frame' });
193
+ return;
194
+ }
195
+ const frame = readEventFrame(raw);
196
+ if (frame)
197
+ this.acceptEvent(frame);
198
+ else
199
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed overlay frame' });
200
+ };
201
+ socket.onerror = () => this.emitDiagnostic({ code: 'connection_error', message: 'Overlay connection error' });
202
+ socket.onclose = (event) => {
203
+ if (this.socket !== socket)
204
+ return;
205
+ this.socket = null;
206
+ this.clearHandshakeTimer();
207
+ this.clearRecordTimer();
208
+ if (!this.active)
209
+ return;
210
+ this.setState(event.code === 1008
211
+ ? { status: 'expired', variables: {}, records: {} }
212
+ : { status: 'stale' });
213
+ this.emitDiagnostic({ code: 'connection_closed', message: `Overlay connection closed (${event.code})` });
214
+ if (event.code === 1008)
215
+ this.emitDiagnostic({ code: 'access_expired', message: 'Overlay access expired or was revoked' });
216
+ if (!ready) {
217
+ this.pending?.reject(new Error('Overlay SDK V2 handshake failed'));
218
+ this.pending = null;
219
+ }
220
+ if (event.code === 1008 || this.options.reconnect === false) {
221
+ this.active = false;
222
+ return;
223
+ }
224
+ const delay = Math.min(30_000, 500 * 2 ** Math.min(this.attempt++, 6));
225
+ this.retryTimer = setTimeout(() => this.open(url), delay);
226
+ };
227
+ }
228
+ fail(error) {
229
+ this.pending?.reject(error);
230
+ this.pending = null;
231
+ this.stop();
232
+ }
233
+ clearHandshakeTimer() {
234
+ if (this.handshakeTimer)
235
+ clearTimeout(this.handshakeTimer);
236
+ this.handshakeTimer = null;
237
+ }
238
+ clearTimers() {
239
+ this.clearHandshakeTimer();
240
+ this.clearRecordTimer();
241
+ if (this.retryTimer)
242
+ clearTimeout(this.retryTimer);
243
+ this.retryTimer = null;
244
+ }
245
+ startRecordRefresh(socket) {
246
+ this.clearRecordTimer();
247
+ if (this.grantedRecords.length === 0)
248
+ return;
249
+ let refreshing = false;
250
+ const refresh = () => {
251
+ if (refreshing || this.socket !== socket || this.state.status !== 'ready')
252
+ return;
253
+ refreshing = true;
254
+ void Promise.all(this.grantedRecords.map(async (record) => {
255
+ try {
256
+ const found = await this.readRecord(record.kind, record.id, () => this.socket === socket);
257
+ if (!found && this.socket === socket)
258
+ this.removeRecord(record.kind, record.id);
259
+ }
260
+ catch {
261
+ if (this.socket === socket)
262
+ this.emitDiagnostic({
263
+ code: 'record_unavailable', message: `Could not refresh ${record.kind} ${record.id}`,
264
+ });
265
+ }
266
+ })).finally(() => { refreshing = false; });
267
+ };
268
+ refresh();
269
+ this.recordTimer = setInterval(refresh, this.options.recordRefreshMs ?? 30_000);
270
+ }
271
+ clearRecordTimer() {
272
+ if (this.recordTimer)
273
+ clearInterval(this.recordTimer);
274
+ this.recordTimer = null;
275
+ }
276
+ }
277
+ /** Create a connection. Register handlers, then call start(). */
278
+ export function createOverlay(subscriptions, options = {}) {
279
+ return new LiveOverlay(subscriptions, options);
280
+ }
281
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export { createOverlay, type OverlayOptions } from './client.js';
2
+ export { createSimulation, type OverlaySimulation } from './simulation.js';
3
+ export { EVENT_FAMILIES, type EventFamily, type RecordKind, type BoardKey } from './types.js';
4
+ export type { AcceptedSubscriptions, ConnectionStatus, Diagnostic, OverlayFrame, OverlayReader, OverlayState, OverlayRecord, RecordRequest, RejectedSubscription, Subscriptions, Unsubscribe, } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,KAAK,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC9F,YAAY,EACV,qBAAqB,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAChF,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,oBAAoB,EAAE,aAAa,EAAE,WAAW,GAC7F,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { createOverlay } from './client.js';
2
+ export { createSimulation } from './simulation.js';
3
+ export { EVENT_FAMILIES } from './types.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAA0B,MAAM,iBAAiB,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAoD,MAAM,YAAY,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { AcceptedSubscriptions, OverlayFrame, OverlayRecord, RecordKind, RejectedSubscription, Subscriptions } from './types.js';
2
+ export declare const PROTOCOL_VERSION = 2;
3
+ export interface SubscribedFrame {
4
+ type: 'sdk.subscribed';
5
+ version: 2;
6
+ revision: number;
7
+ accepted: AcceptedSubscriptions;
8
+ rejected: RejectedSubscription[];
9
+ }
10
+ export interface ReadyFrame {
11
+ type: 'sdk.ready';
12
+ version: 2;
13
+ revision: number;
14
+ }
15
+ /** A malformed request never reaches the socket; the server decides grants. */
16
+ export declare function helloFrame(subscriptions: Subscriptions): object;
17
+ export declare function isRecordKind(value: string): value is RecordKind;
18
+ export declare function recordKey(kind: RecordKind, id: string): string;
19
+ export declare function readRecord(value: unknown): OverlayRecord | null;
20
+ export declare function readSubscribedFrame(value: unknown): SubscribedFrame | null;
21
+ export declare function readReadyFrame(value: unknown): ReadyFrame | null;
22
+ export declare function readEventFrame(value: unknown): OverlayFrame | null;
23
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAiB,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAErJ,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,gBAAgB,CAAC;IACvB,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,qBAAqB,CAAC;IAChC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,CAAC,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;CAClB;AAkBD,+EAA+E;AAC/E,wBAAgB,UAAU,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,CAc/D;AAUD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,UAAU,CAE/D;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAM/D;AASD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAS1E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAIhE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAIlE"}
@@ -0,0 +1,78 @@
1
+ export const PROTOCOL_VERSION = 2;
2
+ const NAME = /^[a-zA-Z_][a-zA-Z0-9_.-]{0,95}$/;
3
+ const BOARD = /^[a-zA-Z0-9_:.-]{1,96}$/;
4
+ const RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9_.:-]{0,127}$/;
5
+ const RECORD_KINDS = ['widget', 'giveaway', 'nowplaying', 'alert_design', 'theme'];
6
+ function isObject(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
9
+ function uniqueNames(values, label, max, pattern = NAME) {
10
+ if (values.length > max || values.some((value) => typeof value !== 'string' || !pattern.test(value))) {
11
+ throw new TypeError(`Invalid ${label} subscription`);
12
+ }
13
+ return [...new Set(values)];
14
+ }
15
+ /** A malformed request never reaches the socket; the server decides grants. */
16
+ export function helloFrame(subscriptions) {
17
+ if (!subscriptions || !Array.isArray(subscriptions.events))
18
+ throw new TypeError('Events must be an array');
19
+ const events = uniqueNames(subscriptions.events, 'event', 64);
20
+ const variables = uniqueNames(subscriptions.variables ?? [], 'variable', 32);
21
+ const messages = uniqueNames(subscriptions.messages ?? [], 'message', 64);
22
+ const boards = uniqueNames(subscriptions.boards ?? [], 'board', 32, BOARD);
23
+ const records = subscriptions.records ?? [];
24
+ if (!Array.isArray(records) || records.length > 64 || records.some((record) => !record || typeof record.kind !== 'string' || !NAME.test(record.kind) ||
25
+ typeof record.id !== 'string' || !RECORD_ID.test(record.id))) {
26
+ throw new TypeError('Invalid record subscription');
27
+ }
28
+ const uniqueRecords = [...new Map(records.map((record) => [`${record.kind}:${record.id}`, record])).values()];
29
+ return { hello: 'overlay-sdk', version: PROTOCOL_VERSION, subscribe: { events, variables, messages, boards, records: uniqueRecords } };
30
+ }
31
+ function isStringArray(value) {
32
+ return Array.isArray(value) && value.every((item) => typeof item === 'string');
33
+ }
34
+ function isRecordRequest(value) {
35
+ return isObject(value) && typeof value.kind === 'string' && typeof value.id === 'string';
36
+ }
37
+ export function isRecordKind(value) {
38
+ return RECORD_KINDS.includes(value);
39
+ }
40
+ export function recordKey(kind, id) {
41
+ return `${kind}:${id}`;
42
+ }
43
+ export function readRecord(value) {
44
+ if (!isObject(value) || typeof value.kind !== 'string' || !isRecordKind(value.kind) ||
45
+ typeof value.id !== 'string' || !RECORD_ID.test(value.id) ||
46
+ typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision) || value.revision < 0 ||
47
+ !Object.hasOwn(value, 'data'))
48
+ return null;
49
+ return { kind: value.kind, id: value.id, revision: value.revision, data: value.data };
50
+ }
51
+ function isRejection(value) {
52
+ return isObject(value) &&
53
+ ['event', 'variable', 'message', 'board', 'record'].includes(String(value.kind)) &&
54
+ typeof value.name === 'string' &&
55
+ (value.reason === 'unknown' || value.reason === 'not_granted');
56
+ }
57
+ export function readSubscribedFrame(value) {
58
+ if (!isObject(value) || value.type !== 'sdk.subscribed' || value.version !== PROTOCOL_VERSION ||
59
+ typeof value.revision !== 'number' || !Number.isSafeInteger(value.revision) || value.revision < 1 ||
60
+ !isObject(value.accepted) || !isStringArray(value.accepted.events) ||
61
+ !isStringArray(value.accepted.variables) || !isStringArray(value.accepted.messages) ||
62
+ !isStringArray(value.accepted.boards) || !Array.isArray(value.accepted.records) ||
63
+ !value.accepted.records.every(isRecordRequest) || !Array.isArray(value.rejected) ||
64
+ !value.rejected.every(isRejection))
65
+ return null;
66
+ return value;
67
+ }
68
+ export function readReadyFrame(value) {
69
+ return isObject(value) && value.type === 'sdk.ready' && value.version === PROTOCOL_VERSION &&
70
+ typeof value.revision === 'number' && Number.isSafeInteger(value.revision) && value.revision > 0
71
+ ? value : null;
72
+ }
73
+ export function readEventFrame(value) {
74
+ return isObject(value) && typeof value.type === 'string' && value.type.length > 0
75
+ ? { ...value, type: value.type }
76
+ : null;
77
+ }
78
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { Diagnostic, EventFamily, OverlayFrame, OverlayReader, OverlayRecord, OverlayState, RecordKind, Unsubscribe } from './types.js';
2
+ /** Shared state and handler rules for live sockets and explicit simulation. */
3
+ export declare abstract class OverlayRuntime implements OverlayReader {
4
+ private current;
5
+ private readonly events;
6
+ private readonly stateHandlers;
7
+ private readonly diagnosticHandlers;
8
+ get state(): OverlayState;
9
+ abstract start(): Promise<void>;
10
+ abstract stop(): void;
11
+ abstract getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null>;
12
+ onEvent(family: EventFamily, handler: (frame: OverlayFrame) => void): Unsubscribe;
13
+ onState(handler: (state: OverlayState) => void): Unsubscribe;
14
+ onDiagnostic(handler: (diagnostic: Diagnostic) => void): Unsubscribe;
15
+ protected acceptEvent(frame: OverlayFrame): void;
16
+ protected applyRecord(record: OverlayRecord): void;
17
+ protected removeRecord(kind: RecordKind, id: string): void;
18
+ protected setState(update: Partial<OverlayState>): void;
19
+ protected emitDiagnostic(diagnostic: Diagnostic): void;
20
+ }
21
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EACjF,UAAU,EAAE,WAAW,EACxB,MAAM,YAAY,CAAC;AASpB,+EAA+E;AAC/E,8BAAsB,cAAe,YAAW,aAAa;IAC3D,OAAO,CAAC,OAAO,CAAmG;IAClH,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyD;IAChF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA4C;IAC1E,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAA+C;IAElF,IAAI,KAAK,IAAI,YAAY,CAAyB;IAClD,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAC/B,QAAQ,CAAC,IAAI,IAAI,IAAI;IACrB,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAE/E,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,WAAW;IAOjF,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,WAAW;IAK5D,YAAY,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,UAAU,KAAK,IAAI,GAAG,WAAW;IAKpE,SAAS,CAAC,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI;IAoBhD,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMlD,SAAS,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAQ1D,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI;IAQvD,SAAS,CAAC,cAAc,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI;CAKvD"}
@@ -0,0 +1,90 @@
1
+ import { readRecord, recordKey } from './protocol.js';
2
+ function familyOf(frame) {
3
+ if (frame.type !== 'stage')
4
+ return frame.type;
5
+ if (frame.kind === 'media')
6
+ return 'stage.media';
7
+ if (frame.kind === 'minigame')
8
+ return 'stage.minigame';
9
+ return 'stage.alert';
10
+ }
11
+ /** Shared state and handler rules for live sockets and explicit simulation. */
12
+ export class OverlayRuntime {
13
+ current = { status: 'idle', revision: null, variables: {}, records: {}, lastEventAt: null };
14
+ events = new Map();
15
+ stateHandlers = new Set();
16
+ diagnosticHandlers = new Set();
17
+ get state() { return this.current; }
18
+ onEvent(family, handler) {
19
+ const handlers = this.events.get(family) ?? new Set();
20
+ handlers.add(handler);
21
+ this.events.set(family, handlers);
22
+ return () => { handlers.delete(handler); if (handlers.size === 0)
23
+ this.events.delete(family); };
24
+ }
25
+ onState(handler) {
26
+ this.stateHandlers.add(handler);
27
+ return () => { this.stateHandlers.delete(handler); };
28
+ }
29
+ onDiagnostic(handler) {
30
+ this.diagnosticHandlers.add(handler);
31
+ return () => { this.diagnosticHandlers.delete(handler); };
32
+ }
33
+ acceptEvent(frame) {
34
+ if (frame.type === 'sdk.record') {
35
+ const record = readRecord(frame);
36
+ if (!record) {
37
+ this.emitDiagnostic({ code: 'invalid_frame', message: 'Ignored malformed record update' });
38
+ return;
39
+ }
40
+ this.applyRecord(record);
41
+ return;
42
+ }
43
+ if (frame.type === 'vars' && typeof frame.values === 'object' && frame.values !== null && !Array.isArray(frame.values)) {
44
+ this.setState({ variables: { ...this.current.variables, ...frame.values } });
45
+ }
46
+ this.setState({ lastEventAt: Date.now() });
47
+ for (const handler of this.events.get(familyOf(frame)) ?? []) {
48
+ try {
49
+ handler(frame);
50
+ }
51
+ catch {
52
+ this.emitDiagnostic({ code: 'handler_error', message: 'Overlay event handler failed' });
53
+ }
54
+ }
55
+ }
56
+ applyRecord(record) {
57
+ const key = recordKey(record.kind, record.id);
58
+ if ((this.current.records[key]?.revision ?? -1) >= record.revision)
59
+ return;
60
+ this.setState({ records: { ...this.current.records, [key]: record } });
61
+ }
62
+ removeRecord(kind, id) {
63
+ const key = recordKey(kind, id);
64
+ if (!(key in this.current.records))
65
+ return;
66
+ const records = { ...this.current.records };
67
+ delete records[key];
68
+ this.setState({ records });
69
+ }
70
+ setState(update) {
71
+ this.current = { ...this.current, ...update };
72
+ for (const handler of this.stateHandlers) {
73
+ try {
74
+ handler(this.current);
75
+ }
76
+ catch {
77
+ this.emitDiagnostic({ code: 'handler_error', message: 'Overlay state handler failed' });
78
+ }
79
+ }
80
+ }
81
+ emitDiagnostic(diagnostic) {
82
+ for (const handler of this.diagnosticHandlers) {
83
+ try {
84
+ handler(diagnostic);
85
+ }
86
+ catch { /* Diagnostics cannot recurse. */ }
87
+ }
88
+ }
89
+ }
90
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { OverlayFrame, OverlayReader, OverlayRecord, Subscriptions } from './types.js';
2
+ export interface OverlaySimulation extends OverlayReader {
3
+ emit(frame: OverlayFrame): void;
4
+ setVariables(values: Record<string, unknown>): void;
5
+ setRecord(record: OverlayRecord): void;
6
+ }
7
+ /** Explicit, local-only test source. It never opens a network connection. */
8
+ export declare function createSimulation(subscriptions: Subscriptions): OverlaySimulation;
9
+ //# sourceMappingURL=simulation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulation.d.ts","sourceRoot":"","sources":["../src/simulation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAc,aAAa,EAAE,MAAM,YAAY,CAAC;AAExG,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACtD,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IAChC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACpD,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;CACxC;AAwDD,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,aAAa,GAAG,iBAAiB,CAEhF"}
@@ -0,0 +1,59 @@
1
+ import { helloFrame, readEventFrame, readRecord, recordKey } from './protocol.js';
2
+ import { OverlayRuntime } from './runtime.js';
3
+ class LocalSimulation extends OverlayRuntime {
4
+ subscriptions;
5
+ running = false;
6
+ constructor(subscriptions) {
7
+ super();
8
+ this.subscriptions = subscriptions;
9
+ helloFrame(subscriptions);
10
+ }
11
+ async start() {
12
+ this.running = true;
13
+ this.setState({ status: 'ready', revision: 0 });
14
+ }
15
+ stop() {
16
+ this.running = false;
17
+ this.setState({ status: 'stopped' });
18
+ }
19
+ emit(frame) {
20
+ if (!this.running)
21
+ throw new Error('Start the overlay simulation before emitting events');
22
+ const valid = readEventFrame(frame);
23
+ if (!valid)
24
+ throw new TypeError('Invalid overlay frame');
25
+ const family = valid.type === 'stage'
26
+ ? valid.kind === 'media' ? 'stage.media' : valid.kind === 'minigame' ? 'stage.minigame' : 'stage.alert'
27
+ : valid.type;
28
+ if (family !== 'vars' && !new Set(this.subscriptions.events).has(family)) {
29
+ throw new Error(`Event ${family} was not subscribed`);
30
+ }
31
+ this.acceptEvent(valid);
32
+ }
33
+ setVariables(values) {
34
+ const granted = Object.fromEntries(Object.entries(values).filter(([key]) => this.subscriptions.variables?.includes(key)));
35
+ this.emit({ type: 'vars', values: granted });
36
+ }
37
+ async getRecord(kind, id) {
38
+ if (!this.subscriptions.records?.some((record) => record.kind === kind && record.id === id)) {
39
+ throw new Error('Record was not subscribed');
40
+ }
41
+ return this.state.records[recordKey(kind, id)] ?? null;
42
+ }
43
+ setRecord(value) {
44
+ if (!this.running)
45
+ throw new Error('Start the overlay simulation before setting records');
46
+ const record = readRecord(value);
47
+ if (!record)
48
+ throw new TypeError('Invalid overlay record');
49
+ if (!this.subscriptions.records?.some((request) => request.kind === record.kind && request.id === record.id)) {
50
+ throw new Error('Record was not subscribed');
51
+ }
52
+ this.applyRecord(record);
53
+ }
54
+ }
55
+ /** Explicit, local-only test source. It never opens a network connection. */
56
+ export function createSimulation(subscriptions) {
57
+ return new LocalSimulation(subscriptions);
58
+ }
59
+ //# sourceMappingURL=simulation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"simulation.js","sourceRoot":"","sources":["../src/simulation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /** Names of live frames available to normal Bloopbot widgets. */
2
+ export declare const EVENT_FAMILIES: readonly ["adbreak", "charity", "chat", "chathighlight", "cheer", "contributions", "countdown", "credits", "custom", "emotewall", "follow", "gift", "giveaway", "goal", "hypetrain", "jar", "leaderboard", "minigames", "poll", "prediction", "queue", "raid", "resub", "slideshow", "stage.alert", "stage.media", "stage.minigame", "sub", "tickerline", "wheel"];
3
+ export type EventFamily = (typeof EVENT_FAMILIES)[number];
4
+ export type RecordKind = 'widget' | 'giveaway' | 'nowplaying' | 'alert_design' | 'theme';
5
+ export type BoardKey = 'loyalty' | `contrib:${'cheers' | 'gifts' | 'total'}:${'stream' | 'alltime'}` | `var:${string}`;
6
+ export interface RecordRequest {
7
+ kind: RecordKind;
8
+ id: string;
9
+ }
10
+ export interface Subscriptions {
11
+ events: readonly EventFamily[];
12
+ variables?: readonly string[];
13
+ messages?: readonly string[];
14
+ boards?: readonly BoardKey[];
15
+ records?: readonly RecordRequest[];
16
+ }
17
+ export interface AcceptedSubscriptions {
18
+ events: string[];
19
+ variables: string[];
20
+ messages: string[];
21
+ boards: string[];
22
+ records: RecordRequest[];
23
+ }
24
+ export interface RejectedSubscription {
25
+ kind: 'event' | 'variable' | 'message' | 'board' | 'record';
26
+ name: string;
27
+ reason: 'unknown' | 'not_granted';
28
+ }
29
+ export interface OverlayFrame {
30
+ type: string;
31
+ [field: string]: unknown;
32
+ }
33
+ export interface OverlayRecord {
34
+ kind: RecordKind;
35
+ id: string;
36
+ revision: number;
37
+ data: unknown;
38
+ }
39
+ export type ConnectionStatus = 'idle' | 'connecting' | 'ready' | 'stale' | 'expired' | 'stopped';
40
+ export interface OverlayState {
41
+ status: ConnectionStatus;
42
+ revision: number | null;
43
+ variables: Readonly<Record<string, unknown>>;
44
+ records: Readonly<Record<string, OverlayRecord>>;
45
+ lastEventAt: number | null;
46
+ }
47
+ export interface Diagnostic {
48
+ code: 'subscription_rejected' | 'invalid_frame' | 'connection_closed' | 'connection_error' | 'handler_error' | 'unsupported_server' | 'access_expired' | 'record_unavailable';
49
+ message: string;
50
+ subscription?: RejectedSubscription;
51
+ }
52
+ export type Unsubscribe = () => void;
53
+ export interface OverlayReader {
54
+ readonly state: OverlayState;
55
+ start(): Promise<void>;
56
+ stop(): void;
57
+ onEvent(family: EventFamily, handler: (frame: OverlayFrame) => void): Unsubscribe;
58
+ onState(handler: (state: OverlayState) => void): Unsubscribe;
59
+ onDiagnostic(handler: (diagnostic: Diagnostic) => void): Unsubscribe;
60
+ getRecord(kind: RecordKind, id: string): Promise<OverlayRecord | null>;
61
+ }
62
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ /** Names of live frames available to normal Bloopbot widgets. */
2
+ export const EVENT_FAMILIES = [
3
+ 'adbreak', 'charity', 'chat', 'chathighlight', 'cheer', 'contributions',
4
+ 'countdown', 'credits', 'custom', 'emotewall', 'follow', 'gift', 'giveaway',
5
+ 'goal', 'hypetrain', 'jar', 'leaderboard', 'minigames', 'poll', 'prediction',
6
+ 'queue', 'raid', 'resub', 'slideshow', 'stage.alert', 'stage.media',
7
+ 'stage.minigame', 'sub', 'tickerline', 'wheel',
8
+ ];
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,eAAe;IACvE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU;IAC3E,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY;IAC5E,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa;IACnE,gBAAgB,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO;CACtC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@bloopbot/overlay-sdk",
3
+ "version": "0.1.0-rc.0",
4
+ "description": "Typed browser SDK for Bloopbot custom overlays",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "types": "./dist/index.d.ts",
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "node scripts/build.mjs",
22
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.tests.json",
23
+ "lint": "eslint src tests --ext .ts",
24
+ "test": "vitest run",
25
+ "test:coverage": "vitest run --coverage",
26
+ "pack:check": "npm run build && npm pack --dry-run --json"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^24.13.6",
30
+ "@types/ws": "^8.5.10",
31
+ "@typescript-eslint/eslint-plugin": "^7.13.0",
32
+ "@typescript-eslint/parser": "^7.13.0",
33
+ "@vitest/coverage-v8": "^5.0.2",
34
+ "esbuild": "^0.28.2",
35
+ "eslint": "^8.57.0",
36
+ "typescript": "^5.4.5",
37
+ "vitest": "^5.0.2",
38
+ "ws": "^8.18.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=22.12.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/BloopSoftware/overlay-sdk"
49
+ }
50
+ }