sparkle-react 0.0.50 → 0.0.52

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/dist/index.d.mts CHANGED
@@ -6,6 +6,7 @@ declare const sparkleConfig: z.ZodObject<{
6
6
  wsUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
7
7
  /** Projet Sparkle — requis pour `useTask` (invoke / invokeUntil côté runtime). */
8
8
  projectId: z.ZodOptional<z.ZodString>;
9
+ apiKey: z.ZodOptional<z.ZodString>;
9
10
  obs: z.ZodOptional<z.ZodObject<{
10
11
  clientId: z.ZodString;
11
12
  bridgeUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
@@ -20,6 +21,7 @@ declare const sparkleConfig: z.ZodObject<{
20
21
  userId: string;
21
22
  wsUrl: string;
22
23
  projectId?: string | undefined;
24
+ apiKey?: string | undefined;
23
25
  obs?: {
24
26
  clientId: string;
25
27
  bridgeUrl: string;
@@ -28,6 +30,7 @@ declare const sparkleConfig: z.ZodObject<{
28
30
  userId: string;
29
31
  wsUrl?: string | undefined;
30
32
  projectId?: string | undefined;
33
+ apiKey?: string | undefined;
31
34
  obs?: {
32
35
  clientId: string;
33
36
  bridgeUrl?: string | undefined;
@@ -484,9 +487,28 @@ type ChannelLabelsSnapshot = {
484
487
 
485
488
  /**
486
489
  * Agrégats chaîne (session, total, latest, recent) — même données que `ctx.labels.get()`.
487
- * Se met à jour quand un événement Twitch/SE pertinent arrive via `useHook`.
488
- * Requiert `config.projectId` sur `SparkleProvider`.
490
+ * Poussés en temps réel via WebSocket (`labels:snapshot`, comme `useRealtime`).
491
+ * Requiert `config.projectId` et `config.userId` sur `SparkleProvider`.
489
492
  */
490
493
  declare function useLabels(): ChannelLabelsSnapshot;
491
494
 
492
- export { type ChannelLabelCounts, type ChannelLabelLatest, type ChannelLabelRecent, type ChannelLabelsSnapshot, type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, type TwitchGetApiBody, type TwitchGetClient, type UseAuthResult, useAuth, useHook, useLabels, useRealtime, useTask, useTwitch };
495
+ interface SpotifyTrack {
496
+ isPlaying: boolean;
497
+ title: string | null;
498
+ artist: string | null;
499
+ album: string | null;
500
+ coverUrl: string | null;
501
+ durationMs: number;
502
+ progressMs: number;
503
+ }
504
+ interface UseSpotifyResult {
505
+ track: SpotifyTrack | null;
506
+ loading: boolean;
507
+ error: Error | null;
508
+ refresh: () => Promise<void>;
509
+ }
510
+ declare function useSpotify(options?: {
511
+ intervalMs?: number;
512
+ }): UseSpotifyResult;
513
+
514
+ export { type ChannelLabelCounts, type ChannelLabelLatest, type ChannelLabelRecent, type ChannelLabelsSnapshot, type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, type SpotifyTrack, type TwitchGetApiBody, type TwitchGetClient, type UseAuthResult, type UseSpotifyResult, useAuth, useHook, useLabels, useRealtime, useSpotify, useTask, useTwitch };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import b,{useMemo as ne,useEffect as oe,useState as se}from"react";import*as I from"react";import{createContext as A}from"react";var v=A({token:null}),T=({children:r,config:e})=>I.createElement(v.Provider,{value:{token:e.userId}},r);import V,{useCallback as x,useContext as z,useRef as H}from"react";import{createContext as J,useEffect as E,useState as B}from"react";import _ from"events";import l from"zod";var F=l.object({type:l.literal("subscribe"),channels:l.array(l.string())}),W=l.object({type:l.literal("hook"),channels:l.array(l.string())}),$=l.object({type:l.literal("unsubscribe"),channels:l.string()}),ze=l.object({event:l.literal("update"),type:l.enum(["value","hook"]),data:l.object({key:l.string(),value:l.string()})}),He=l.union([F,$,W]),y=class extends _{constructor(t){super();this.socket=null;this.queue=[];this.subscriptions=[];this.hooks=[];this.isAuthenticating=!1;this.jwt=null;this.url=t}send(t){this.socket?.readyState===WebSocket.OPEN?this.socket?.send(JSON.stringify(t)):this.queue.push(t)}authenticate(t){this.isAuthenticating||(this.isAuthenticating=!0,this.jwt=t,this.connect())}connect(){let t=this.url+"?jwt="+this.jwt;this.socket=new WebSocket(t),this.socket.onopen=()=>{this.queue.forEach(n=>this.send(n)),this.queue=[]},this.socket.onmessage=n=>{let o=JSON.parse(n.data);this.emit("update",o)},this.socket.onclose=()=>{this.reconnect()}}reconnect(){setTimeout(()=>{this.queue=[{type:"subscribe",channels:this.subscriptions},{type:"hook",channels:this.hooks}],console.log("try reconnecting"),this.connect()},5e3)}subscribe(t){let n=t.filter(o=>!this.subscriptions.includes(o));n.length&&(this.subscriptions=[...this.subscriptions,...n],console.log("subscribe",t),this.send({type:"subscribe",channels:t}))}hook(t){let n=[t].filter(o=>!this.hooks.includes(o));n.length&&(this.hooks=[...this.hooks,...n],this.send({type:"hook",channels:[t]}))}unsubscribe(t){this.send({type:"unsubscribe",channels:t})}disconnect(){this.socket&&(this.socket.onclose=()=>{},this.socket.close())}};var w=J({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),R=({children:r,config:e})=>{let[t,n]=B({}),[o,i]=B({}),s=H(null),{token:u}=z(v);E(()=>(s.current=new y(e.wsUrl),s.current.on("update",p=>{let{data:d,type:S}=p;if(S==="hook"){let f=JSON.parse(d.value);i(g=>({...g,[f.hook]:f.payload})),console.log("receive hooks",f)}else{let f=d.value;console.log("value",f);try{let g=parseFloat(d.value);if(!isNaN(g)&&String(g)===String(d.value).trim())f=g;else try{f=JSON.parse(d.value)}catch{f=d.value}}catch{f=d.value}n(g=>({...g,[d.key]:f})),console.log("receive values",t)}}),()=>{s.current?.disconnect()}),[]),E(()=>{!s.current||!u||s.current.authenticate(u)},[u]);let c=x(p=>{s.current?.subscribe(p)},[s]),a=x(p=>{s.current?.hook(p)},[s]);return V.createElement(w.Provider,{value:{subscribe:c,listen:a,values:t,hooks:o}},r)};import Y,{createContext as K,useCallback as Q,useEffect as X,useRef as U,useState as Z}from"react";var ee=K({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function L({url:r,children:e}){let[t,n]=Z("disconnected"),o=U(null),i=U(!1);X(()=>{if(!r)return;i.current=!1;let u;return(async()=>{let{default:a}=await import("obs-websocket-js"),p=new a;o.current=p;let d=async()=>{if(!i.current){n("connecting");try{await p.connect(r),i.current||n("connected")}catch{i.current||(n("error"),u=setTimeout(d,3e3))}}};p.on("ConnectionClosed",()=>{i.current||(n("disconnected"),u=setTimeout(d,3e3))}),p.on("ConnectionError",()=>{i.current||n("error")}),await d()})(),()=>{i.current=!0,clearTimeout(u),o.current?.disconnect(),o.current=null,n("disconnected")}},[r]);let s=Q(async(u,c)=>{let a=o.current;if(!a)throw new Error("OBS not connected");return a.call(u,c)},[]);return Y.createElement(ee.Provider,{value:{status:t,call:s}},e)}import{createContext as te,useContext as re}from"react";var C=te(null);function h(){let r=re(C);if(!r)throw new Error("Sparkle hooks require SparkleProvider");return r}var ie=({children:r})=>{let[e,t]=se(!1);return oe(()=>{t(!0)},[]),e?r:null},ae={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws"},dt=({children:r,config:e})=>{let t={...ae,...e},n=ne(()=>t.obs?.clientId?`${t.obs.bridgeUrl??"wss://sparkle-bot-v2.fly.dev/obs-bridge"}?role=client&clientId=${encodeURIComponent(t.obs.clientId)}`:"",[t.obs?.clientId,t.obs?.bridgeUrl]),o=b.createElement(C.Provider,{value:t},b.createElement(T,{config:t},b.createElement(R,{config:t},b.createElement(ie,null,r))));return n?b.createElement(L,{url:n},o):o};import{useContext as ce,useEffect as ue,useMemo as le}from"react";function ht(r,e){let t=ce(w),n=le(()=>[r],[r]);if(!t)throw new Error("You must use useRealtime inside a SparkleProvider");return ue(()=>{t.subscribe([r])},[n]),n.map(o=>t.values[o]??e)}import{useEffect as de,useState as P}from"react";var pe="sparkle-widget-session-request",fe="sparkle-widget-session-result";function ge(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function me(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function M(r){if(!r||typeof r!="object")return{userId:null,username:null,profilePicture:null};let e=r,t=typeof e.userId=="string"&&e.userId.trim()?e.userId:null,n=typeof e.username=="string"&&e.username.trim()?e.username:null,o=typeof e.profilePicture=="string"&&e.profilePicture.trim()?e.profilePicture:null;return{userId:t,username:n,profilePicture:o}}function he(){if(typeof window>"u")return Promise.resolve({userId:null,username:null,profilePicture:null});let r=window.top??window.parent;return!r||r===window?Promise.resolve({userId:null,username:null,profilePicture:null}):new Promise(e=>{let t=ge(),n=i=>{let s=i.data;!s||s.type!==fe||s.requestId!==t||(window.removeEventListener("message",n),window.clearTimeout(o),e(M(s)))},o=window.setTimeout(()=>{window.removeEventListener("message",n),e({userId:null,username:null,profilePicture:null})},12e3);window.addEventListener("message",n),r.postMessage({type:pe,requestId:t},"*")})}async function we(){let r=await fetch("/api/sparkle-widget-session",{method:"GET",credentials:"include"});if(!r.ok)return{userId:null,username:null,profilePicture:null};let e=await r.json().catch(()=>null);return M(e)}function yt(){let[r,e]=P(void 0),[t,n]=P(void 0),[o,i]=P(void 0);return de(()=>{let s=!1;return(async()=>{let c=me()?await he():await we();s||(e(c.userId),n(c.username),i(c.profilePicture))})(),()=>{s=!0}},[]),{userId:r,username:t,profilePicture:o}}import{useContext as be,useEffect as ye}from"react";var Pt=["notification.follow","notification.cheer","notification.tips","notification.subscribe","notification.subscribe.end","notification.subscribe.gift","notification.raid","chat.message","stream.online","stream.offline","stream.update","reward.create","reward.update","reward.remove","reward.redemption.claim","reward.redemption.update","poll.begin","poll.progress","poll.end","prediction.begin","prediction.progress","prediction.lock","prediction.end","moderator.add","moderator.remove","moderator.ban","moderator.unban","moderator.shield_mode.begin"];function m(r){let e=be(w);if(!e)throw new Error("You must use useHook inside a SparkleProvider");return ye(()=>{e.listen(r)},[r]),e.hooks[r]}import{useCallback as j,useMemo as ke}from"react";var Se="sparkle-invoke-task",ve="sparkle-invoke-task-result";function Ce(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function O(r,e){if(typeof window>"u")return Promise.reject(new Error("useTask requires a browser environment"));let t=window.top??window.parent;return!t||t===window?Promise.reject(new Error("useTask: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((n,o)=>{let i=Ce(),s=c=>{let a=c.data;!a||a.type!==ve||a.requestId!==i||(window.removeEventListener("message",s),window.clearTimeout(u),a.ok?n(!!a.result):o(new Error(typeof a.error=="string"?a.error:"invoke_failed")))},u=window.setTimeout(()=>{window.removeEventListener("message",s),o(new Error("useTask: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",s),t.postMessage({type:Se,requestId:i,projectId:r,...e},"*")})}function q(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}async function G(r,e){let t=await fetch(`/api/projects/${r}/invoke-task`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>null);throw new Error(n?.error??t.statusText)}return!0}function Et(){let e=h().projectId,t=j(async(o,i)=>{if(!e?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let s={kind:"invoke",taskId:o,payload:i};return q()?O(e,s):G(e,s)},[e]),n=j(async(o,i,s)=>{if(!e?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let u={kind:"invokeUntil",taskId:o,payload:s,delaySeconds:i};return q()?O(e,u):G(e,u)},[e]);return ke(()=>({invoke:t,invokeUntil:n}),[t,n])}import{useCallback as Pe,useMemo as Ie}from"react";function N(r){let e=(t,n)=>(...o)=>r(t,n,o);return{bits:{getLeaderboard:e("bits","getLeaderboard")},channelPoints:{getCustomRewards:e("channelPoints","getCustomRewards"),getCustomRewardsByIds:e("channelPoints","getCustomRewardsByIds"),getCustomRewardById:e("channelPoints","getCustomRewardById"),getRedemptionsByIds:e("channelPoints","getRedemptionsByIds"),getRedemptionById:e("channelPoints","getRedemptionById"),getRedemptions:e("channelPoints","getRedemptions")},channel:{getChannelInfoById:e("channel","getChannelInfoById"),getFollowDate:e("channel","getFollowDate"),getChannelEditors:e("channel","getChannelEditors"),getVips:e("channel","getVips"),checkVipForUsers:e("channel","checkVipForUsers"),checkVipForUser:e("channel","checkVipForUser"),getChannelFollowerCount:e("channel","getChannelFollowerCount")},chat:{getChatters:e("chat","getChatters"),getSettings:e("chat","getSettings"),getSettingsPrivileged:e("chat","getSettingsPrivileged"),getColorsForUsers:e("chat","getColorsForUsers"),getColorForUser:e("chat","getColorForUser")},clips:{getClips:e("clips","getClips"),getClipById:e("clips","getClipById"),getClipsByIds:e("clips","getClipsByIds")},goals:{getGoals:e("goals","getGoals")},polls:{getPolls:e("polls","getPolls"),getPollsByIds:e("polls","getPollsByIds"),getPollById:e("polls","getPollById")},predictions:{getPredictions:e("predictions","getPredictions"),getPredictionsByIds:e("predictions","getPredictionsByIds"),getPredictionById:e("predictions","getPredictionById")},streams:{getStream:e("streams","getStream"),getStreamMarkers:e("streams","getStreamMarkers")},subscriptions:{getSubscriptions:e("subscriptions","getSubscriptions"),getSubscriptionsForUsers:e("subscriptions","getSubscriptionsForUsers"),getSubscriptionForUser:e("subscriptions","getSubscriptionForUser"),checkUserSubscription:e("subscriptions","checkUserSubscription")},moderation:{getBanned:e("moderation","getBanned"),checkBan:e("moderation","checkBan"),getModerators:e("moderation","getModerators"),checkModerator:e("moderation","checkModerator"),getBlockedTerms:e("moderation","getBlockedTerms")},utils:{getUserById:e("utils","getUserById"),getUserByName:e("utils","getUserByName"),getUserByIds:e("utils","getUserByIds"),getUserByNames:e("utils","getUserByNames")}}}var Te="sparkle-twitch-get",xe="sparkle-twitch-get-result";function Ee(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function Be(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function Re(r,e){if(typeof window>"u")return Promise.reject(new Error("useTwitch requires a browser environment"));let t=window.top??window.parent;return!t||t===window?Promise.reject(new Error("useTwitch: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((n,o)=>{let i=Ee(),s=c=>{let a=c.data;!a||a.type!==xe||a.requestId!==i||(window.removeEventListener("message",s),window.clearTimeout(u),a.ok?n(a.result):o(new Error(typeof a.error=="string"?a.error:"twitch_get_failed")))},u=window.setTimeout(()=>{window.removeEventListener("message",s),o(new Error("useTwitch: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",s),t.postMessage({type:Te,requestId:i,projectId:r,...e},"*")})}async function Ue(r,e){let t=await fetch(`/api/projects/${encodeURIComponent(r)}/twitch-get`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let o=await t.json().catch(()=>null);throw new Error(o?.error??t.statusText)}return(await t.json()).result}function jt(){let e=h().projectId,t=Pe(async(n,o,i)=>{if(!e?.trim())throw new Error("useTwitch: renseignez `config.projectId` sur SparkleProvider.");let s={path:[n,o],args:i};return Be()?Re(e,s):Ue(e,s)},[e]);return Ie(()=>N(t),[t])}import{useCallback as Le,useEffect as D,useRef as Me,useState as je}from"react";var k=()=>({session:{followers:0,subscribers:0,cheers:0,tips:0},total:{followers:0,subscribers:0,cheers:0,tips:0},latest:{followers:"",subscribers:"",cheers:{amount:0,user:""},tips:{amount:0,user:""}},recent:{followers:[],subscribers:[],cheers:[],tips:[]}});var Oe="sparkle-labels-get",qe="sparkle-labels-get-result";function Ge(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function Ne(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function De(r){if(typeof window>"u")return Promise.reject(new Error("useLabels requires a browser environment"));let e=window.top??window.parent;return!e||e===window?Promise.reject(new Error("useLabels: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((t,n)=>{let o=Ge(),i=u=>{let c=u.data;!c||c.type!==qe||c.requestId!==o||(window.removeEventListener("message",i),window.clearTimeout(s),c.ok&&c.labels?t(c.labels):n(new Error(typeof c.error=="string"?c.error:"labels_get_failed")))},s=window.setTimeout(()=>{window.removeEventListener("message",i),n(new Error("useLabels: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",i),e.postMessage({type:Oe,requestId:o,projectId:r},"*")})}async function Ae(r){let e=await fetch(`/api/projects/${encodeURIComponent(r)}/labels`,{method:"GET",credentials:"include"});if(!e.ok){let n=await e.json().catch(()=>null);throw new Error(n?.error??e.statusText)}return(await e.json()).labels??k()}async function _e(r){return Ne()?De(r):Ae(r)}function _t(){let e=h().projectId?.trim()??"",[t,n]=je(k),o=Me(0),i=m("notification.follow"),s=m("notification.subscribe"),u=m("notification.subscribe.gift"),c=m("notification.cheer"),a=m("notification.tips"),p=m("stream.offline"),d=Le(async()=>{if(!e)return;let S=++o.current;try{let f=await _e(e);S===o.current&&n(f)}catch{}},[e]);return D(()=>{if(!e){n(k());return}d()},[e,d]),D(()=>{e&&d()},[e,d,i,s,u,c,a,p]),t}export{Pt as SparkleHookKeys,dt as SparkleProvider,yt as useAuth,m as useHook,_t as useLabels,ht as useRealtime,Et as useTask,jt as useTwitch};
1
+ import b,{useMemo as ce,useEffect as ue,useState as le}from"react";import*as x from"react";import{createContext as z}from"react";var v=z({token:null}),R=({children:r,config:e})=>x.createElement(v.Provider,{value:{token:e.userId}},r);import K,{useCallback as B,useContext as Q,useRef as X}from"react";import{createContext as Z,useEffect as U,useState as L}from"react";import V from"events";import d from"zod";var H=d.object({type:d.literal("subscribe"),channels:d.array(d.string())}),J=d.object({type:d.literal("hook"),channels:d.array(d.string())}),Y=d.object({type:d.literal("unsubscribe"),channels:d.string()}),ot=d.object({event:d.literal("update"),type:d.enum(["value","hook"]),data:d.object({key:d.string(),value:d.string()})}),it=d.union([H,Y,J]),y=class extends V{constructor(t){super();this.socket=null;this.queue=[];this.subscriptions=[];this.hooks=[];this.isAuthenticating=!1;this.jwt=null;this.url=t}send(t){this.socket?.readyState===WebSocket.OPEN?this.socket?.send(JSON.stringify(t)):this.queue.push(t)}authenticate(t){this.isAuthenticating||(this.isAuthenticating=!0,this.jwt=t,this.connect())}connect(){let t=this.url+"?jwt="+this.jwt;this.socket=new WebSocket(t),this.socket.onopen=()=>{this.queue.forEach(n=>this.send(n)),this.queue=[]},this.socket.onmessage=n=>{let s=JSON.parse(n.data);this.emit("update",s)},this.socket.onclose=()=>{this.reconnect()}}reconnect(){setTimeout(()=>{this.queue=[{type:"subscribe",channels:this.subscriptions},{type:"hook",channels:this.hooks}],console.log("try reconnecting"),this.connect()},5e3)}subscribe(t){let n=t.filter(s=>!this.subscriptions.includes(s));n.length&&(this.subscriptions=[...this.subscriptions,...n],console.log("subscribe",t),this.send({type:"subscribe",channels:t}))}hook(t){let n=[t].filter(s=>!this.hooks.includes(s));n.length&&(this.hooks=[...this.hooks,...n],this.send({type:"hook",channels:[t]}))}unsubscribe(t){this.send({type:"unsubscribe",channels:t})}disconnect(){this.socket&&(this.socket.onclose=()=>{},this.socket.close())}};var h=Z({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),M=({children:r,config:e})=>{let[t,n]=L({}),[s,o]=L({}),i=X(null),{token:u}=Q(v);U(()=>(i.current=new y(e.wsUrl),i.current.on("update",p=>{let{data:l,type:m}=p;if(m==="hook"){let f=JSON.parse(l.value);o(g=>({...g,[f.hook]:f.payload})),console.log("receive hooks",f)}else{let f=l.value;console.log("value",f);try{let g=parseFloat(l.value);if(!isNaN(g)&&String(g)===String(l.value).trim())f=g;else try{f=JSON.parse(l.value)}catch{f=l.value}}catch{f=l.value}n(g=>({...g,[l.key]:f})),console.log("receive values",t)}}),()=>{i.current?.disconnect()}),[]),U(()=>{!i.current||!u||i.current.authenticate(u)},[u]);let a=B(p=>{i.current?.subscribe(p)},[i]),c=B(p=>{i.current?.hook(p)},[i]);return K.createElement(h.Provider,{value:{subscribe:a,listen:c,values:t,hooks:s}},r)};import ee,{createContext as te,useCallback as re,useEffect as ne,useRef as j,useState as se}from"react";var oe=te({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function q({url:r,children:e}){let[t,n]=se("disconnected"),s=j(null),o=j(!1);ne(()=>{if(!r)return;o.current=!1;let u;return(async()=>{let{default:c}=await import("obs-websocket-js"),p=new c;s.current=p;let l=async()=>{if(!o.current){n("connecting");try{await p.connect(r),o.current||n("connected")}catch{o.current||(n("error"),u=setTimeout(l,3e3))}}};p.on("ConnectionClosed",()=>{o.current||(n("disconnected"),u=setTimeout(l,3e3))}),p.on("ConnectionError",()=>{o.current||n("error")}),await l()})(),()=>{o.current=!0,clearTimeout(u),s.current?.disconnect(),s.current=null,n("disconnected")}},[r]);let i=re(async(u,a)=>{let c=s.current;if(!c)throw new Error("OBS not connected");return c.call(u,a)},[]);return ee.createElement(oe.Provider,{value:{status:t,call:i}},e)}import{createContext as ie,useContext as ae}from"react";var C=ie(null);function w(){let r=ae(C);if(!r)throw new Error("Sparkle hooks require SparkleProvider");return r}var de=({children:r})=>{let[e,t]=le(!1);return ue(()=>{t(!0)},[]),e?r:null},pe={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws"},Pt=({children:r,config:e})=>{let t={...pe,...e},n=ce(()=>t.obs?.clientId?`${t.obs.bridgeUrl??"wss://sparkle-bot-v2.fly.dev/obs-bridge"}?role=client&clientId=${encodeURIComponent(t.obs.clientId)}`:"",[t.obs?.clientId,t.obs?.bridgeUrl]),s=b.createElement(C.Provider,{value:t},b.createElement(R,{config:t},b.createElement(M,{config:t},b.createElement(de,null,r))));return n?b.createElement(q,{url:n},s):s};import{useContext as fe,useEffect as ge,useMemo as me}from"react";function Rt(r,e){let t=fe(h),n=me(()=>[r],[r]);if(!t)throw new Error("You must use useRealtime inside a SparkleProvider");return ge(()=>{t.subscribe([r])},[n]),n.map(s=>t.values[s]??e)}import{useEffect as we,useState as P}from"react";var he="sparkle-widget-session-request",be="sparkle-widget-session-result";function ye(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function ke(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function O(r){if(!r||typeof r!="object")return{userId:null,username:null,profilePicture:null};let e=r,t=typeof e.userId=="string"&&e.userId.trim()?e.userId:null,n=typeof e.username=="string"&&e.username.trim()?e.username:null,s=typeof e.profilePicture=="string"&&e.profilePicture.trim()?e.profilePicture:null;return{userId:t,username:n,profilePicture:s}}function Se(){if(typeof window>"u")return Promise.resolve({userId:null,username:null,profilePicture:null});let r=window.top??window.parent;return!r||r===window?Promise.resolve({userId:null,username:null,profilePicture:null}):new Promise(e=>{let t=ye(),n=o=>{let i=o.data;!i||i.type!==be||i.requestId!==t||(window.removeEventListener("message",n),window.clearTimeout(s),e(O(i)))},s=window.setTimeout(()=>{window.removeEventListener("message",n),e({userId:null,username:null,profilePicture:null})},12e3);window.addEventListener("message",n),r.postMessage({type:he,requestId:t},"*")})}async function ve(){let r=await fetch("/api/sparkle-widget-session",{method:"GET",credentials:"include"});if(!r.ok)return{userId:null,username:null,profilePicture:null};let e=await r.json().catch(()=>null);return O(e)}function Lt(){let[r,e]=P(void 0),[t,n]=P(void 0),[s,o]=P(void 0);return we(()=>{let i=!1;return(async()=>{let a=ke()?await Se():await ve();i||(e(a.userId),n(a.username),o(a.profilePicture))})(),()=>{i=!0}},[]),{userId:r,username:t,profilePicture:s}}import{useContext as Ce,useEffect as Pe}from"react";var Gt=["notification.follow","notification.cheer","notification.tips","notification.subscribe","notification.subscribe.end","notification.subscribe.gift","notification.raid","chat.message","stream.online","stream.offline","stream.update","reward.create","reward.update","reward.remove","reward.redemption.claim","reward.redemption.update","poll.begin","poll.progress","poll.end","prediction.begin","prediction.progress","prediction.lock","prediction.end","moderator.add","moderator.remove","moderator.ban","moderator.unban","moderator.shield_mode.begin"];function Nt(r){let e=Ce(h);if(!e)throw new Error("You must use useHook inside a SparkleProvider");return Pe(()=>{e.listen(r)},[r]),e.hooks[r]}import{useCallback as G,useMemo as Ie}from"react";var Te="sparkle-invoke-task",Ee="sparkle-invoke-task-result";function xe(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function N(r,e){if(typeof window>"u")return Promise.reject(new Error("useTask requires a browser environment"));let t=window.top??window.parent;return!t||t===window?Promise.reject(new Error("useTask: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((n,s)=>{let o=xe(),i=a=>{let c=a.data;!c||c.type!==Ee||c.requestId!==o||(window.removeEventListener("message",i),window.clearTimeout(u),c.ok?n(!!c.result):s(new Error(typeof c.error=="string"?c.error:"invoke_failed")))},u=window.setTimeout(()=>{window.removeEventListener("message",i),s(new Error("useTask: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",i),t.postMessage({type:Te,requestId:o,projectId:r,...e},"*")})}function D(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}async function _(r,e){let t=await fetch(`/api/projects/${r}/invoke-task`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let n=await t.json().catch(()=>null);throw new Error(n?.error??t.statusText)}return!0}function Ft(){let e=w().projectId,t=G(async(s,o)=>{if(!e?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let i={kind:"invoke",taskId:s,payload:o};return D()?N(e,i):_(e,i)},[e]),n=G(async(s,o,i)=>{if(!e?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let u={kind:"invokeUntil",taskId:s,payload:i,delaySeconds:o};return D()?N(e,u):_(e,u)},[e]);return Ie(()=>({invoke:t,invokeUntil:n}),[t,n])}import{useCallback as Re,useMemo as Be}from"react";function A(r){let e=(t,n)=>(...s)=>r(t,n,s);return{bits:{getLeaderboard:e("bits","getLeaderboard")},channelPoints:{getCustomRewards:e("channelPoints","getCustomRewards"),getCustomRewardsByIds:e("channelPoints","getCustomRewardsByIds"),getCustomRewardById:e("channelPoints","getCustomRewardById"),getRedemptionsByIds:e("channelPoints","getRedemptionsByIds"),getRedemptionById:e("channelPoints","getRedemptionById"),getRedemptions:e("channelPoints","getRedemptions")},channel:{getChannelInfoById:e("channel","getChannelInfoById"),getFollowDate:e("channel","getFollowDate"),getChannelEditors:e("channel","getChannelEditors"),getVips:e("channel","getVips"),checkVipForUsers:e("channel","checkVipForUsers"),checkVipForUser:e("channel","checkVipForUser"),getChannelFollowerCount:e("channel","getChannelFollowerCount")},chat:{getChatters:e("chat","getChatters"),getSettings:e("chat","getSettings"),getSettingsPrivileged:e("chat","getSettingsPrivileged"),getColorsForUsers:e("chat","getColorsForUsers"),getColorForUser:e("chat","getColorForUser")},clips:{getClips:e("clips","getClips"),getClipById:e("clips","getClipById"),getClipsByIds:e("clips","getClipsByIds")},goals:{getGoals:e("goals","getGoals")},polls:{getPolls:e("polls","getPolls"),getPollsByIds:e("polls","getPollsByIds"),getPollById:e("polls","getPollById")},predictions:{getPredictions:e("predictions","getPredictions"),getPredictionsByIds:e("predictions","getPredictionsByIds"),getPredictionById:e("predictions","getPredictionById")},streams:{getStream:e("streams","getStream"),getStreamMarkers:e("streams","getStreamMarkers")},subscriptions:{getSubscriptions:e("subscriptions","getSubscriptions"),getSubscriptionsForUsers:e("subscriptions","getSubscriptionsForUsers"),getSubscriptionForUser:e("subscriptions","getSubscriptionForUser"),checkUserSubscription:e("subscriptions","checkUserSubscription")},moderation:{getBanned:e("moderation","getBanned"),checkBan:e("moderation","checkBan"),getModerators:e("moderation","getModerators"),checkModerator:e("moderation","checkModerator"),getBlockedTerms:e("moderation","getBlockedTerms")},utils:{getUserById:e("utils","getUserById"),getUserByName:e("utils","getUserByName"),getUserByIds:e("utils","getUserByIds"),getUserByNames:e("utils","getUserByNames")}}}var Ue="sparkle-twitch-get",Le="sparkle-twitch-get-result";function Me(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function je(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function qe(r,e){if(typeof window>"u")return Promise.reject(new Error("useTwitch requires a browser environment"));let t=window.top??window.parent;return!t||t===window?Promise.reject(new Error("useTwitch: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((n,s)=>{let o=Me(),i=a=>{let c=a.data;!c||c.type!==Le||c.requestId!==o||(window.removeEventListener("message",i),window.clearTimeout(u),c.ok?n(c.result):s(new Error(typeof c.error=="string"?c.error:"twitch_get_failed")))},u=window.setTimeout(()=>{window.removeEventListener("message",i),s(new Error("useTwitch: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",i),t.postMessage({type:Ue,requestId:o,projectId:r,...e},"*")})}async function Oe(r,e){let t=await fetch(`/api/projects/${encodeURIComponent(r)}/twitch-get`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok){let s=await t.json().catch(()=>null);throw new Error(s?.error??t.statusText)}return(await t.json()).result}function Jt(){let e=w().projectId,t=Re(async(n,s,o)=>{if(!e?.trim())throw new Error("useTwitch: renseignez `config.projectId` sur SparkleProvider.");let i={path:[n,s],args:o};return je()?qe(e,i):Oe(e,i)},[e]);return Be(()=>A(t),[t])}import{useCallback as Ne,useContext as De,useEffect as T,useRef as _e,useState as Ae}from"react";function F(r){if(!r||typeof r!="object")return null;let e=r,t=n=>{let s=Number(e[n]);return Number.isFinite(s)?Math.max(0,Math.floor(s)):0};return{followers:t("followers"),subscribers:t("subscribers"),cheers:t("cheers"),tips:t("tips")}}function Ge(r){if(!r||typeof r!="object")return null;let e=r,t=e.cheers&&typeof e.cheers=="object"?e.cheers:null,n=e.tips&&typeof e.tips=="object"?e.tips:null;return{followers:typeof e.followers=="string"?e.followers:"",subscribers:typeof e.subscribers=="string"?e.subscribers:"",cheers:{amount:t&&Number.isFinite(Number(t.amount))?Math.max(0,Math.floor(Number(t.amount))):0,user:t&&typeof t.user=="string"?t.user:""},tips:{amount:n&&Number.isFinite(Number(n.amount))?Number(n.amount):0,user:n&&typeof n.user=="string"?n.user:""}}}function k(r){return Array.isArray(r)?r.filter(e=>typeof e=="string"):[]}function I(r){if(!r||typeof r!="object")return null;let e=r,t=F(e.session),n=F(e.total),s=Ge(e.latest),o=e.recent&&typeof e.recent=="object"?e.recent:null;return!t||!n||!s||!o?null:{session:t,total:n,latest:s,recent:{followers:k(o.followers),subscribers:k(o.subscribers),cheers:k(o.cheers),tips:k(o.tips)}}}var S=()=>({session:{followers:0,subscribers:0,cheers:0,tips:0},total:{followers:0,subscribers:0,cheers:0,tips:0},latest:{followers:"",subscribers:"",cheers:{amount:0,user:""},tips:{amount:0,user:""}},recent:{followers:[],subscribers:[],cheers:[],tips:[]}});var W="labels:snapshot",Fe="sparkle-labels-get",We="sparkle-labels-get-result";function $e(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function ze(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function Ve(r){if(typeof window>"u")return Promise.reject(new Error("useLabels requires a browser environment"));let e=window.top??window.parent;return!e||e===window?Promise.reject(new Error("useLabels: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((t,n)=>{let s=$e(),o=u=>{let a=u.data;!a||a.type!==We||a.requestId!==s||(window.removeEventListener("message",o),window.clearTimeout(i),a.ok&&a.labels?t(a.labels):n(new Error(typeof a.error=="string"?a.error:"labels_get_failed")))},i=window.setTimeout(()=>{window.removeEventListener("message",o),n(new Error("useLabels: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",o),e.postMessage({type:Fe,requestId:s,projectId:r},"*")})}async function He(r){let e=await fetch(`/api/projects/${encodeURIComponent(r)}/labels`,{method:"GET",credentials:"include"});if(!e.ok){let n=await e.json().catch(()=>null);throw new Error(n?.error??e.statusText)}return(await e.json()).labels??S()}async function Je(r,e){return e?Ve(r):He(r)}function tr(){let r=w(),e=r.projectId?.trim()??"",t=r.userId?.trim()??"",n=De(h),s=!!t,o=ze()&&!s,[i,u]=Ae(S),a=_e(0),c=n.values[W];T(()=>{s&&n.subscribe([W])},[s,n]),T(()=>{if(!s)return;let l=I(c);l&&u(l)},[s,c]);let p=Ne(async()=>{if(!e)return;let l=++a.current;try{let m=await Je(e,o);l===a.current&&u(m)}catch{}},[e,o]);return T(()=>{if(!e){u(S());return}if(s){I(c)||p();return}p()},[e,p,s,c]),i}import{useCallback as Ye,useEffect as $,useRef as Ke,useState as E}from"react";function Qe(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function Xe(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function Ze(r){if(typeof window>"u")return Promise.reject(new Error("useSpotify requires a browser environment"));let e=window.top??window.parent;return!e||e===window?Promise.reject(new Error("useSpotify: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((t,n)=>{let s=Qe(),o=u=>{let a=u.data;!a||a.type!=="sparkle-spotify-get-result"||a.requestId!==s||(window.removeEventListener("message",o),window.clearTimeout(i),a.ok?t(a.track):n(new Error(typeof a.error=="string"?a.error:"spotify_get_failed")))},i=window.setTimeout(()=>{window.removeEventListener("message",o),n(new Error("useSpotify: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",o),e.postMessage({type:"sparkle-spotify-get",requestId:s,projectId:r},"*")})}async function et(r){let e=await fetch(`/api/projects/${encodeURIComponent(r)}/spotify`,{method:"GET",credentials:"include"});if(!e.ok){let n=await e.json().catch(()=>null);throw new Error(n?.error??e.statusText)}return(await e.json()).track??null}function or(r){let t=w().projectId?.trim()??"",n=r?.intervalMs??5e3,s=Xe(),[o,i]=E(null),[u,a]=E(!0),[c,p]=E(null),l=Ke(0),m=Ye(async()=>{if(!t){i(null),a(!1);return}let f=++l.current;try{let g=s?await Ze(t):await et(t);f===l.current&&(i(g),p(null))}catch(g){f===l.current&&p(g instanceof Error?g:new Error(String(g)))}finally{f===l.current&&a(!1)}},[t,s]);return $(()=>{m()},[m]),$(()=>{if(n<=0||!t)return;let f=setInterval(()=>{m()},n);return()=>clearInterval(f)},[m,n,t]),{track:o,loading:u,error:c,refresh:m}}export{Gt as SparkleHookKeys,Pt as SparkleProvider,Lt as useAuth,Nt as useHook,tr as useLabels,Rt as useRealtime,or as useSpotify,Ft as useTask,Jt as useTwitch};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/provider.tsx","../src/providers/authProvider.tsx","../src/providers/realtimeProvider.tsx","../src/lib/websocket.ts","../src/providers/obsProvider.tsx","../src/sparkle-config-context.tsx","../src/hooks/useRealtime.ts","../src/hooks/useAuth.ts","../src/hooks/useHook.ts","../src/hooks/useTask.ts","../src/hooks/useTwitch.ts","../src/twitch/create-twitch-get-client.ts","../src/hooks/useLabels.ts","../src/types/labels.ts"],"sourcesContent":["import React, { useMemo, type ReactNode, useEffect, useState } from \"react\"\nimport { AuthProvider } from \"./providers/authProvider\"\nimport { WebSocketProvider } from \"./providers/realtimeProvider\"\nimport { ObsProvider } from \"./providers/obsProvider\"\nimport { SparkleConfigContext } from \"./sparkle-config-context\"\nimport { SparkleConfig } from \"./utils/config\"\n\nconst ClientGate = ({ children }: { children: ReactNode }) => {\n const [ready, setReady] = useState(false)\n\n useEffect(() => {\n setReady(true)\n }, [])\n\n if (!ready) return null\n return children\n}\n\nexport interface SparkleProviderProps {\n config?: SparkleConfig\n children: ReactNode\n}\n\nconst defaultConfig: SparkleConfig = {\n userId: \"\",\n wsUrl: \"wss://sparkle-bot-v2.fly.dev/ws\",\n}\n\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\n const mergedConfig = { ...defaultConfig, ...config }\n\n const obsUrl = useMemo(() => {\n if (!mergedConfig.obs?.clientId) return \"\"\n const base =\n mergedConfig.obs.bridgeUrl ?? \"wss://sparkle-bot-v2.fly.dev/obs-bridge\"\n return `${base}?role=client&clientId=${encodeURIComponent(mergedConfig.obs.clientId)}`\n }, [mergedConfig.obs?.clientId, mergedConfig.obs?.bridgeUrl])\n\n const inner = (\n <SparkleConfigContext.Provider value={mergedConfig}>\n <AuthProvider config={mergedConfig}>\n <WebSocketProvider config={mergedConfig}>\n <ClientGate>{children}</ClientGate>\n </WebSocketProvider>\n </AuthProvider>\n </SparkleConfigContext.Provider>\n )\n\n if (obsUrl) {\n return <ObsProvider url={obsUrl}>{inner}</ObsProvider>\n }\n\n return inner\n}\n","import * as React from \"react\"\r\n\r\nimport { type ReactNode, createContext } from \"react\"\r\n\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\ninterface AuthContextType {\r\n token: string | undefined | null\r\n}\r\n\r\nexport const SparkleAuthContext = createContext<AuthContextType>({\r\n token: null,\r\n})\r\n\r\ninterface AuthProviderProps {\r\n config: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nexport const AuthProvider = ({ children, config }: AuthProviderProps) => {\r\n return (\r\n <SparkleAuthContext.Provider\r\n value={{\r\n token: config.userId,\r\n }}\r\n >\r\n {children}\r\n </SparkleAuthContext.Provider>\r\n )\r\n}\r\n","import React, { ReactNode, useCallback, useContext, useRef } from \"react\"\r\n\r\nimport { createContext, useEffect, useState } from \"react\"\r\nimport { SparkleAuthContext } from \"./authProvider\"\r\nimport { UpdateMessage, WebSocketClient } from \"../lib/websocket\"\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\ninterface WebSocketContextType {\r\n subscribe: (keys: string[]) => void\r\n listen: (hook: string) => void\r\n values: Record<string, any>\r\n hooks: Record<string, any>\r\n}\r\n\r\nexport const RealtimeContext = createContext<WebSocketContextType>({\r\n subscribe: () => {},\r\n listen: () => {},\r\n values: {},\r\n hooks: {},\r\n})\r\n\r\nexport type RealtimeValue = string | number | boolean | { [key: string]: any }\r\n\r\ntype State = Record<string, RealtimeValue>\r\n\r\ninterface WebSocketProviderProps {\r\n config: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nexport const WebSocketProvider = ({\r\n children,\r\n config,\r\n}: WebSocketProviderProps) => {\r\n const [values, setValues] = useState<State>({})\r\n const [hooks, setHooks] = useState<Record<string, any>>({})\r\n\r\n const socket = useRef<WebSocketClient>(null)\r\n\r\n const { token } = useContext(SparkleAuthContext)\r\n\r\n useEffect(() => {\r\n socket.current = new WebSocketClient(config.wsUrl)\r\n\r\n socket.current.on(\"update\", (message: UpdateMessage) => {\r\n const { data, type } = message\r\n\r\n if (type === \"hook\") {\r\n const payload = JSON.parse(data.value)\r\n\r\n setHooks((prev) => ({\r\n ...prev,\r\n [payload.hook]: payload.payload,\r\n }))\r\n console.log(\"receive hooks\", payload)\r\n } else {\r\n let value: any = data.value\r\n\r\n console.log(\"value\", value)\r\n\r\n try {\r\n const num = parseFloat(data.value)\r\n if (!isNaN(num) && String(num) === String(data.value).trim()) {\r\n value = num\r\n } else {\r\n try {\r\n value = JSON.parse(data.value)\r\n } catch {\r\n value = data.value\r\n }\r\n }\r\n } catch {\r\n value = data.value\r\n }\r\n\r\n setValues((prev) => ({ ...prev, [data.key]: value }))\r\n console.log(\"receive values\", values)\r\n }\r\n })\r\n\r\n return () => {\r\n socket.current?.disconnect()\r\n }\r\n }, [])\r\n\r\n useEffect(() => {\r\n if (!socket.current || !token) return\r\n\r\n socket.current.authenticate(token)\r\n }, [token])\r\n\r\n const subscribe = useCallback(\r\n (keys: string[]) => {\r\n socket.current?.subscribe(keys)\r\n },\r\n [socket]\r\n )\r\n\r\n const listen = useCallback(\r\n (hook: string) => {\r\n socket.current?.hook(hook)\r\n },\r\n [socket]\r\n )\r\n\r\n return (\r\n <RealtimeContext.Provider\r\n value={{\r\n subscribe,\r\n listen,\r\n values,\r\n hooks,\r\n }}\r\n >\r\n {children}\r\n </RealtimeContext.Provider>\r\n )\r\n}\r\n","import EventEmitter from \"events\"\r\nimport z from \"zod\"\r\n\r\nconst subscribeSchema = z.object({\r\n type: z.literal(\"subscribe\"),\r\n channels: z.array(z.string()),\r\n})\r\n\r\nconst hookSchema = z.object({\r\n type: z.literal(\"hook\"),\r\n channels: z.array(z.string()),\r\n})\r\n\r\nconst unsubscribeSchema = z.object({\r\n type: z.literal(\"unsubscribe\"),\r\n channels: z.string(),\r\n})\r\n\r\nconst updateSchema = z.object({\r\n event: z.literal(\"update\"),\r\n type: z.enum([\"value\", \"hook\"]),\r\n data: z.object({\r\n key: z.string(),\r\n value: z.string(),\r\n }),\r\n})\r\n\r\nconst messageSchema = z.union([subscribeSchema, unsubscribeSchema, hookSchema])\r\n\r\nexport type Message = z.infer<typeof messageSchema>\r\n\r\nexport type UpdateMessage = z.infer<typeof updateSchema>\r\nexport type SubscribeMessage = z.infer<typeof subscribeSchema>\r\nexport type UnsubscribeMessage = z.infer<typeof unsubscribeSchema>\r\n\r\nexport class WebSocketClient extends EventEmitter {\r\n private socket: WebSocket | null = null\r\n private queue: Message[] = []\r\n private subscriptions: string[] = []\r\n private hooks: string[] = []\r\n private isAuthenticating = false\r\n\r\n private url: string\r\n private jwt: string | null = null\r\n\r\n constructor(url: string) {\r\n super()\r\n this.url = url\r\n }\r\n\r\n private send(data: Message) {\r\n if (this.socket?.readyState === WebSocket.OPEN) {\r\n this.socket?.send(JSON.stringify(data))\r\n } else {\r\n this.queue.push(data)\r\n }\r\n }\r\n\r\n authenticate(jwt: string) {\r\n if (this.isAuthenticating) return\r\n this.isAuthenticating = true\r\n this.jwt = jwt\r\n this.connect()\r\n }\r\n\r\n private connect() {\r\n const authenticatedUrl = this.url + \"?jwt=\" + this.jwt\r\n this.socket = new WebSocket(authenticatedUrl)\r\n\r\n this.socket.onopen = () => {\r\n this.queue.forEach((data) => this.send(data))\r\n this.queue = []\r\n }\r\n\r\n this.socket.onmessage = (event) => {\r\n const data = JSON.parse(event.data)\r\n this.emit(\"update\", data)\r\n }\r\n\r\n this.socket.onclose = () => {\r\n this.reconnect()\r\n }\r\n }\r\n\r\n reconnect() {\r\n setTimeout(() => {\r\n this.queue = [\r\n {\r\n type: \"subscribe\",\r\n channels: this.subscriptions,\r\n },\r\n {\r\n type: \"hook\",\r\n channels: this.hooks,\r\n },\r\n ]\r\n\r\n console.log(\"try reconnecting\")\r\n\r\n this.connect()\r\n }, 5000)\r\n }\r\n\r\n subscribe(channels: string[]) {\r\n const newChannels = channels.filter(\r\n (channel) => !this.subscriptions.includes(channel),\r\n )\r\n\r\n if (!newChannels.length) return\r\n this.subscriptions = [...this.subscriptions, ...newChannels]\r\n\r\n console.log(\"subscribe\", channels)\r\n\r\n this.send({\r\n type: \"subscribe\",\r\n channels,\r\n })\r\n }\r\n\r\n hook(hook: string) {\r\n const newChannels = [hook].filter(\r\n (channel) => !this.hooks.includes(channel),\r\n )\r\n\r\n if (!newChannels.length) return\r\n this.hooks = [...this.hooks, ...newChannels]\r\n\r\n this.send({\r\n type: \"hook\",\r\n channels: [hook],\r\n })\r\n }\r\n\r\n unsubscribe(channels: string) {\r\n this.send({ type: \"unsubscribe\", channels })\r\n }\r\n\r\n disconnect() {\r\n if (!this.socket) return\r\n\r\n this.socket.onclose = () => {}\r\n this.socket.close()\r\n }\r\n}\r\n","import React, {\r\n createContext,\r\n useCallback,\r\n useEffect,\r\n useRef,\r\n useState,\r\n type ReactNode,\r\n} from \"react\"\r\nimport type OBSWebSocket from \"obs-websocket-js\"\r\nimport type { OBSRequestTypes, OBSResponseTypes } from \"obs-websocket-js\"\r\n\r\ntype ObsStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"error\"\r\n\r\ninterface ObsContextType {\r\n status: ObsStatus\r\n call: <T extends keyof OBSRequestTypes>(\r\n requestType: T,\r\n requestData?: OBSRequestTypes[T]\r\n ) => Promise<OBSResponseTypes[T]>\r\n}\r\n\r\nexport const ObsContext = createContext<ObsContextType>({\r\n status: \"disconnected\",\r\n call: () => Promise.reject(new Error(\"ObsProvider not mounted\")),\r\n})\r\n\r\nexport interface ObsProviderProps {\r\n url: string\r\n children: ReactNode\r\n}\r\n\r\nexport function ObsProvider({ url, children }: ObsProviderProps) {\r\n const [status, setStatus] = useState<ObsStatus>(\"disconnected\")\r\n const obsRef = useRef<OBSWebSocket | null>(null)\r\n const cancelledRef = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!url) return\r\n\r\n cancelledRef.current = false\r\n let reconnectTimer: ReturnType<typeof setTimeout>\r\n\r\n const setup = async () => {\r\n const { default: OBSWebSocket } = await import(\"obs-websocket-js\")\r\n const obs = new OBSWebSocket()\r\n obsRef.current = obs\r\n\r\n const doConnect = async () => {\r\n if (cancelledRef.current) return\r\n setStatus(\"connecting\")\r\n try {\r\n await obs.connect(url)\r\n if (!cancelledRef.current) setStatus(\"connected\")\r\n } catch {\r\n if (!cancelledRef.current) {\r\n setStatus(\"error\")\r\n reconnectTimer = setTimeout(doConnect, 3000)\r\n }\r\n }\r\n }\r\n\r\n obs.on(\"ConnectionClosed\" as any, () => {\r\n if (!cancelledRef.current) {\r\n setStatus(\"disconnected\")\r\n reconnectTimer = setTimeout(doConnect, 3000)\r\n }\r\n })\r\n\r\n obs.on(\"ConnectionError\" as any, () => {\r\n if (!cancelledRef.current) setStatus(\"error\")\r\n })\r\n\r\n await doConnect()\r\n }\r\n\r\n setup()\r\n\r\n return () => {\r\n cancelledRef.current = true\r\n clearTimeout(reconnectTimer!)\r\n obsRef.current?.disconnect()\r\n obsRef.current = null\r\n setStatus(\"disconnected\")\r\n }\r\n }, [url])\r\n\r\n const call = useCallback(\r\n async <T extends keyof OBSRequestTypes>(\r\n requestType: T,\r\n requestData?: OBSRequestTypes[T]\r\n ): Promise<OBSResponseTypes[T]> => {\r\n const obs = obsRef.current\r\n if (!obs) throw new Error(\"OBS not connected\")\r\n return obs.call(requestType, requestData)\r\n },\r\n []\r\n )\r\n\r\n return (\r\n <ObsContext.Provider value={{ status, call }}>\r\n {children}\r\n </ObsContext.Provider>\r\n )\r\n}\r\n","import { createContext, useContext } from \"react\"\r\nimport type { SparkleConfig } from \"./utils/config\"\r\n\r\nexport const SparkleConfigContext = createContext<SparkleConfig | null>(null)\r\n\r\nexport function useSparkleConfig(): SparkleConfig {\r\n const cfg = useContext(SparkleConfigContext)\r\n if (!cfg) {\r\n throw new Error(\"Sparkle hooks require SparkleProvider\")\r\n }\r\n return cfg\r\n}\r\n","import { useContext, useEffect, useMemo } from \"react\"\r\nimport { RealtimeContext, RealtimeValue } from \"../providers/realtimeProvider\"\r\n\r\nexport function useRealtime<T extends RealtimeValue>(\r\n key: string,\r\n defaultValue?: T\r\n): T[] {\r\n const context = useContext(RealtimeContext)\r\n\r\n const keys = useMemo(() => [key], [key])\r\n\r\n if (!context) {\r\n throw new Error(\"You must use useRealtime inside a SparkleProvider\")\r\n }\r\n\r\n useEffect(() => {\r\n context.subscribe([key])\r\n }, [keys])\r\n\r\n return keys.map((k) => (context.values[k] ?? defaultValue) as T)\r\n}\r\n","import { useEffect, useState } from \"react\"\n\nconst WIDGET_SESSION_REQ = \"sparkle-widget-session-request\"\nconst WIDGET_SESSION_RES = \"sparkle-widget-session-result\"\n\nfunction newRequestId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID()\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\n}\n\nfunction isEmbedded(): boolean {\n if (typeof window === \"undefined\") return false\n try {\n return window.self !== window.top\n } catch {\n return true\n }\n}\n\nfunction parseSessionMessage(d: unknown): {\n userId: string | null\n username: string | null\n profilePicture: string | null\n} {\n if (!d || typeof d !== \"object\") {\n return { userId: null, username: null, profilePicture: null }\n }\n const o = d as Record<string, unknown>\n const userId =\n typeof o.userId === \"string\" && o.userId.trim() ? o.userId : null\n const username =\n typeof o.username === \"string\" && o.username.trim() ? o.username : null\n const profilePicture =\n typeof o.profilePicture === \"string\" && o.profilePicture.trim()\n ? o.profilePicture\n : null\n return { userId, username, profilePicture }\n}\n\nfunction requestSessionViaParent(): Promise<{\n userId: string | null\n username: string | null\n profilePicture: string | null\n}> {\n if (typeof window === \"undefined\") {\n return Promise.resolve({\n userId: null,\n username: null,\n profilePicture: null,\n })\n }\n const target = window.top ?? window.parent\n if (!target || target === window) {\n return Promise.resolve({\n userId: null,\n username: null,\n profilePicture: null,\n })\n }\n return new Promise((resolve) => {\n const requestId = newRequestId()\n const onMsg = (ev: MessageEvent) => {\n const d = ev.data\n if (!d || d.type !== WIDGET_SESSION_RES || d.requestId !== requestId)\n return\n window.removeEventListener(\"message\", onMsg)\n window.clearTimeout(timer)\n resolve(parseSessionMessage(d))\n }\n const timer = window.setTimeout(() => {\n window.removeEventListener(\"message\", onMsg)\n resolve({ userId: null, username: null, profilePicture: null })\n }, 12_000)\n window.addEventListener(\"message\", onMsg)\n target.postMessage({ type: WIDGET_SESSION_REQ, requestId }, \"*\")\n })\n}\n\nasync function fetchSessionPayload(): Promise<{\n userId: string | null\n username: string | null\n profilePicture: string | null\n}> {\n const res = await fetch(\"/api/sparkle-widget-session\", {\n method: \"GET\",\n credentials: \"include\",\n })\n if (!res.ok) {\n return { userId: null, username: null, profilePicture: null }\n }\n const data = await res.json().catch(() => null)\n return parseSessionMessage(data)\n}\n\nexport type UseAuthResult = {\n /** Identifiant Twitch (`Account.accountId`), pas l’id interne Sparkle. */\n userId: string | null | undefined\n /** `User.name` Prisma (nom affiché OAuth). */\n username: string | null | undefined\n /** `User.image` Prisma (URL avatar). */\n profilePicture: string | null | undefined\n}\n\n/**\n * Profil session navigateur : `userId` = id Twitch (`Account.accountId` OAuth) ;\n * `username` / `profilePicture` depuis `User`. Champs à `undefined` pendant le chargement.\n */\nexport function useAuth(): UseAuthResult {\n const [userId, setUserId] = useState<string | null | undefined>(undefined)\n const [username, setUsername] = useState<string | null | undefined>(undefined)\n const [profilePicture, setProfilePicture] = useState<\n string | null | undefined\n >(undefined)\n\n useEffect(() => {\n let cancelled = false\n const run = async () => {\n const p = isEmbedded()\n ? await requestSessionViaParent()\n : await fetchSessionPayload()\n if (!cancelled) {\n setUserId(p.userId)\n setUsername(p.username)\n setProfilePicture(p.profilePicture)\n }\n }\n void run()\n return () => {\n cancelled = true\n }\n }, [])\n\n return { userId, username, profilePicture }\n}\n","import { useContext, useEffect, useMemo } from \"react\"\r\nimport { RealtimeContext } from \"../providers/realtimeProvider\"\r\nimport { EventTypeMap } from \"./hook\"\r\n\r\nexport const SparkleHookKeys = [\r\n \"notification.follow\",\r\n \"notification.cheer\",\r\n \"notification.tips\",\r\n \"notification.subscribe\",\r\n \"notification.subscribe.end\",\r\n \"notification.subscribe.gift\",\r\n \"notification.raid\",\r\n\r\n \"chat.message\",\r\n\r\n \"stream.online\",\r\n \"stream.offline\",\r\n \"stream.update\",\r\n\r\n \"reward.create\",\r\n \"reward.update\",\r\n \"reward.remove\",\r\n\r\n \"reward.redemption.claim\",\r\n \"reward.redemption.update\",\r\n\r\n \"poll.begin\",\r\n \"poll.progress\",\r\n \"poll.end\",\r\n\r\n \"prediction.begin\",\r\n \"prediction.progress\",\r\n \"prediction.lock\",\r\n \"prediction.end\",\r\n\r\n \"moderator.add\",\r\n \"moderator.remove\",\r\n \"moderator.ban\",\r\n \"moderator.unban\",\r\n \"moderator.shield_mode.begin\",\r\n] as const\r\n\r\nexport type SparkleHookKey = (typeof SparkleHookKeys)[number]\r\n\r\nexport function useHook<T extends keyof EventTypeMap>(\r\n hook: T\r\n): EventTypeMap[T] | undefined {\r\n const context = useContext(RealtimeContext)\r\n\r\n if (!context) {\r\n throw new Error(\"You must use useHook inside a SparkleProvider\")\r\n }\r\n\r\n useEffect(() => {\r\n context.listen(hook)\r\n }, [hook])\r\n\r\n return context.hooks[hook] as EventTypeMap[T]\r\n}\r\n","import { useCallback, useMemo } from \"react\"\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\n\r\nconst BRIDGE_TYPE = \"sparkle-invoke-task\"\r\nconst BRIDGE_RESULT = \"sparkle-invoke-task-result\"\r\n\r\ntype InvokeBridgeBody =\r\n | { kind: \"invoke\"; taskId: string; payload?: unknown }\r\n | {\r\n kind: \"invokeUntil\"\r\n taskId: string\r\n payload?: unknown\r\n delaySeconds: number\r\n }\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction bridgeInvoke(\r\n projectId: string,\r\n body: InvokeBridgeBody\r\n): Promise<boolean> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useTask requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useTask: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok) resolve(Boolean(d.result))\r\n else reject(new Error(typeof d.error === \"string\" ? d.error : \"invoke_failed\"))\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useTask: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage({ type: BRIDGE_TYPE, requestId, projectId, ...body }, \"*\")\r\n })\r\n}\r\n\r\n/**\r\n * Dès que le widget tourne dans une iframe, un `fetch` vers `/api/...` n’emporte en général\r\n * pas la session (partitionnement / contexte imbriqué). Le relais `postMessage` → fenêtre\r\n * parente (même onglet, même origine) envoie les cookies comme une navigation classique.\r\n */\r\nfunction shouldUsePostMessageBridge(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nasync function fetchInvoke(\r\n projectId: string,\r\n body: InvokeBridgeBody\r\n): Promise<boolean> {\r\n const res = await fetch(`/api/projects/${projectId}/invoke-task`, {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n })\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n return true\r\n}\r\n\r\n/**\r\n * Exécute une tâche Sparkle dans la runtime (équivalent à `ctx.invoke` / `ctx.invokeUntil`).\r\n * En preview Sandpack, les appels passent par `postMessage` vers l’éditeur (session).\r\n */\r\nexport function useTask() {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId\r\n\r\n const invoke = useCallback(\r\n async (taskId: string, payload?: unknown) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\"useTask: renseignez `config.projectId` sur SparkleProvider.\")\r\n }\r\n const body: InvokeBridgeBody = { kind: \"invoke\", taskId, payload }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeInvoke(projectId, body)\r\n }\r\n return fetchInvoke(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n const invokeUntil = useCallback(\r\n async (taskId: string, delaySeconds: number, payload?: unknown) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\"useTask: renseignez `config.projectId` sur SparkleProvider.\")\r\n }\r\n const body: InvokeBridgeBody = {\r\n kind: \"invokeUntil\",\r\n taskId,\r\n payload,\r\n delaySeconds,\r\n }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeInvoke(projectId, body)\r\n }\r\n return fetchInvoke(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n return useMemo(() => ({ invoke, invokeUntil }), [invoke, invokeUntil])\r\n}\r\n","import { useCallback, useMemo } from \"react\"\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\nimport { createTwitchGetClient } from \"../twitch/create-twitch-get-client\"\r\nimport type { TwitchGetClient } from \"../twitch/twitch-get-types\"\r\n\r\nexport type { TwitchGetClient } from \"../twitch/twitch-get-types\"\r\n\r\nconst BRIDGE_TYPE = \"sparkle-twitch-get\"\r\nconst BRIDGE_RESULT = \"sparkle-twitch-get-result\"\r\n\r\nexport type TwitchGetApiBody = {\r\n path: string[]\r\n args?: unknown[]\r\n}\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction shouldUsePostMessageBridge(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nfunction bridgeTwitchGet(\r\n projectId: string,\r\n body: TwitchGetApiBody\r\n): Promise<unknown> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useTwitch requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useTwitch: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok) resolve(d.result)\r\n else\r\n reject(\r\n new Error(typeof d.error === \"string\" ? d.error : \"twitch_get_failed\")\r\n )\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useTwitch: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage(\r\n { type: BRIDGE_TYPE, requestId, projectId, ...body },\r\n \"*\"\r\n )\r\n })\r\n}\r\n\r\nasync function fetchTwitchGet(\r\n projectId: string,\r\n body: TwitchGetApiBody\r\n): Promise<unknown> {\r\n const res = await fetch(\r\n `/api/projects/${encodeURIComponent(projectId)}/twitch-get`,\r\n {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n }\r\n )\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n const json = (await res.json()) as { result?: unknown }\r\n return json.result\r\n}\r\n\r\n/**\r\n * Client Twitch en lecture seule (Twurple via le backend du projet).\r\n * Même surface GET que `ctx.twitch` dans les handlers runtime — pas les mutations.\r\n *\r\n * Requiert `config.projectId` sur `SparkleProvider`.\r\n */\r\nexport function useTwitch(): TwitchGetClient {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId\r\n\r\n const invoke = useCallback(\r\n async (namespace: string, method: string, args: unknown[]) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\r\n \"useTwitch: renseignez `config.projectId` sur SparkleProvider.\"\r\n )\r\n }\r\n const body: TwitchGetApiBody = {\r\n path: [namespace, method],\r\n args,\r\n }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeTwitchGet(projectId, body)\r\n }\r\n return fetchTwitchGet(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n return useMemo(() => createTwitchGetClient(invoke), [invoke])\r\n}\r\n","import type { TwitchGetClient } from \"./twitch-get-types\"\n\nexport type TwitchGetInvoker = (\n namespace: string,\n method: string,\n args: unknown[]\n) => Promise<unknown>\n\nexport function createTwitchGetClient(\n invoke: TwitchGetInvoker\n): TwitchGetClient {\n const call =\n (ns: string, method: string) =>\n (...args: unknown[]) =>\n invoke(ns, method, args)\n\n return {\n bits: {\n getLeaderboard: call(\"bits\", \"getLeaderboard\"),\n },\n channelPoints: {\n getCustomRewards: call(\"channelPoints\", \"getCustomRewards\"),\n getCustomRewardsByIds: call(\"channelPoints\", \"getCustomRewardsByIds\"),\n getCustomRewardById: call(\"channelPoints\", \"getCustomRewardById\"),\n getRedemptionsByIds: call(\"channelPoints\", \"getRedemptionsByIds\"),\n getRedemptionById: call(\"channelPoints\", \"getRedemptionById\"),\n getRedemptions: call(\"channelPoints\", \"getRedemptions\"),\n },\n channel: {\n getChannelInfoById: call(\"channel\", \"getChannelInfoById\"),\n getFollowDate: call(\"channel\", \"getFollowDate\"),\n getChannelEditors: call(\"channel\", \"getChannelEditors\"),\n getVips: call(\"channel\", \"getVips\"),\n checkVipForUsers: call(\"channel\", \"checkVipForUsers\"),\n checkVipForUser: call(\"channel\", \"checkVipForUser\"),\n getChannelFollowerCount: call(\"channel\", \"getChannelFollowerCount\"),\n },\n chat: {\n getChatters: call(\"chat\", \"getChatters\"),\n getSettings: call(\"chat\", \"getSettings\"),\n getSettingsPrivileged: call(\"chat\", \"getSettingsPrivileged\"),\n getColorsForUsers: call(\"chat\", \"getColorsForUsers\"),\n getColorForUser: call(\"chat\", \"getColorForUser\"),\n },\n clips: {\n getClips: call(\"clips\", \"getClips\"),\n getClipById: call(\"clips\", \"getClipById\"),\n getClipsByIds: call(\"clips\", \"getClipsByIds\"),\n },\n goals: {\n getGoals: call(\"goals\", \"getGoals\"),\n },\n polls: {\n getPolls: call(\"polls\", \"getPolls\"),\n getPollsByIds: call(\"polls\", \"getPollsByIds\"),\n getPollById: call(\"polls\", \"getPollById\"),\n },\n predictions: {\n getPredictions: call(\"predictions\", \"getPredictions\"),\n getPredictionsByIds: call(\"predictions\", \"getPredictionsByIds\"),\n getPredictionById: call(\"predictions\", \"getPredictionById\"),\n },\n streams: {\n getStream: call(\"streams\", \"getStream\"),\n getStreamMarkers: call(\"streams\", \"getStreamMarkers\"),\n },\n subscriptions: {\n getSubscriptions: call(\"subscriptions\", \"getSubscriptions\"),\n getSubscriptionsForUsers: call(\n \"subscriptions\",\n \"getSubscriptionsForUsers\"\n ),\n getSubscriptionForUser: call(\"subscriptions\", \"getSubscriptionForUser\"),\n checkUserSubscription: call(\"subscriptions\", \"checkUserSubscription\"),\n },\n moderation: {\n getBanned: call(\"moderation\", \"getBanned\"),\n checkBan: call(\"moderation\", \"checkBan\"),\n getModerators: call(\"moderation\", \"getModerators\"),\n checkModerator: call(\"moderation\", \"checkModerator\"),\n getBlockedTerms: call(\"moderation\", \"getBlockedTerms\"),\n },\n utils: {\n getUserById: call(\"utils\", \"getUserById\"),\n getUserByName: call(\"utils\", \"getUserByName\"),\n getUserByIds: call(\"utils\", \"getUserByIds\"),\n getUserByNames: call(\"utils\", \"getUserByNames\"),\n },\n } as unknown as TwitchGetClient\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\"\r\n\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\nimport {\r\n emptyChannelLabels,\r\n type ChannelLabelsSnapshot,\r\n} from \"../types/labels\"\r\nimport { useHook } from \"./useHook\"\r\n\r\nconst BRIDGE_TYPE = \"sparkle-labels-get\"\r\nconst BRIDGE_RESULT = \"sparkle-labels-get-result\"\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction shouldUsePostMessageBridge(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nfunction bridgeLabelsGet(projectId: string): Promise<ChannelLabelsSnapshot> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useLabels requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useLabels: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok && d.labels) resolve(d.labels as ChannelLabelsSnapshot)\r\n else\r\n reject(\r\n new Error(typeof d.error === \"string\" ? d.error : \"labels_get_failed\")\r\n )\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useLabels: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage({ type: BRIDGE_TYPE, requestId, projectId }, \"*\")\r\n })\r\n}\r\n\r\nasync function fetchLabels(projectId: string): Promise<ChannelLabelsSnapshot> {\r\n const res = await fetch(\r\n `/api/projects/${encodeURIComponent(projectId)}/labels`,\r\n {\r\n method: \"GET\",\r\n credentials: \"include\",\r\n }\r\n )\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n const json = (await res.json()) as { labels?: ChannelLabelsSnapshot }\r\n return json.labels ?? emptyChannelLabels()\r\n}\r\n\r\nasync function loadLabels(projectId: string): Promise<ChannelLabelsSnapshot> {\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeLabelsGet(projectId)\r\n }\r\n return fetchLabels(projectId)\r\n}\r\n\r\n/**\r\n * Agrégats chaîne (session, total, latest, recent) — même données que `ctx.labels.get()`.\r\n * Se met à jour quand un événement Twitch/SE pertinent arrive via `useHook`.\r\n * Requiert `config.projectId` sur `SparkleProvider`.\r\n */\r\nexport function useLabels(): ChannelLabelsSnapshot {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId?.trim() ?? \"\"\r\n const [labels, setLabels] = useState<ChannelLabelsSnapshot>(emptyChannelLabels)\r\n const requestIdRef = useRef(0)\r\n\r\n const follow = useHook(\"notification.follow\")\r\n const subscribe = useHook(\"notification.subscribe\")\r\n const subscribeGift = useHook(\"notification.subscribe.gift\")\r\n const cheer = useHook(\"notification.cheer\")\r\n const tips = useHook(\"notification.tips\")\r\n const streamOffline = useHook(\"stream.offline\")\r\n\r\n const refresh = useCallback(async () => {\r\n if (!projectId) return\r\n const requestId = ++requestIdRef.current\r\n try {\r\n const next = await loadLabels(projectId)\r\n if (requestId === requestIdRef.current) {\r\n setLabels(next)\r\n }\r\n } catch {\r\n // Conserve la dernière valeur connue.\r\n }\r\n }, [projectId])\r\n\r\n useEffect(() => {\r\n if (!projectId) {\r\n setLabels(emptyChannelLabels())\r\n return\r\n }\r\n void refresh()\r\n }, [projectId, refresh])\r\n\r\n useEffect(() => {\r\n if (!projectId) return\r\n void refresh()\r\n }, [\r\n projectId,\r\n refresh,\r\n follow,\r\n subscribe,\r\n subscribeGift,\r\n cheer,\r\n tips,\r\n streamOffline,\r\n ])\r\n\r\n return labels\r\n}\r\n","export type ChannelLabelCounts = {\r\n followers: number\r\n subscribers: number\r\n cheers: number\r\n tips: number\r\n}\r\n\r\nexport type ChannelLabelLatest = {\r\n followers: string\r\n subscribers: string\r\n cheers: { amount: number; user: string }\r\n tips: { amount: number; user: string }\r\n}\r\n\r\nexport type ChannelLabelRecent = {\r\n followers: string[]\r\n subscribers: string[]\r\n cheers: string[]\r\n tips: string[]\r\n}\r\n\r\nexport type ChannelLabelsSnapshot = {\r\n session: ChannelLabelCounts\r\n total: ChannelLabelCounts\r\n latest: ChannelLabelLatest\r\n recent: ChannelLabelRecent\r\n}\r\n\r\nexport const emptyChannelLabels = (): ChannelLabelsSnapshot => ({\r\n session: {\r\n followers: 0,\r\n subscribers: 0,\r\n cheers: 0,\r\n tips: 0,\r\n },\r\n total: {\r\n followers: 0,\r\n subscribers: 0,\r\n cheers: 0,\r\n tips: 0,\r\n },\r\n latest: {\r\n followers: \"\",\r\n subscribers: \"\",\r\n cheers: { amount: 0, user: \"\" },\r\n tips: { amount: 0, user: \"\" },\r\n },\r\n recent: {\r\n followers: [],\r\n subscribers: [],\r\n cheers: [],\r\n tips: [],\r\n },\r\n})\r\n"],"mappings":"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","names":["React","useMemo","useEffect","useState","React","createContext","SparkleAuthContext","AuthProvider","children","config","React","useCallback","useContext","useRef","createContext","useEffect","useState","EventEmitter","z","subscribeSchema","hookSchema","unsubscribeSchema","updateSchema","messageSchema","WebSocketClient","url","data","jwt","authenticatedUrl","event","channels","newChannels","channel","hook","RealtimeContext","createContext","WebSocketProvider","children","config","values","setValues","useState","hooks","setHooks","socket","useRef","token","useContext","SparkleAuthContext","useEffect","WebSocketClient","message","data","type","payload","prev","value","num","subscribe","useCallback","keys","listen","hook","React","React","createContext","useCallback","useEffect","useRef","useState","ObsContext","ObsProvider","url","children","status","setStatus","obsRef","cancelledRef","reconnectTimer","OBSWebSocket","obs","doConnect","call","requestType","requestData","createContext","useContext","SparkleConfigContext","useSparkleConfig","cfg","ClientGate","children","ready","setReady","useState","useEffect","defaultConfig","SparkleProvider","config","mergedConfig","obsUrl","useMemo","inner","React","SparkleConfigContext","AuthProvider","WebSocketProvider","ObsProvider","useContext","useEffect","useMemo","useRealtime","key","defaultValue","context","useContext","RealtimeContext","keys","useMemo","useEffect","k","useEffect","useState","WIDGET_SESSION_REQ","WIDGET_SESSION_RES","newRequestId","isEmbedded","parseSessionMessage","d","o","userId","username","profilePicture","requestSessionViaParent","target","resolve","requestId","onMsg","ev","timer","fetchSessionPayload","res","data","useAuth","setUserId","setUsername","setProfilePicture","cancelled","p","useContext","useEffect","SparkleHookKeys","useHook","hook","context","useContext","RealtimeContext","useEffect","useCallback","useMemo","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","bridgeInvoke","projectId","body","target","resolve","reject","requestId","onMsg","ev","d","timer","shouldUsePostMessageBridge","fetchInvoke","res","err","useTask","useSparkleConfig","invoke","useCallback","taskId","payload","invokeUntil","delaySeconds","useMemo","useCallback","useMemo","createTwitchGetClient","invoke","call","ns","method","args","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","shouldUsePostMessageBridge","bridgeTwitchGet","projectId","body","target","resolve","reject","requestId","onMsg","ev","d","timer","fetchTwitchGet","res","err","useTwitch","useSparkleConfig","invoke","useCallback","namespace","method","args","useMemo","createTwitchGetClient","useCallback","useEffect","useRef","useState","emptyChannelLabels","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","shouldUsePostMessageBridge","bridgeLabelsGet","projectId","target","resolve","reject","requestId","onMsg","ev","d","timer","fetchLabels","res","err","emptyChannelLabels","loadLabels","useLabels","useSparkleConfig","labels","setLabels","useState","requestIdRef","useRef","follow","useHook","subscribe","subscribeGift","cheer","tips","streamOffline","refresh","useCallback","next","useEffect"]}
1
+ {"version":3,"sources":["../src/provider.tsx","../src/providers/authProvider.tsx","../src/providers/realtimeProvider.tsx","../src/lib/websocket.ts","../src/providers/obsProvider.tsx","../src/sparkle-config-context.tsx","../src/hooks/useRealtime.ts","../src/hooks/useAuth.ts","../src/hooks/useHook.ts","../src/hooks/useTask.ts","../src/hooks/useTwitch.ts","../src/twitch/create-twitch-get-client.ts","../src/hooks/useLabels.ts","../src/types/labels.ts","../src/hooks/useSpotify.ts"],"sourcesContent":["import React, { useMemo, type ReactNode, useEffect, useState } from \"react\"\nimport { AuthProvider } from \"./providers/authProvider\"\nimport { WebSocketProvider } from \"./providers/realtimeProvider\"\nimport { ObsProvider } from \"./providers/obsProvider\"\nimport { SparkleConfigContext } from \"./sparkle-config-context\"\nimport { SparkleConfig } from \"./utils/config\"\n\nconst ClientGate = ({ children }: { children: ReactNode }) => {\n const [ready, setReady] = useState(false)\n\n useEffect(() => {\n setReady(true)\n }, [])\n\n if (!ready) return null\n return children\n}\n\nexport interface SparkleProviderProps {\n config?: SparkleConfig\n children: ReactNode\n}\n\nconst defaultConfig: SparkleConfig = {\n userId: \"\",\n wsUrl: \"wss://sparkle-bot-v2.fly.dev/ws\",\n}\n\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\n const mergedConfig = { ...defaultConfig, ...config }\n\n const obsUrl = useMemo(() => {\n if (!mergedConfig.obs?.clientId) return \"\"\n const base =\n mergedConfig.obs.bridgeUrl ?? \"wss://sparkle-bot-v2.fly.dev/obs-bridge\"\n return `${base}?role=client&clientId=${encodeURIComponent(mergedConfig.obs.clientId)}`\n }, [mergedConfig.obs?.clientId, mergedConfig.obs?.bridgeUrl])\n\n const inner = (\n <SparkleConfigContext.Provider value={mergedConfig}>\n <AuthProvider config={mergedConfig}>\n <WebSocketProvider config={mergedConfig}>\n <ClientGate>{children}</ClientGate>\n </WebSocketProvider>\n </AuthProvider>\n </SparkleConfigContext.Provider>\n )\n\n if (obsUrl) {\n return <ObsProvider url={obsUrl}>{inner}</ObsProvider>\n }\n\n return inner\n}\n","import * as React from \"react\"\r\n\r\nimport { type ReactNode, createContext } from \"react\"\r\n\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\ninterface AuthContextType {\r\n token: string | undefined | null\r\n}\r\n\r\nexport const SparkleAuthContext = createContext<AuthContextType>({\r\n token: null,\r\n})\r\n\r\ninterface AuthProviderProps {\r\n config: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nexport const AuthProvider = ({ children, config }: AuthProviderProps) => {\r\n return (\r\n <SparkleAuthContext.Provider\r\n value={{\r\n token: config.userId,\r\n }}\r\n >\r\n {children}\r\n </SparkleAuthContext.Provider>\r\n )\r\n}\r\n","import React, { ReactNode, useCallback, useContext, useRef } from \"react\"\r\n\r\nimport { createContext, useEffect, useState } from \"react\"\r\nimport { SparkleAuthContext } from \"./authProvider\"\r\nimport { UpdateMessage, WebSocketClient } from \"../lib/websocket\"\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\ninterface WebSocketContextType {\r\n subscribe: (keys: string[]) => void\r\n listen: (hook: string) => void\r\n values: Record<string, any>\r\n hooks: Record<string, any>\r\n}\r\n\r\nexport const RealtimeContext = createContext<WebSocketContextType>({\r\n subscribe: () => {},\r\n listen: () => {},\r\n values: {},\r\n hooks: {},\r\n})\r\n\r\nexport type RealtimeValue = string | number | boolean | { [key: string]: any }\r\n\r\ntype State = Record<string, RealtimeValue>\r\n\r\ninterface WebSocketProviderProps {\r\n config: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nexport const WebSocketProvider = ({\r\n children,\r\n config,\r\n}: WebSocketProviderProps) => {\r\n const [values, setValues] = useState<State>({})\r\n const [hooks, setHooks] = useState<Record<string, any>>({})\r\n\r\n const socket = useRef<WebSocketClient>(null)\r\n\r\n const { token } = useContext(SparkleAuthContext)\r\n\r\n useEffect(() => {\r\n socket.current = new WebSocketClient(config.wsUrl)\r\n\r\n socket.current.on(\"update\", (message: UpdateMessage) => {\r\n const { data, type } = message\r\n\r\n if (type === \"hook\") {\r\n const payload = JSON.parse(data.value)\r\n\r\n setHooks((prev) => ({\r\n ...prev,\r\n [payload.hook]: payload.payload,\r\n }))\r\n console.log(\"receive hooks\", payload)\r\n } else {\r\n let value: any = data.value\r\n\r\n console.log(\"value\", value)\r\n\r\n try {\r\n const num = parseFloat(data.value)\r\n if (!isNaN(num) && String(num) === String(data.value).trim()) {\r\n value = num\r\n } else {\r\n try {\r\n value = JSON.parse(data.value)\r\n } catch {\r\n value = data.value\r\n }\r\n }\r\n } catch {\r\n value = data.value\r\n }\r\n\r\n setValues((prev) => ({ ...prev, [data.key]: value }))\r\n console.log(\"receive values\", values)\r\n }\r\n })\r\n\r\n return () => {\r\n socket.current?.disconnect()\r\n }\r\n }, [])\r\n\r\n useEffect(() => {\r\n if (!socket.current || !token) return\r\n\r\n socket.current.authenticate(token)\r\n }, [token])\r\n\r\n const subscribe = useCallback(\r\n (keys: string[]) => {\r\n socket.current?.subscribe(keys)\r\n },\r\n [socket]\r\n )\r\n\r\n const listen = useCallback(\r\n (hook: string) => {\r\n socket.current?.hook(hook)\r\n },\r\n [socket]\r\n )\r\n\r\n return (\r\n <RealtimeContext.Provider\r\n value={{\r\n subscribe,\r\n listen,\r\n values,\r\n hooks,\r\n }}\r\n >\r\n {children}\r\n </RealtimeContext.Provider>\r\n )\r\n}\r\n","import EventEmitter from \"events\"\r\nimport z from \"zod\"\r\n\r\nconst subscribeSchema = z.object({\r\n type: z.literal(\"subscribe\"),\r\n channels: z.array(z.string()),\r\n})\r\n\r\nconst hookSchema = z.object({\r\n type: z.literal(\"hook\"),\r\n channels: z.array(z.string()),\r\n})\r\n\r\nconst unsubscribeSchema = z.object({\r\n type: z.literal(\"unsubscribe\"),\r\n channels: z.string(),\r\n})\r\n\r\nconst updateSchema = z.object({\r\n event: z.literal(\"update\"),\r\n type: z.enum([\"value\", \"hook\"]),\r\n data: z.object({\r\n key: z.string(),\r\n value: z.string(),\r\n }),\r\n})\r\n\r\nconst messageSchema = z.union([subscribeSchema, unsubscribeSchema, hookSchema])\r\n\r\nexport type Message = z.infer<typeof messageSchema>\r\n\r\nexport type UpdateMessage = z.infer<typeof updateSchema>\r\nexport type SubscribeMessage = z.infer<typeof subscribeSchema>\r\nexport type UnsubscribeMessage = z.infer<typeof unsubscribeSchema>\r\n\r\nexport class WebSocketClient extends EventEmitter {\r\n private socket: WebSocket | null = null\r\n private queue: Message[] = []\r\n private subscriptions: string[] = []\r\n private hooks: string[] = []\r\n private isAuthenticating = false\r\n\r\n private url: string\r\n private jwt: string | null = null\r\n\r\n constructor(url: string) {\r\n super()\r\n this.url = url\r\n }\r\n\r\n private send(data: Message) {\r\n if (this.socket?.readyState === WebSocket.OPEN) {\r\n this.socket?.send(JSON.stringify(data))\r\n } else {\r\n this.queue.push(data)\r\n }\r\n }\r\n\r\n authenticate(jwt: string) {\r\n if (this.isAuthenticating) return\r\n this.isAuthenticating = true\r\n this.jwt = jwt\r\n this.connect()\r\n }\r\n\r\n private connect() {\r\n const authenticatedUrl = this.url + \"?jwt=\" + this.jwt\r\n this.socket = new WebSocket(authenticatedUrl)\r\n\r\n this.socket.onopen = () => {\r\n this.queue.forEach((data) => this.send(data))\r\n this.queue = []\r\n }\r\n\r\n this.socket.onmessage = (event) => {\r\n const data = JSON.parse(event.data)\r\n this.emit(\"update\", data)\r\n }\r\n\r\n this.socket.onclose = () => {\r\n this.reconnect()\r\n }\r\n }\r\n\r\n reconnect() {\r\n setTimeout(() => {\r\n this.queue = [\r\n {\r\n type: \"subscribe\",\r\n channels: this.subscriptions,\r\n },\r\n {\r\n type: \"hook\",\r\n channels: this.hooks,\r\n },\r\n ]\r\n\r\n console.log(\"try reconnecting\")\r\n\r\n this.connect()\r\n }, 5000)\r\n }\r\n\r\n subscribe(channels: string[]) {\r\n const newChannels = channels.filter(\r\n (channel) => !this.subscriptions.includes(channel),\r\n )\r\n\r\n if (!newChannels.length) return\r\n this.subscriptions = [...this.subscriptions, ...newChannels]\r\n\r\n console.log(\"subscribe\", channels)\r\n\r\n this.send({\r\n type: \"subscribe\",\r\n channels,\r\n })\r\n }\r\n\r\n hook(hook: string) {\r\n const newChannels = [hook].filter(\r\n (channel) => !this.hooks.includes(channel),\r\n )\r\n\r\n if (!newChannels.length) return\r\n this.hooks = [...this.hooks, ...newChannels]\r\n\r\n this.send({\r\n type: \"hook\",\r\n channels: [hook],\r\n })\r\n }\r\n\r\n unsubscribe(channels: string) {\r\n this.send({ type: \"unsubscribe\", channels })\r\n }\r\n\r\n disconnect() {\r\n if (!this.socket) return\r\n\r\n this.socket.onclose = () => {}\r\n this.socket.close()\r\n }\r\n}\r\n","import React, {\r\n createContext,\r\n useCallback,\r\n useEffect,\r\n useRef,\r\n useState,\r\n type ReactNode,\r\n} from \"react\"\r\nimport type OBSWebSocket from \"obs-websocket-js\"\r\nimport type { OBSRequestTypes, OBSResponseTypes } from \"obs-websocket-js\"\r\n\r\ntype ObsStatus = \"disconnected\" | \"connecting\" | \"connected\" | \"error\"\r\n\r\ninterface ObsContextType {\r\n status: ObsStatus\r\n call: <T extends keyof OBSRequestTypes>(\r\n requestType: T,\r\n requestData?: OBSRequestTypes[T]\r\n ) => Promise<OBSResponseTypes[T]>\r\n}\r\n\r\nexport const ObsContext = createContext<ObsContextType>({\r\n status: \"disconnected\",\r\n call: () => Promise.reject(new Error(\"ObsProvider not mounted\")),\r\n})\r\n\r\nexport interface ObsProviderProps {\r\n url: string\r\n children: ReactNode\r\n}\r\n\r\nexport function ObsProvider({ url, children }: ObsProviderProps) {\r\n const [status, setStatus] = useState<ObsStatus>(\"disconnected\")\r\n const obsRef = useRef<OBSWebSocket | null>(null)\r\n const cancelledRef = useRef(false)\r\n\r\n useEffect(() => {\r\n if (!url) return\r\n\r\n cancelledRef.current = false\r\n let reconnectTimer: ReturnType<typeof setTimeout>\r\n\r\n const setup = async () => {\r\n const { default: OBSWebSocket } = await import(\"obs-websocket-js\")\r\n const obs = new OBSWebSocket()\r\n obsRef.current = obs\r\n\r\n const doConnect = async () => {\r\n if (cancelledRef.current) return\r\n setStatus(\"connecting\")\r\n try {\r\n await obs.connect(url)\r\n if (!cancelledRef.current) setStatus(\"connected\")\r\n } catch {\r\n if (!cancelledRef.current) {\r\n setStatus(\"error\")\r\n reconnectTimer = setTimeout(doConnect, 3000)\r\n }\r\n }\r\n }\r\n\r\n obs.on(\"ConnectionClosed\" as any, () => {\r\n if (!cancelledRef.current) {\r\n setStatus(\"disconnected\")\r\n reconnectTimer = setTimeout(doConnect, 3000)\r\n }\r\n })\r\n\r\n obs.on(\"ConnectionError\" as any, () => {\r\n if (!cancelledRef.current) setStatus(\"error\")\r\n })\r\n\r\n await doConnect()\r\n }\r\n\r\n setup()\r\n\r\n return () => {\r\n cancelledRef.current = true\r\n clearTimeout(reconnectTimer!)\r\n obsRef.current?.disconnect()\r\n obsRef.current = null\r\n setStatus(\"disconnected\")\r\n }\r\n }, [url])\r\n\r\n const call = useCallback(\r\n async <T extends keyof OBSRequestTypes>(\r\n requestType: T,\r\n requestData?: OBSRequestTypes[T]\r\n ): Promise<OBSResponseTypes[T]> => {\r\n const obs = obsRef.current\r\n if (!obs) throw new Error(\"OBS not connected\")\r\n return obs.call(requestType, requestData)\r\n },\r\n []\r\n )\r\n\r\n return (\r\n <ObsContext.Provider value={{ status, call }}>\r\n {children}\r\n </ObsContext.Provider>\r\n )\r\n}\r\n","import { createContext, useContext } from \"react\"\r\nimport type { SparkleConfig } from \"./utils/config\"\r\n\r\nexport const SparkleConfigContext = createContext<SparkleConfig | null>(null)\r\n\r\nexport function useSparkleConfig(): SparkleConfig {\r\n const cfg = useContext(SparkleConfigContext)\r\n if (!cfg) {\r\n throw new Error(\"Sparkle hooks require SparkleProvider\")\r\n }\r\n return cfg\r\n}\r\n","import { useContext, useEffect, useMemo } from \"react\"\r\nimport { RealtimeContext, RealtimeValue } from \"../providers/realtimeProvider\"\r\n\r\nexport function useRealtime<T extends RealtimeValue>(\r\n key: string,\r\n defaultValue?: T\r\n): T[] {\r\n const context = useContext(RealtimeContext)\r\n\r\n const keys = useMemo(() => [key], [key])\r\n\r\n if (!context) {\r\n throw new Error(\"You must use useRealtime inside a SparkleProvider\")\r\n }\r\n\r\n useEffect(() => {\r\n context.subscribe([key])\r\n }, [keys])\r\n\r\n return keys.map((k) => (context.values[k] ?? defaultValue) as T)\r\n}\r\n","import { useEffect, useState } from \"react\"\n\nconst WIDGET_SESSION_REQ = \"sparkle-widget-session-request\"\nconst WIDGET_SESSION_RES = \"sparkle-widget-session-result\"\n\nfunction newRequestId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID()\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\n}\n\nfunction isEmbedded(): boolean {\n if (typeof window === \"undefined\") return false\n try {\n return window.self !== window.top\n } catch {\n return true\n }\n}\n\nfunction parseSessionMessage(d: unknown): {\n userId: string | null\n username: string | null\n profilePicture: string | null\n} {\n if (!d || typeof d !== \"object\") {\n return { userId: null, username: null, profilePicture: null }\n }\n const o = d as Record<string, unknown>\n const userId =\n typeof o.userId === \"string\" && o.userId.trim() ? o.userId : null\n const username =\n typeof o.username === \"string\" && o.username.trim() ? o.username : null\n const profilePicture =\n typeof o.profilePicture === \"string\" && o.profilePicture.trim()\n ? o.profilePicture\n : null\n return { userId, username, profilePicture }\n}\n\nfunction requestSessionViaParent(): Promise<{\n userId: string | null\n username: string | null\n profilePicture: string | null\n}> {\n if (typeof window === \"undefined\") {\n return Promise.resolve({\n userId: null,\n username: null,\n profilePicture: null,\n })\n }\n const target = window.top ?? window.parent\n if (!target || target === window) {\n return Promise.resolve({\n userId: null,\n username: null,\n profilePicture: null,\n })\n }\n return new Promise((resolve) => {\n const requestId = newRequestId()\n const onMsg = (ev: MessageEvent) => {\n const d = ev.data\n if (!d || d.type !== WIDGET_SESSION_RES || d.requestId !== requestId)\n return\n window.removeEventListener(\"message\", onMsg)\n window.clearTimeout(timer)\n resolve(parseSessionMessage(d))\n }\n const timer = window.setTimeout(() => {\n window.removeEventListener(\"message\", onMsg)\n resolve({ userId: null, username: null, profilePicture: null })\n }, 12_000)\n window.addEventListener(\"message\", onMsg)\n target.postMessage({ type: WIDGET_SESSION_REQ, requestId }, \"*\")\n })\n}\n\nasync function fetchSessionPayload(): Promise<{\n userId: string | null\n username: string | null\n profilePicture: string | null\n}> {\n const res = await fetch(\"/api/sparkle-widget-session\", {\n method: \"GET\",\n credentials: \"include\",\n })\n if (!res.ok) {\n return { userId: null, username: null, profilePicture: null }\n }\n const data = await res.json().catch(() => null)\n return parseSessionMessage(data)\n}\n\nexport type UseAuthResult = {\n /** Identifiant Twitch (`Account.accountId`), pas l’id interne Sparkle. */\n userId: string | null | undefined\n /** `User.name` Prisma (nom affiché OAuth). */\n username: string | null | undefined\n /** `User.image` Prisma (URL avatar). */\n profilePicture: string | null | undefined\n}\n\n/**\n * Profil session navigateur : `userId` = id Twitch (`Account.accountId` OAuth) ;\n * `username` / `profilePicture` depuis `User`. Champs à `undefined` pendant le chargement.\n */\nexport function useAuth(): UseAuthResult {\n const [userId, setUserId] = useState<string | null | undefined>(undefined)\n const [username, setUsername] = useState<string | null | undefined>(undefined)\n const [profilePicture, setProfilePicture] = useState<\n string | null | undefined\n >(undefined)\n\n useEffect(() => {\n let cancelled = false\n const run = async () => {\n const p = isEmbedded()\n ? await requestSessionViaParent()\n : await fetchSessionPayload()\n if (!cancelled) {\n setUserId(p.userId)\n setUsername(p.username)\n setProfilePicture(p.profilePicture)\n }\n }\n void run()\n return () => {\n cancelled = true\n }\n }, [])\n\n return { userId, username, profilePicture }\n}\n","import { useContext, useEffect, useMemo } from \"react\"\r\nimport { RealtimeContext } from \"../providers/realtimeProvider\"\r\nimport { EventTypeMap } from \"./hook\"\r\n\r\nexport const SparkleHookKeys = [\r\n \"notification.follow\",\r\n \"notification.cheer\",\r\n \"notification.tips\",\r\n \"notification.subscribe\",\r\n \"notification.subscribe.end\",\r\n \"notification.subscribe.gift\",\r\n \"notification.raid\",\r\n\r\n \"chat.message\",\r\n\r\n \"stream.online\",\r\n \"stream.offline\",\r\n \"stream.update\",\r\n\r\n \"reward.create\",\r\n \"reward.update\",\r\n \"reward.remove\",\r\n\r\n \"reward.redemption.claim\",\r\n \"reward.redemption.update\",\r\n\r\n \"poll.begin\",\r\n \"poll.progress\",\r\n \"poll.end\",\r\n\r\n \"prediction.begin\",\r\n \"prediction.progress\",\r\n \"prediction.lock\",\r\n \"prediction.end\",\r\n\r\n \"moderator.add\",\r\n \"moderator.remove\",\r\n \"moderator.ban\",\r\n \"moderator.unban\",\r\n \"moderator.shield_mode.begin\",\r\n] as const\r\n\r\nexport type SparkleHookKey = (typeof SparkleHookKeys)[number]\r\n\r\nexport function useHook<T extends keyof EventTypeMap>(\r\n hook: T\r\n): EventTypeMap[T] | undefined {\r\n const context = useContext(RealtimeContext)\r\n\r\n if (!context) {\r\n throw new Error(\"You must use useHook inside a SparkleProvider\")\r\n }\r\n\r\n useEffect(() => {\r\n context.listen(hook)\r\n }, [hook])\r\n\r\n return context.hooks[hook] as EventTypeMap[T]\r\n}\r\n","import { useCallback, useMemo } from \"react\"\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\n\r\nconst BRIDGE_TYPE = \"sparkle-invoke-task\"\r\nconst BRIDGE_RESULT = \"sparkle-invoke-task-result\"\r\n\r\ntype InvokeBridgeBody =\r\n | { kind: \"invoke\"; taskId: string; payload?: unknown }\r\n | {\r\n kind: \"invokeUntil\"\r\n taskId: string\r\n payload?: unknown\r\n delaySeconds: number\r\n }\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction bridgeInvoke(\r\n projectId: string,\r\n body: InvokeBridgeBody\r\n): Promise<boolean> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useTask requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useTask: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok) resolve(Boolean(d.result))\r\n else reject(new Error(typeof d.error === \"string\" ? d.error : \"invoke_failed\"))\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useTask: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage({ type: BRIDGE_TYPE, requestId, projectId, ...body }, \"*\")\r\n })\r\n}\r\n\r\n/**\r\n * Dès que le widget tourne dans une iframe, un `fetch` vers `/api/...` n’emporte en général\r\n * pas la session (partitionnement / contexte imbriqué). Le relais `postMessage` → fenêtre\r\n * parente (même onglet, même origine) envoie les cookies comme une navigation classique.\r\n */\r\nfunction shouldUsePostMessageBridge(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nasync function fetchInvoke(\r\n projectId: string,\r\n body: InvokeBridgeBody\r\n): Promise<boolean> {\r\n const res = await fetch(`/api/projects/${projectId}/invoke-task`, {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n })\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n return true\r\n}\r\n\r\n/**\r\n * Exécute une tâche Sparkle dans la runtime (équivalent à `ctx.invoke` / `ctx.invokeUntil`).\r\n * En preview Sandpack, les appels passent par `postMessage` vers l’éditeur (session).\r\n */\r\nexport function useTask() {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId\r\n\r\n const invoke = useCallback(\r\n async (taskId: string, payload?: unknown) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\"useTask: renseignez `config.projectId` sur SparkleProvider.\")\r\n }\r\n const body: InvokeBridgeBody = { kind: \"invoke\", taskId, payload }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeInvoke(projectId, body)\r\n }\r\n return fetchInvoke(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n const invokeUntil = useCallback(\r\n async (taskId: string, delaySeconds: number, payload?: unknown) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\"useTask: renseignez `config.projectId` sur SparkleProvider.\")\r\n }\r\n const body: InvokeBridgeBody = {\r\n kind: \"invokeUntil\",\r\n taskId,\r\n payload,\r\n delaySeconds,\r\n }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeInvoke(projectId, body)\r\n }\r\n return fetchInvoke(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n return useMemo(() => ({ invoke, invokeUntil }), [invoke, invokeUntil])\r\n}\r\n","import { useCallback, useMemo } from \"react\"\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\nimport { createTwitchGetClient } from \"../twitch/create-twitch-get-client\"\r\nimport type { TwitchGetClient } from \"../twitch/twitch-get-types\"\r\n\r\nexport type { TwitchGetClient } from \"../twitch/twitch-get-types\"\r\n\r\nconst BRIDGE_TYPE = \"sparkle-twitch-get\"\r\nconst BRIDGE_RESULT = \"sparkle-twitch-get-result\"\r\n\r\nexport type TwitchGetApiBody = {\r\n path: string[]\r\n args?: unknown[]\r\n}\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction shouldUsePostMessageBridge(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nfunction bridgeTwitchGet(\r\n projectId: string,\r\n body: TwitchGetApiBody\r\n): Promise<unknown> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useTwitch requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useTwitch: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok) resolve(d.result)\r\n else\r\n reject(\r\n new Error(typeof d.error === \"string\" ? d.error : \"twitch_get_failed\")\r\n )\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useTwitch: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage(\r\n { type: BRIDGE_TYPE, requestId, projectId, ...body },\r\n \"*\"\r\n )\r\n })\r\n}\r\n\r\nasync function fetchTwitchGet(\r\n projectId: string,\r\n body: TwitchGetApiBody\r\n): Promise<unknown> {\r\n const res = await fetch(\r\n `/api/projects/${encodeURIComponent(projectId)}/twitch-get`,\r\n {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n }\r\n )\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n const json = (await res.json()) as { result?: unknown }\r\n return json.result\r\n}\r\n\r\n/**\r\n * Client Twitch en lecture seule (Twurple via le backend du projet).\r\n * Même surface GET que `ctx.twitch` dans les handlers runtime — pas les mutations.\r\n *\r\n * Requiert `config.projectId` sur `SparkleProvider`.\r\n */\r\nexport function useTwitch(): TwitchGetClient {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId\r\n\r\n const invoke = useCallback(\r\n async (namespace: string, method: string, args: unknown[]) => {\r\n if (!projectId?.trim()) {\r\n throw new Error(\r\n \"useTwitch: renseignez `config.projectId` sur SparkleProvider.\"\r\n )\r\n }\r\n const body: TwitchGetApiBody = {\r\n path: [namespace, method],\r\n args,\r\n }\r\n if (shouldUsePostMessageBridge()) {\r\n return bridgeTwitchGet(projectId, body)\r\n }\r\n return fetchTwitchGet(projectId, body)\r\n },\r\n [projectId]\r\n )\r\n\r\n return useMemo(() => createTwitchGetClient(invoke), [invoke])\r\n}\r\n","import type { TwitchGetClient } from \"./twitch-get-types\"\n\nexport type TwitchGetInvoker = (\n namespace: string,\n method: string,\n args: unknown[]\n) => Promise<unknown>\n\nexport function createTwitchGetClient(\n invoke: TwitchGetInvoker\n): TwitchGetClient {\n const call =\n (ns: string, method: string) =>\n (...args: unknown[]) =>\n invoke(ns, method, args)\n\n return {\n bits: {\n getLeaderboard: call(\"bits\", \"getLeaderboard\"),\n },\n channelPoints: {\n getCustomRewards: call(\"channelPoints\", \"getCustomRewards\"),\n getCustomRewardsByIds: call(\"channelPoints\", \"getCustomRewardsByIds\"),\n getCustomRewardById: call(\"channelPoints\", \"getCustomRewardById\"),\n getRedemptionsByIds: call(\"channelPoints\", \"getRedemptionsByIds\"),\n getRedemptionById: call(\"channelPoints\", \"getRedemptionById\"),\n getRedemptions: call(\"channelPoints\", \"getRedemptions\"),\n },\n channel: {\n getChannelInfoById: call(\"channel\", \"getChannelInfoById\"),\n getFollowDate: call(\"channel\", \"getFollowDate\"),\n getChannelEditors: call(\"channel\", \"getChannelEditors\"),\n getVips: call(\"channel\", \"getVips\"),\n checkVipForUsers: call(\"channel\", \"checkVipForUsers\"),\n checkVipForUser: call(\"channel\", \"checkVipForUser\"),\n getChannelFollowerCount: call(\"channel\", \"getChannelFollowerCount\"),\n },\n chat: {\n getChatters: call(\"chat\", \"getChatters\"),\n getSettings: call(\"chat\", \"getSettings\"),\n getSettingsPrivileged: call(\"chat\", \"getSettingsPrivileged\"),\n getColorsForUsers: call(\"chat\", \"getColorsForUsers\"),\n getColorForUser: call(\"chat\", \"getColorForUser\"),\n },\n clips: {\n getClips: call(\"clips\", \"getClips\"),\n getClipById: call(\"clips\", \"getClipById\"),\n getClipsByIds: call(\"clips\", \"getClipsByIds\"),\n },\n goals: {\n getGoals: call(\"goals\", \"getGoals\"),\n },\n polls: {\n getPolls: call(\"polls\", \"getPolls\"),\n getPollsByIds: call(\"polls\", \"getPollsByIds\"),\n getPollById: call(\"polls\", \"getPollById\"),\n },\n predictions: {\n getPredictions: call(\"predictions\", \"getPredictions\"),\n getPredictionsByIds: call(\"predictions\", \"getPredictionsByIds\"),\n getPredictionById: call(\"predictions\", \"getPredictionById\"),\n },\n streams: {\n getStream: call(\"streams\", \"getStream\"),\n getStreamMarkers: call(\"streams\", \"getStreamMarkers\"),\n },\n subscriptions: {\n getSubscriptions: call(\"subscriptions\", \"getSubscriptions\"),\n getSubscriptionsForUsers: call(\n \"subscriptions\",\n \"getSubscriptionsForUsers\"\n ),\n getSubscriptionForUser: call(\"subscriptions\", \"getSubscriptionForUser\"),\n checkUserSubscription: call(\"subscriptions\", \"checkUserSubscription\"),\n },\n moderation: {\n getBanned: call(\"moderation\", \"getBanned\"),\n checkBan: call(\"moderation\", \"checkBan\"),\n getModerators: call(\"moderation\", \"getModerators\"),\n checkModerator: call(\"moderation\", \"checkModerator\"),\n getBlockedTerms: call(\"moderation\", \"getBlockedTerms\"),\n },\n utils: {\n getUserById: call(\"utils\", \"getUserById\"),\n getUserByName: call(\"utils\", \"getUserByName\"),\n getUserByIds: call(\"utils\", \"getUserByIds\"),\n getUserByNames: call(\"utils\", \"getUserByNames\"),\n },\n } as unknown as TwitchGetClient\n}\n","import { useCallback, useContext, useEffect, useRef, useState } from \"react\"\r\n\r\nimport { RealtimeContext } from \"../providers/realtimeProvider\"\r\nimport { useSparkleConfig } from \"../sparkle-config-context\"\r\nimport {\r\n coerceChannelLabelsSnapshot,\r\n emptyChannelLabels,\r\n type ChannelLabelsSnapshot,\r\n} from \"../types/labels\"\r\n\r\nconst LABELS_SNAPSHOT_KEY = \"labels:snapshot\"\r\nconst BRIDGE_TYPE = \"sparkle-labels-get\"\r\nconst BRIDGE_RESULT = \"sparkle-labels-get-result\"\r\n\r\nfunction newRequestId(): string {\r\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\r\n return crypto.randomUUID()\r\n }\r\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\r\n}\r\n\r\nfunction isEmbeddedFrame(): boolean {\r\n if (typeof window === \"undefined\") return false\r\n try {\r\n return window.self !== window.top\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nfunction bridgeLabelsGet(projectId: string): Promise<ChannelLabelsSnapshot> {\r\n if (typeof window === \"undefined\") {\r\n return Promise.reject(new Error(\"useLabels requires a browser environment\"))\r\n }\r\n const target = window.top ?? window.parent\r\n if (!target || target === window) {\r\n return Promise.reject(\r\n new Error(\r\n \"useLabels: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\r\n )\r\n )\r\n }\r\n\r\n return new Promise((resolve, reject) => {\r\n const requestId = newRequestId()\r\n const onMsg = (ev: MessageEvent) => {\r\n const d = ev.data\r\n if (!d || d.type !== BRIDGE_RESULT || d.requestId !== requestId) return\r\n window.removeEventListener(\"message\", onMsg)\r\n window.clearTimeout(timer)\r\n if (d.ok && d.labels) resolve(d.labels as ChannelLabelsSnapshot)\r\n else\r\n reject(\r\n new Error(typeof d.error === \"string\" ? d.error : \"labels_get_failed\")\r\n )\r\n }\r\n const timer = window.setTimeout(() => {\r\n window.removeEventListener(\"message\", onMsg)\r\n reject(new Error(\"useLabels: délai dépassé\"))\r\n }, 45_000)\r\n window.addEventListener(\"message\", onMsg)\r\n target.postMessage({ type: BRIDGE_TYPE, requestId, projectId }, \"*\")\r\n })\r\n}\r\n\r\nasync function fetchLabels(projectId: string): Promise<ChannelLabelsSnapshot> {\r\n const res = await fetch(\r\n `/api/projects/${encodeURIComponent(projectId)}/labels`,\r\n {\r\n method: \"GET\",\r\n credentials: \"include\",\r\n }\r\n )\r\n if (!res.ok) {\r\n const err = (await res.json().catch(() => null)) as { error?: string } | null\r\n throw new Error(err?.error ?? res.statusText)\r\n }\r\n const json = (await res.json()) as { labels?: ChannelLabelsSnapshot }\r\n return json.labels ?? emptyChannelLabels()\r\n}\r\n\r\nasync function loadLabelsFallback(\r\n projectId: string,\r\n useBridge: boolean\r\n): Promise<ChannelLabelsSnapshot> {\r\n if (useBridge) return bridgeLabelsGet(projectId)\r\n return fetchLabels(projectId)\r\n}\r\n\r\n/**\r\n * Agrégats chaîne (session, total, latest, recent) — même données que `ctx.labels.get()`.\r\n * Poussés en temps réel via WebSocket (`labels:snapshot`, comme `useRealtime`).\r\n * Requiert `config.projectId` et `config.userId` sur `SparkleProvider`.\r\n */\r\nexport function useLabels(): ChannelLabelsSnapshot {\r\n const config = useSparkleConfig()\r\n const projectId = config.projectId?.trim() ?? \"\"\r\n const userId = config.userId?.trim() ?? \"\"\r\n const realtime = useContext(RealtimeContext)\r\n const useRealtimeLabels = Boolean(userId)\r\n const useBridge = isEmbeddedFrame() && !useRealtimeLabels\r\n\r\n const [labels, setLabels] = useState<ChannelLabelsSnapshot>(emptyChannelLabels)\r\n const requestIdRef = useRef(0)\r\n const realtimeSnapshot = realtime.values[LABELS_SNAPSHOT_KEY]\r\n\r\n useEffect(() => {\r\n if (!useRealtimeLabels) return\r\n realtime.subscribe([LABELS_SNAPSHOT_KEY])\r\n }, [useRealtimeLabels, realtime])\r\n\r\n useEffect(() => {\r\n if (!useRealtimeLabels) return\r\n const parsed = coerceChannelLabelsSnapshot(realtimeSnapshot)\r\n if (parsed) setLabels(parsed)\r\n }, [useRealtimeLabels, realtimeSnapshot])\r\n\r\n const refresh = useCallback(async () => {\r\n if (!projectId) return\r\n const requestId = ++requestIdRef.current\r\n try {\r\n const next = await loadLabelsFallback(projectId, useBridge)\r\n if (requestId === requestIdRef.current) {\r\n setLabels(next)\r\n }\r\n } catch {\r\n // Conserve la dernière valeur connue.\r\n }\r\n }, [projectId, useBridge])\r\n\r\n useEffect(() => {\r\n if (!projectId) {\r\n setLabels(emptyChannelLabels())\r\n return\r\n }\r\n\r\n if (useRealtimeLabels) {\r\n const parsed = coerceChannelLabelsSnapshot(realtimeSnapshot)\r\n if (!parsed) void refresh()\r\n return\r\n }\r\n\r\n void refresh()\r\n }, [projectId, refresh, useRealtimeLabels, realtimeSnapshot])\r\n\r\n return labels\r\n}\r\n","export type ChannelLabelCounts = {\r\n followers: number\r\n subscribers: number\r\n cheers: number\r\n tips: number\r\n}\r\n\r\nexport type ChannelLabelLatest = {\r\n followers: string\r\n subscribers: string\r\n cheers: { amount: number; user: string }\r\n tips: { amount: number; user: string }\r\n}\r\n\r\nexport type ChannelLabelRecent = {\r\n followers: string[]\r\n subscribers: string[]\r\n cheers: string[]\r\n tips: string[]\r\n}\r\n\r\nexport type ChannelLabelsSnapshot = {\r\n session: ChannelLabelCounts\r\n total: ChannelLabelCounts\r\n latest: ChannelLabelLatest\r\n recent: ChannelLabelRecent\r\n}\r\n\r\nfunction parseCounts(raw: unknown): ChannelLabelsSnapshot[\"session\"] | null {\r\n if (!raw || typeof raw !== \"object\") return null\r\n const record = raw as Record<string, unknown>\r\n const read = (key: keyof ChannelLabelsSnapshot[\"session\"]) => {\r\n const value = Number(record[key])\r\n return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0\r\n }\r\n return {\r\n followers: read(\"followers\"),\r\n subscribers: read(\"subscribers\"),\r\n cheers: read(\"cheers\"),\r\n tips: read(\"tips\"),\r\n }\r\n}\r\n\r\nfunction parseLatest(raw: unknown): ChannelLabelsSnapshot[\"latest\"] | null {\r\n if (!raw || typeof raw !== \"object\") return null\r\n const record = raw as Record<string, unknown>\r\n const cheers =\r\n record.cheers && typeof record.cheers === \"object\"\r\n ? (record.cheers as Record<string, unknown>)\r\n : null\r\n const tips =\r\n record.tips && typeof record.tips === \"object\"\r\n ? (record.tips as Record<string, unknown>)\r\n : null\r\n\r\n return {\r\n followers: typeof record.followers === \"string\" ? record.followers : \"\",\r\n subscribers:\r\n typeof record.subscribers === \"string\" ? record.subscribers : \"\",\r\n cheers: {\r\n amount:\r\n cheers && Number.isFinite(Number(cheers.amount))\r\n ? Math.max(0, Math.floor(Number(cheers.amount)))\r\n : 0,\r\n user: cheers && typeof cheers.user === \"string\" ? cheers.user : \"\",\r\n },\r\n tips: {\r\n amount:\r\n tips && Number.isFinite(Number(tips.amount)) ? Number(tips.amount) : 0,\r\n user: tips && typeof tips.user === \"string\" ? tips.user : \"\",\r\n },\r\n }\r\n}\r\n\r\nfunction parseRecentList(raw: unknown): string[] {\r\n if (!Array.isArray(raw)) return []\r\n return raw.filter((item): item is string => typeof item === \"string\")\r\n}\r\n\r\nexport function coerceChannelLabelsSnapshot(\r\n raw: unknown\r\n): ChannelLabelsSnapshot | null {\r\n if (!raw || typeof raw !== \"object\") return null\r\n\r\n const record = raw as Record<string, unknown>\r\n const session = parseCounts(record.session)\r\n const total = parseCounts(record.total)\r\n const latest = parseLatest(record.latest)\r\n const recentRaw =\r\n record.recent && typeof record.recent === \"object\"\r\n ? (record.recent as Record<string, unknown>)\r\n : null\r\n\r\n if (!session || !total || !latest || !recentRaw) return null\r\n\r\n return {\r\n session,\r\n total,\r\n latest,\r\n recent: {\r\n followers: parseRecentList(recentRaw.followers),\r\n subscribers: parseRecentList(recentRaw.subscribers),\r\n cheers: parseRecentList(recentRaw.cheers),\r\n tips: parseRecentList(recentRaw.tips),\r\n },\r\n }\r\n}\r\n\r\nexport const emptyChannelLabels = (): ChannelLabelsSnapshot => ({\r\n session: {\r\n followers: 0,\r\n subscribers: 0,\r\n cheers: 0,\r\n tips: 0,\r\n },\r\n total: {\r\n followers: 0,\r\n subscribers: 0,\r\n cheers: 0,\r\n tips: 0,\r\n },\r\n latest: {\r\n followers: \"\",\r\n subscribers: \"\",\r\n cheers: { amount: 0, user: \"\" },\r\n tips: { amount: 0, user: \"\" },\r\n },\r\n recent: {\r\n followers: [],\r\n subscribers: [],\r\n cheers: [],\r\n tips: [],\r\n },\r\n})\r\n","import { useCallback, useEffect, useRef, useState } from \"react\"\nimport { useSparkleConfig } from \"../sparkle-config-context\"\n\nexport interface SpotifyTrack {\n isPlaying: boolean\n title: string | null\n artist: string | null\n album: string | null\n coverUrl: string | null\n durationMs: number\n progressMs: number\n}\n\nexport interface UseSpotifyResult {\n track: SpotifyTrack | null\n loading: boolean\n error: Error | null\n refresh: () => Promise<void>\n}\n\nfunction newRequestId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID()\n }\n return `${Date.now()}-${Math.random().toString(16).slice(2)}`\n}\n\nfunction isEmbeddedFrame(): boolean {\n if (typeof window === \"undefined\") return false\n try {\n return window.self !== window.top\n } catch {\n return true\n }\n}\n\nfunction bridgeSpotifyGet(projectId: string): Promise<SpotifyTrack | null> {\n if (typeof window === \"undefined\") {\n return Promise.reject(new Error(\"useSpotify requires a browser environment\"))\n }\n const target = window.top ?? window.parent\n if (!target || target === window) {\n return Promise.reject(\n new Error(\n \"useSpotify: aucun parent — utilisez la preview éditeur ou une page intégrée.\"\n )\n )\n }\n\n return new Promise((resolve, reject) => {\n const requestId = newRequestId()\n const onMsg = (ev: MessageEvent) => {\n const d = ev.data\n if (!d || d.type !== \"sparkle-spotify-get-result\" || d.requestId !== requestId) return\n window.removeEventListener(\"message\", onMsg)\n window.clearTimeout(timer)\n if (d.ok) resolve(d.track as SpotifyTrack | null)\n else\n reject(\n new Error(typeof d.error === \"string\" ? d.error : \"spotify_get_failed\")\n )\n }\n const timer = window.setTimeout(() => {\n window.removeEventListener(\"message\", onMsg)\n reject(new Error(\"useSpotify: délai dépassé\"))\n }, 45_000)\n window.addEventListener(\"message\", onMsg)\n target.postMessage({ type: \"sparkle-spotify-get\", requestId, projectId }, \"*\")\n })\n}\n\nasync function fetchSpotify(projectId: string): Promise<SpotifyTrack | null> {\n const res = await fetch(\n `/api/projects/${encodeURIComponent(projectId)}/spotify`,\n {\n method: \"GET\",\n credentials: \"include\",\n }\n )\n if (!res.ok) {\n const err = (await res.json().catch(() => null)) as { error?: string } | null\n throw new Error(err?.error ?? res.statusText)\n }\n const json = (await res.json()) as { track?: SpotifyTrack | null }\n return json.track ?? null\n}\n\nexport function useSpotify(options?: { intervalMs?: number }): UseSpotifyResult {\n const config = useSparkleConfig()\n const projectId = config.projectId?.trim() ?? \"\"\n const intervalMs = options?.intervalMs ?? 5000\n const useBridge = isEmbeddedFrame()\n\n const [track, setTrack] = useState<SpotifyTrack | null>(null)\n const [loading, setLoading] = useState(true)\n const [error, setError] = useState<Error | null>(null)\n\n const requestIdRef = useRef(0)\n\n const refresh = useCallback(async () => {\n if (!projectId) {\n setTrack(null)\n setLoading(false)\n return\n }\n\n const requestId = ++requestIdRef.current\n try {\n const data = useBridge\n ? await bridgeSpotifyGet(projectId)\n : await fetchSpotify(projectId)\n\n if (requestId === requestIdRef.current) {\n setTrack(data)\n setError(null)\n }\n } catch (err) {\n if (requestId === requestIdRef.current) {\n setError(err instanceof Error ? err : new Error(String(err)))\n }\n } finally {\n if (requestId === requestIdRef.current) {\n setLoading(false)\n }\n }\n }, [projectId, useBridge])\n\n useEffect(() => {\n void refresh()\n }, [refresh])\n\n useEffect(() => {\n if (intervalMs <= 0 || !projectId) return\n\n const timer = setInterval(() => {\n void refresh()\n }, intervalMs)\n\n return () => clearInterval(timer)\n }, [refresh, intervalMs, projectId])\n\n return { track, loading, error, refresh }\n}\n"],"mappings":"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","names":["React","useMemo","useEffect","useState","React","createContext","SparkleAuthContext","AuthProvider","children","config","React","useCallback","useContext","useRef","createContext","useEffect","useState","EventEmitter","z","subscribeSchema","hookSchema","unsubscribeSchema","updateSchema","messageSchema","WebSocketClient","url","data","jwt","authenticatedUrl","event","channels","newChannels","channel","hook","RealtimeContext","createContext","WebSocketProvider","children","config","values","setValues","useState","hooks","setHooks","socket","useRef","token","useContext","SparkleAuthContext","useEffect","WebSocketClient","message","data","type","payload","prev","value","num","subscribe","useCallback","keys","listen","hook","React","React","createContext","useCallback","useEffect","useRef","useState","ObsContext","ObsProvider","url","children","status","setStatus","obsRef","cancelledRef","reconnectTimer","OBSWebSocket","obs","doConnect","call","requestType","requestData","createContext","useContext","SparkleConfigContext","useSparkleConfig","cfg","ClientGate","children","ready","setReady","useState","useEffect","defaultConfig","SparkleProvider","config","mergedConfig","obsUrl","useMemo","inner","React","SparkleConfigContext","AuthProvider","WebSocketProvider","ObsProvider","useContext","useEffect","useMemo","useRealtime","key","defaultValue","context","useContext","RealtimeContext","keys","useMemo","useEffect","k","useEffect","useState","WIDGET_SESSION_REQ","WIDGET_SESSION_RES","newRequestId","isEmbedded","parseSessionMessage","d","o","userId","username","profilePicture","requestSessionViaParent","target","resolve","requestId","onMsg","ev","timer","fetchSessionPayload","res","data","useAuth","setUserId","setUsername","setProfilePicture","cancelled","p","useContext","useEffect","SparkleHookKeys","useHook","hook","context","useContext","RealtimeContext","useEffect","useCallback","useMemo","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","bridgeInvoke","projectId","body","target","resolve","reject","requestId","onMsg","ev","d","timer","shouldUsePostMessageBridge","fetchInvoke","res","err","useTask","useSparkleConfig","invoke","useCallback","taskId","payload","invokeUntil","delaySeconds","useMemo","useCallback","useMemo","createTwitchGetClient","invoke","call","ns","method","args","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","shouldUsePostMessageBridge","bridgeTwitchGet","projectId","body","target","resolve","reject","requestId","onMsg","ev","d","timer","fetchTwitchGet","res","err","useTwitch","useSparkleConfig","invoke","useCallback","namespace","method","args","useMemo","createTwitchGetClient","useCallback","useContext","useEffect","useRef","useState","parseCounts","raw","record","read","key","value","parseLatest","cheers","tips","parseRecentList","item","coerceChannelLabelsSnapshot","session","total","latest","recentRaw","emptyChannelLabels","LABELS_SNAPSHOT_KEY","BRIDGE_TYPE","BRIDGE_RESULT","newRequestId","isEmbeddedFrame","bridgeLabelsGet","projectId","target","resolve","reject","requestId","onMsg","ev","d","timer","fetchLabels","res","err","emptyChannelLabels","loadLabelsFallback","useBridge","useLabels","config","useSparkleConfig","userId","realtime","useContext","RealtimeContext","useRealtimeLabels","labels","setLabels","useState","requestIdRef","useRef","realtimeSnapshot","useEffect","parsed","coerceChannelLabelsSnapshot","refresh","useCallback","next","useCallback","useEffect","useRef","useState","newRequestId","isEmbeddedFrame","bridgeSpotifyGet","projectId","target","resolve","reject","requestId","onMsg","ev","d","timer","fetchSpotify","res","err","useSpotify","options","useSparkleConfig","intervalMs","useBridge","track","setTrack","useState","loading","setLoading","error","setError","requestIdRef","useRef","refresh","useCallback","data","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkle-react",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "files": [
5
5
  "dist"
6
6
  ],