sparkle-react 0.0.32 → 0.0.33
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 +87 -32
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default, { ReactNode } from 'react';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { ApiClient } from '@twurple/api';
|
|
4
5
|
import { OBSRequestTypes, OBSResponseTypes } from 'obs-websocket-js';
|
|
@@ -7,17 +8,6 @@ export { OBSRequestTypes, OBSResponseTypes } from 'obs-websocket-js';
|
|
|
7
8
|
declare const sparkleConfig: z.ZodObject<{
|
|
8
9
|
userId: z.ZodString;
|
|
9
10
|
wsUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
10
|
-
/** Projet Sparkle (runtime `serverId`) — requis pour `useTask` côté widget public. */
|
|
11
|
-
projectId: z.ZodOptional<z.ZodString>;
|
|
12
|
-
/** Fichier widget courant — requis pour `useTask` via le proxy. */
|
|
13
|
-
fileId: z.ZodOptional<z.ZodString>;
|
|
14
|
-
/**
|
|
15
|
-
* URL ou chemin relatif du proxy Next (ex. `/api/public-widget/task`).
|
|
16
|
-
* Ne pointe jamais vers la runtime directement.
|
|
17
|
-
*/
|
|
18
|
-
taskProxyPath: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
19
|
-
/** Jeton optionnel (ex. session viewer passée par postMessage depuis la page parent). */
|
|
20
|
-
viewerSessionToken: z.ZodOptional<z.ZodString>;
|
|
21
11
|
obs: z.ZodOptional<z.ZodObject<{
|
|
22
12
|
clientId: z.ZodString;
|
|
23
13
|
bridgeUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
@@ -28,28 +18,70 @@ declare const sparkleConfig: z.ZodObject<{
|
|
|
28
18
|
clientId: string;
|
|
29
19
|
bridgeUrl?: string | undefined;
|
|
30
20
|
}>>;
|
|
21
|
+
/** Contexte optionnel pour `useInvokeTask` (widgets page `/u/...` ou preview). */
|
|
22
|
+
publicInvoke: z.ZodOptional<z.ZodObject<{
|
|
23
|
+
/** Projet Sparkle (serverId runtime / `invoke`). */
|
|
24
|
+
projectId: z.ZodString;
|
|
25
|
+
/** Fichier widget courant. */
|
|
26
|
+
widgetFileId: z.ZodString;
|
|
27
|
+
/** Propriétaire de la page publique (body `ownerUserId` pour `/api/public-widget/invoke`). */
|
|
28
|
+
pageOwnerUserId: z.ZodString;
|
|
29
|
+
/**
|
|
30
|
+
* Slug `/u/[slug]` : si défini, `useInvokeTask` appelle
|
|
31
|
+
* `/api/public/u/[slug]/invoke-task`.
|
|
32
|
+
*/
|
|
33
|
+
publicProfileTwitchSlug: z.ZodOptional<z.ZodString>;
|
|
34
|
+
/**
|
|
35
|
+
* Chemin relatif du proxy invoke lorsque `publicProfileTwitchSlug` est absent.
|
|
36
|
+
*/
|
|
37
|
+
invokePath: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
|
38
|
+
}, "strip", z.ZodTypeAny, {
|
|
39
|
+
projectId: string;
|
|
40
|
+
widgetFileId: string;
|
|
41
|
+
pageOwnerUserId: string;
|
|
42
|
+
invokePath: string;
|
|
43
|
+
publicProfileTwitchSlug?: string | undefined;
|
|
44
|
+
}, {
|
|
45
|
+
projectId: string;
|
|
46
|
+
widgetFileId: string;
|
|
47
|
+
pageOwnerUserId: string;
|
|
48
|
+
publicProfileTwitchSlug?: string | undefined;
|
|
49
|
+
invokePath?: string | undefined;
|
|
50
|
+
}>>;
|
|
51
|
+
/**
|
|
52
|
+
* Jeton signé optionnel (`GET /api/public/viewer-session`) — complété par postMessage parent.
|
|
53
|
+
*/
|
|
54
|
+
viewerSessionToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
31
55
|
}, "strip", z.ZodTypeAny, {
|
|
32
56
|
userId: string;
|
|
33
57
|
wsUrl: string;
|
|
34
|
-
taskProxyPath: string;
|
|
35
|
-
projectId?: string | undefined;
|
|
36
|
-
fileId?: string | undefined;
|
|
37
|
-
viewerSessionToken?: string | undefined;
|
|
38
58
|
obs?: {
|
|
39
59
|
clientId: string;
|
|
40
60
|
bridgeUrl: string;
|
|
41
61
|
} | undefined;
|
|
62
|
+
publicInvoke?: {
|
|
63
|
+
projectId: string;
|
|
64
|
+
widgetFileId: string;
|
|
65
|
+
pageOwnerUserId: string;
|
|
66
|
+
invokePath: string;
|
|
67
|
+
publicProfileTwitchSlug?: string | undefined;
|
|
68
|
+
} | undefined;
|
|
69
|
+
viewerSessionToken?: string | null | undefined;
|
|
42
70
|
}, {
|
|
43
71
|
userId: string;
|
|
44
72
|
wsUrl?: string | undefined;
|
|
45
|
-
projectId?: string | undefined;
|
|
46
|
-
fileId?: string | undefined;
|
|
47
|
-
taskProxyPath?: string | undefined;
|
|
48
|
-
viewerSessionToken?: string | undefined;
|
|
49
73
|
obs?: {
|
|
50
74
|
clientId: string;
|
|
51
75
|
bridgeUrl?: string | undefined;
|
|
52
76
|
} | undefined;
|
|
77
|
+
publicInvoke?: {
|
|
78
|
+
projectId: string;
|
|
79
|
+
widgetFileId: string;
|
|
80
|
+
pageOwnerUserId: string;
|
|
81
|
+
publicProfileTwitchSlug?: string | undefined;
|
|
82
|
+
invokePath?: string | undefined;
|
|
83
|
+
} | undefined;
|
|
84
|
+
viewerSessionToken?: string | null | undefined;
|
|
53
85
|
}>;
|
|
54
86
|
type SparkleConfig = z.infer<typeof sparkleConfig>;
|
|
55
87
|
|
|
@@ -57,7 +89,29 @@ interface SparkleProviderProps {
|
|
|
57
89
|
config?: SparkleConfig;
|
|
58
90
|
children: ReactNode;
|
|
59
91
|
}
|
|
60
|
-
declare const SparkleProvider: ({ children, config }: SparkleProviderProps) =>
|
|
92
|
+
declare const SparkleProvider: ({ children, config }: SparkleProviderProps) => React__default.JSX.Element;
|
|
93
|
+
|
|
94
|
+
declare const SparkleConfigContext: React.Context<{
|
|
95
|
+
userId: string;
|
|
96
|
+
wsUrl: string;
|
|
97
|
+
obs?: {
|
|
98
|
+
clientId: string;
|
|
99
|
+
bridgeUrl: string;
|
|
100
|
+
} | undefined;
|
|
101
|
+
publicInvoke?: {
|
|
102
|
+
projectId: string;
|
|
103
|
+
widgetFileId: string;
|
|
104
|
+
pageOwnerUserId: string;
|
|
105
|
+
invokePath: string;
|
|
106
|
+
publicProfileTwitchSlug?: string | undefined;
|
|
107
|
+
} | undefined;
|
|
108
|
+
viewerSessionToken?: string | null | undefined;
|
|
109
|
+
}>;
|
|
110
|
+
declare function SparkleConfigProvider({ value, children, }: {
|
|
111
|
+
value: SparkleConfig;
|
|
112
|
+
children: ReactNode;
|
|
113
|
+
}): React.JSX.Element;
|
|
114
|
+
declare function useSparkleConfig(): SparkleConfig;
|
|
61
115
|
|
|
62
116
|
type RealtimeValue = string | number | boolean | {
|
|
63
117
|
[key: string]: any;
|
|
@@ -66,7 +120,6 @@ type RealtimeValue = string | number | boolean | {
|
|
|
66
120
|
declare function useRealtime<T extends RealtimeValue>(key: string, defaultValue?: T): T[];
|
|
67
121
|
|
|
68
122
|
declare function useAuth(): {
|
|
69
|
-
/** Identifiant Sparkle du propriétaire du widget (historique : `token`). */
|
|
70
123
|
token: string | null | undefined;
|
|
71
124
|
viewerSessionToken: string | null;
|
|
72
125
|
};
|
|
@@ -353,18 +406,20 @@ declare function useObs(): {
|
|
|
353
406
|
};
|
|
354
407
|
declare function useObsCall<T extends keyof OBSRequestTypes>(requestType: T): (requestData?: OBSRequestTypes[T]) => Promise<OBSResponseTypes[T]>;
|
|
355
408
|
|
|
356
|
-
type
|
|
357
|
-
|
|
358
|
-
|
|
409
|
+
type InvokeTaskResult = {
|
|
410
|
+
ok: boolean;
|
|
411
|
+
status: number;
|
|
359
412
|
};
|
|
360
413
|
/**
|
|
361
|
-
* Appelle
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
414
|
+
* Appelle un handler TASK (`Handlers.value` = identifiant `createTask`) via le proxy Next.
|
|
415
|
+
* - Si `config.publicInvoke.publicProfileTwitchSlug` est défini : `POST /api/public/u/[slug]/invoke-task`.
|
|
416
|
+
* - Sinon : `POST` sur `publicInvoke.invokePath` (défaut `/api/public-widget/invoke`) avec `ownerUserId` dans le corps.
|
|
417
|
+
* Les cookies de session Better Auth sont envoyés (`credentials: "include"`). Le jeton viewer
|
|
418
|
+
* (`viewerSessionToken`, ex. postMessage parent) est transmis pour le scope utilisateur côté handler.
|
|
365
419
|
*/
|
|
366
|
-
declare function
|
|
367
|
-
|
|
420
|
+
declare function useInvokeTask(): {
|
|
421
|
+
invoke: (task: string, payload?: unknown) => Promise<InvokeTaskResult>;
|
|
422
|
+
ready: boolean;
|
|
368
423
|
};
|
|
369
424
|
|
|
370
425
|
/** Message parent → iframe widget (page publique `/u/...`). */
|
|
@@ -373,4 +428,4 @@ declare const SPARKLE_PUBLIC_VIEWER_MSG: {
|
|
|
373
428
|
readonly type: "SPARKLE_VIEWER_TOKEN";
|
|
374
429
|
};
|
|
375
430
|
|
|
376
|
-
export { type
|
|
431
|
+
export { type InvokeTaskResult, SPARKLE_PUBLIC_VIEWER_MSG, SparkleConfigContext, SparkleConfigProvider, type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, useAuth, useHook, useInvokeTask, useObs, useObsCall, useRealtime, useSparkleConfig, useTwitch };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
1
|
+
import k,{useMemo as B,useEffect as ie,useState as ae}from"react";import*as w from"react";import{createContext as _,useContext as H}from"react";var J={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null},x=_(J);function C({value:t,children:r}){return w.createElement(x.Provider,{value:t},r)}function h(){return H(x)}import*as R from"react";import{createContext as D,useEffect as P,useMemo as K,useState as F}from"react";var y={source:"sparkle-public-parent",type:"SPARKLE_VIEWER_TOKEN"},g=D({token:null,viewerSessionToken:null}),E=({children:t})=>{let r=h(),[e,o]=F(r.viewerSessionToken??null);P(()=>{o(r.viewerSessionToken??null)},[r.viewerSessionToken]),P(()=>{let a=s=>{if(s.origin!==window.location.origin)return;let c=s.data;!c||c.source!==y.source||c.type!==y.type||o(typeof c.token=="string"&&c.token.length>0?c.token:null)};return window.addEventListener("message",a),()=>window.removeEventListener("message",a)},[]);let n=K(()=>({token:r.userId,viewerSessionToken:e}),[r.userId,e]);return R.createElement(g.Provider,{value:n},t)};import Q,{useCallback as O,useContext as X,useRef as Z}from"react";import{createContext as ee,useEffect as I,useState as N}from"react";import V from"events";import l from"zod";var G=l.object({type:l.literal("subscribe"),channels:l.array(l.string())}),$=l.object({type:l.literal("hook"),channels:l.array(l.string())}),Y=l.object({type:l.literal("unsubscribe"),channels:l.string()}),Be=l.object({event:l.literal("update"),type:l.enum(["value","hook"]),data:l.object({key:l.string(),value:l.string()})}),Ue=l.union([G,Y,$]),S=class extends V{constructor(e){super();this.socket=null;this.queue=[];this.subscriptions=[];this.hooks=[];this.isAuthenticating=!1;this.jwt=null;this.url=e}send(e){this.socket?.readyState===WebSocket.OPEN?this.socket?.send(JSON.stringify(e)):this.queue.push(e)}authenticate(e){this.isAuthenticating||(this.isAuthenticating=!0,this.jwt=e,this.connect())}connect(){let e=this.url+"?jwt="+this.jwt;this.socket=new WebSocket(e),this.socket.onopen=()=>{this.queue.forEach(o=>this.send(o)),this.queue=[]},this.socket.onmessage=o=>{let n=JSON.parse(o.data);this.emit("update",n)},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(e){let o=e.filter(n=>!this.subscriptions.includes(n));o.length&&(this.subscriptions=[...this.subscriptions,...o],console.log("subscribe",e),this.send({type:"subscribe",channels:e}))}hook(e){let o=[e].filter(n=>!this.hooks.includes(n));o.length&&(this.hooks=[...this.hooks,...o],this.send({type:"hook",channels:[e]}))}unsubscribe(e){this.send({type:"unsubscribe",channels:e})}disconnect(){this.socket&&(this.socket.onclose=()=>{},this.socket.close())}};var v=ee({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),A=({children:t})=>{let r=h(),[e,o]=N({}),[n,a]=N({}),s=Z(null),{token:c}=X(g);I(()=>(s.current=new S(r.wsUrl),s.current.on("update",u=>{let{data:i,type:z}=u;if(z==="hook"){let d=JSON.parse(i.value);a(m=>({...m,[d.hook]:d.payload})),console.log("receive hooks",d)}else{let d=i.value;console.log("value",d);try{let m=parseFloat(i.value);if(!isNaN(m)&&String(m)===String(i.value).trim())d=m;else try{d=JSON.parse(i.value)}catch{d=i.value}}catch{d=i.value}o(m=>({...m,[i.key]:d})),console.log("receive values",e)}}),()=>{s.current?.disconnect()}),[r.wsUrl]),I(()=>{!s.current||!c||s.current.authenticate(c)},[c]);let f=O(u=>{s.current?.subscribe(u)},[s]),p=O(u=>{s.current?.hook(u)},[s]);return Q.createElement(v.Provider,{value:{subscribe:f,listen:p,values:e,hooks:n}},t)};import te,{createContext as re,useCallback as oe,useEffect as ne,useRef as M,useState as se}from"react";var b=re({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function L({url:t,children:r}){let[e,o]=se("disconnected"),n=M(null),a=M(!1);ne(()=>{if(!t)return;a.current=!1;let c;return(async()=>{let{default:p}=await import("obs-websocket-js"),u=new p;n.current=u;let i=async()=>{if(!a.current){o("connecting");try{await u.connect(t),a.current||o("connected")}catch{a.current||(o("error"),c=setTimeout(i,3e3))}}};u.on("ConnectionClosed",()=>{a.current||(o("disconnected"),c=setTimeout(i,3e3))}),u.on("ConnectionError",()=>{a.current||o("error")}),await i()})(),()=>{a.current=!0,clearTimeout(c),n.current?.disconnect(),n.current=null,o("disconnected")}},[t]);let s=oe(async(c,f)=>{let p=n.current;if(!p)throw new Error("OBS not connected");return p.call(c,f)},[]);return te.createElement(b.Provider,{value:{status:e,call:s}},r)}var ce=({children:t})=>{let[r,e]=ae(!1);return ie(()=>{e(!0)},[]),r?t:null},ue={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null},Ze=({children:t,config:r})=>{let e=B(()=>({...ue,...r}),[r]),o=B(()=>e.obs?.clientId?`${e.obs.bridgeUrl??"wss://sparkle-bot-v2.fly.dev/obs-bridge"}?role=client&clientId=${encodeURIComponent(e.obs.clientId)}`:"",[e.obs?.clientId,e.obs?.bridgeUrl]),n=k.createElement(E,null,k.createElement(A,null,k.createElement(ce,null,t)));return k.createElement(C,{value:e},o?k.createElement(L,{url:o},n):n)};import{useContext as le,useEffect as pe,useMemo as de}from"react";function nt(t,r){let e=le(v),o=de(()=>[t],[t]);if(!e)throw new Error("You must use useRealtime inside a SparkleProvider");return pe(()=>{e.subscribe([t])},[o]),o.map(n=>e.values[n]??r)}import{useContext as fe}from"react";function ct(){let t=fe(g);return{token:t.token,viewerSessionToken:t.viewerSessionToken}}import{useContext as me,useEffect as ge}from"react";var ft=["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 mt(t){let r=me(v);if(!r)throw new Error("You must use useHook inside a SparkleProvider");return ge(()=>{r.listen(t)},[t]),r.hooks[t]}import{ApiClient as be}from"@twurple/api";import{StaticAuthProvider as ye}from"@twurple/auth";import{useEffect as Te,useState as we}from"react";import{useCallback as W,useState as he,useRef as ve,useLayoutEffect as ke}from"react";var T=()=>{};var U=typeof window<"u";var Se=(t,r,e)=>{if(!U)return[r,T,T];if(!t)throw new Error("useLocalStorage key may not be falsy");let o=e?e.raw?p=>p:e.deserializer:JSON.parse,n=ve(p=>{try{let u=e?e.raw?String:e.serializer:JSON.stringify,i=localStorage.getItem(p);return i!==null?o(i):(r&&localStorage.setItem(p,u(r)),r)}catch{return r}}),[a,s]=he(()=>n.current(t));ke(()=>s(n.current(t)),[t]);let c=W(p=>{try{let u=typeof p=="function"?p(a):p;if(typeof u>"u")return;let i;e?e.raw?typeof u=="string"?i=u:i=JSON.stringify(u):e.serializer?i=e.serializer(u):i=JSON.stringify(u):i=JSON.stringify(u),localStorage.setItem(t,i),s(o(i))}catch{}},[t,s]),f=W(()=>{try{localStorage.removeItem(t),s(void 0)}catch{}},[t,s]);return[a,c,f]},j=Se;function Pt(t){let[r,e]=we(null),[o]=j("twitch");return Te(()=>{console.log("env",process.env.TWITCH_CLIENT_ID),console.log("init");let n=new be({authProvider:new ye("u9lt242tz2pn5hl5x444ls2xllnvip",o,["bits:read","channel:manage:moderators","channel:manage:polls","channel:manage:predictions","channel:manage:redemptions","channel:manage:vips","channel:read:polls","channel:read:predictions","channel:read:redemptions","channel:read:subscriptions","channel:read:vips","moderation:read","moderator:manage:announcements","channel:moderate","moderator:read:blocked_terms","moderator:read:chatters","moderator:read:followers","user:read:email"])});return e(n),()=>{e(null)}},[]),{apiClient:r}}import{useCallback as xe,useContext as q}from"react";function It(){let{status:t,call:r}=q(b);return{status:t,connected:t==="connected",call:r}}function Nt(t){let{call:r}=q(b);return xe(e=>r(t,e),[r,t])}import{useCallback as Ce,useContext as Pe,useMemo as Re}from"react";function Ut(){let{publicInvoke:t}=h(),{viewerSessionToken:r}=Pe(g),e=Ce(async(o,n)=>{if(!t?.projectId||!t.widgetFileId||!t.pageOwnerUserId)throw new Error("useInvokeTask: config.publicInvoke (projectId, widgetFileId, pageOwnerUserId) est requis");let a=t.publicProfileTwitchSlug?.trim(),s=a?`/api/public/u/${encodeURIComponent(a)}/invoke-task`:(t.invokePath??"/api/public-widget/invoke").trim(),c=JSON.stringify(a?{projectId:t.projectId,fileId:t.widgetFileId,taskId:o,payload:n??{},viewerToken:r}:{ownerUserId:t.pageOwnerUserId,projectId:t.projectId,fileId:t.widgetFileId,task:o,payload:n??{},viewerToken:r}),f=await fetch(s,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:c});return{ok:f.ok,status:f.status}},[t,r]);return Re(()=>({invoke:e,ready:!!t}),[e,t])}export{y as SPARKLE_PUBLIC_VIEWER_MSG,x as SparkleConfigContext,C as SparkleConfigProvider,ft as SparkleHookKeys,Ze as SparkleProvider,ct as useAuth,mt as useHook,Ut as useInvokeTask,It as useObs,Nt as useObsCall,nt as useRealtime,h as useSparkleConfig,Pt as useTwitch};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.tsx","../src/context/sparkle-config-context.tsx","../src/providers/authProvider.tsx","../src/providers/realtimeProvider.tsx","../src/lib/websocket.ts","../src/providers/obsProvider.tsx","../src/utils/config.ts","../src/hooks/useRealtime.ts","../src/hooks/useAuth.ts","../src/hooks/useHook.ts","../src/hooks/useTwitch.ts","../src/utils/hooks/useLocalStorage.ts","../src/utils/utils.ts","../src/hooks/useObs.ts","../src/hooks/useTask.ts"],"sourcesContent":["import React, { useMemo, type ReactNode, useEffect, useState } from \"react\"\r\nimport { SparkleConfigProvider } from \"./context/sparkle-config-context\"\r\nimport { AuthProvider } from \"./providers/authProvider\"\r\nimport { WebSocketProvider } from \"./providers/realtimeProvider\"\r\nimport { ObsProvider } from \"./providers/obsProvider\"\r\nimport { SparkleConfig, sparkleConfig } from \"./utils/config\"\r\n\r\nconst ClientGate = ({ children }: { children: ReactNode }) => {\r\n const [ready, setReady] = useState(false)\r\n\r\n useEffect(() => {\r\n setReady(true)\r\n }, [])\r\n\r\n if (!ready) return null\r\n return children\r\n}\r\n\r\nexport interface SparkleProviderProps {\r\n config?: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nconst defaultConfig: SparkleConfig = sparkleConfig.parse({\r\n userId: \"\",\r\n})\r\n\r\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\r\n const mergedConfig = sparkleConfig.parse({ ...defaultConfig, ...config })\r\n\r\n const obsUrl = useMemo(() => {\r\n if (!mergedConfig.obs?.clientId) return \"\"\r\n const base =\r\n mergedConfig.obs.bridgeUrl ?? \"wss://sparkle-bot-v2.fly.dev/obs-bridge\"\r\n return `${base}?role=client&clientId=${encodeURIComponent(mergedConfig.obs.clientId)}`\r\n }, [mergedConfig.obs?.clientId, mergedConfig.obs?.bridgeUrl])\r\n\r\n const inner = (\r\n <SparkleConfigProvider value={mergedConfig}>\r\n <AuthProvider config={mergedConfig}>\r\n <WebSocketProvider config={mergedConfig}>\r\n <ClientGate>{children}</ClientGate>\r\n </WebSocketProvider>\r\n </AuthProvider>\r\n </SparkleConfigProvider>\r\n )\r\n\r\n if (obsUrl) {\r\n return <ObsProvider url={obsUrl}>{inner}</ObsProvider>\r\n }\r\n\r\n return inner\r\n}\r\n","import { createContext, useContext, type ReactNode } from \"react\"\r\nimport type { SparkleConfig } from \"../utils/config\"\r\n\r\nconst SparkleConfigContext = createContext<SparkleConfig | null>(null)\r\n\r\nexport function SparkleConfigProvider({\r\n value,\r\n children,\r\n}: {\r\n value: SparkleConfig\r\n children: ReactNode\r\n}) {\r\n return (\r\n <SparkleConfigContext.Provider value={value}>\r\n {children}\r\n </SparkleConfigContext.Provider>\r\n )\r\n}\r\n\r\nexport function useSparkleConfig(): SparkleConfig {\r\n const ctx = useContext(SparkleConfigContext)\r\n if (!ctx) {\r\n throw new Error(\"useSparkleConfig doit être utilisé dans un SparkleProvider\")\r\n }\r\n return ctx\r\n}\r\n","import * as React from \"react\"\r\n\r\nimport { type ReactNode, createContext, useEffect, useState } from \"react\"\r\n\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\n/** Message parent → iframe widget (page publique `/u/...`). */\r\nexport const SPARKLE_PUBLIC_VIEWER_MSG = {\r\n source: \"sparkle-public-parent\",\r\n type: \"SPARKLE_VIEWER_TOKEN\",\r\n} as const\r\n\r\ninterface AuthContextType {\r\n /** Identifiant utilisateur Sparkle du propriétaire du widget (auth WS, etc.). */\r\n token: string | undefined | null\r\n /** Jeton signé optionnel du viewer connecté (Twitch via Sparkle). */\r\n viewerSessionToken: string | null\r\n}\r\n\r\nexport const SparkleAuthContext = createContext<AuthContextType>({\r\n token: null,\r\n viewerSessionToken: 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 const [viewerSessionToken, setViewerSessionToken] = useState<string | null>(\r\n config.viewerSessionToken ?? null\r\n )\r\n\r\n useEffect(() => {\r\n setViewerSessionToken(config.viewerSessionToken ?? null)\r\n }, [config.viewerSessionToken])\r\n\r\n useEffect(() => {\r\n const onMessage = (event: MessageEvent) => {\r\n if (event.origin !== window.location.origin) return\r\n const data = event.data as\r\n | {\r\n source?: string\r\n type?: string\r\n token?: string | null\r\n }\r\n | undefined\r\n if (\r\n !data ||\r\n data.source !== SPARKLE_PUBLIC_VIEWER_MSG.source ||\r\n data.type !== SPARKLE_PUBLIC_VIEWER_MSG.type\r\n ) {\r\n return\r\n }\r\n setViewerSessionToken(\r\n typeof data.token === \"string\" && data.token.length > 0\r\n ? data.token\r\n : null\r\n )\r\n }\r\n window.addEventListener(\"message\", onMessage)\r\n return () => window.removeEventListener(\"message\", onMessage)\r\n }, [])\r\n\r\n return (\r\n <SparkleAuthContext.Provider\r\n value={{\r\n token: config.userId,\r\n viewerSessionToken,\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 { z } from \"zod\"\r\n\r\nconst obsConfig = z\r\n .object({\r\n clientId: z.string(),\r\n bridgeUrl: z\r\n .string()\r\n .optional()\r\n .default(\"wss://sparkle-bot-v2.fly.dev/obs-bridge\"),\r\n })\r\n .optional()\r\n\r\nexport const sparkleConfig = z.object({\r\n userId: z.string(),\r\n wsUrl: z.string().optional().default(\"wss://sparkle-bot-v2.fly.dev/ws\"),\r\n /** Projet Sparkle (runtime `serverId`) — requis pour `useTask` côté widget public. */\r\n projectId: z.string().min(1).optional(),\r\n /** Fichier widget courant — requis pour `useTask` via le proxy. */\r\n fileId: z.string().min(1).optional(),\r\n /**\r\n * URL ou chemin relatif du proxy Next (ex. `/api/public-widget/task`).\r\n * Ne pointe jamais vers la runtime directement.\r\n */\r\n taskProxyPath: z.string().optional().default(\"/api/public-widget/task\"),\r\n /** Jeton optionnel (ex. session viewer passée par postMessage depuis la page parent). */\r\n viewerSessionToken: z.string().min(1).optional(),\r\n obs: obsConfig,\r\n})\r\n\r\nexport type SparkleConfig = z.infer<typeof sparkleConfig>\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 { useContext } from \"react\"\r\nimport { SparkleAuthContext } from \"../providers/authProvider\"\r\n\r\nexport function useAuth() {\r\n const { token: accessToken, viewerSessionToken } = useContext(\r\n SparkleAuthContext\r\n )\r\n\r\n return {\r\n /** Identifiant Sparkle du propriétaire du widget (historique : `token`). */\r\n token: accessToken,\r\n viewerSessionToken,\r\n }\r\n}\r\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 { ApiClient } from \"@twurple/api\"\r\nimport { StaticAuthProvider } from \"@twurple/auth\"\r\nimport { useEffect, useState } from \"react\"\r\nimport useLocalStorage from \"../utils/hooks/useLocalStorage\"\r\n\r\nexport function useTwitch(asUser?: string | number) {\r\n const [apiClient, setApiClient] = useState<ApiClient | null>(null)\r\n const [twitch] = useLocalStorage<string>(\"twitch\")\r\n\r\n useEffect(() => {\r\n console.log(\"env\", process.env.TWITCH_CLIENT_ID)\r\n\r\n console.log(\"init\")\r\n\r\n const apiClient = new ApiClient({\r\n authProvider: new StaticAuthProvider(\r\n \"u9lt242tz2pn5hl5x444ls2xllnvip\",\r\n twitch!,\r\n [\r\n \"bits:read\",\r\n \"channel:manage:moderators\",\r\n \"channel:manage:polls\",\r\n \"channel:manage:predictions\",\r\n \"channel:manage:redemptions\",\r\n \"channel:manage:vips\",\r\n\r\n \"channel:read:polls\",\r\n \"channel:read:predictions\",\r\n \"channel:read:redemptions\",\r\n \"channel:read:subscriptions\",\r\n \"channel:read:vips\",\r\n \"moderation:read\",\r\n \"moderator:manage:announcements\",\r\n \"channel:moderate\",\r\n \"moderator:read:blocked_terms\",\r\n \"moderator:read:chatters\",\r\n \"moderator:read:followers\",\r\n \"user:read:email\",\r\n ]\r\n ),\r\n })\r\n\r\n setApiClient(apiClient)\r\n\r\n return () => {\r\n setApiClient(null)\r\n }\r\n }, [])\r\n\r\n return {\r\n apiClient,\r\n }\r\n}\r\n","import {\r\n Dispatch,\r\n SetStateAction,\r\n useCallback,\r\n useState,\r\n useRef,\r\n useLayoutEffect,\r\n} from \"react\"\r\nimport { isBrowser, noop } from \"../utils\"\r\n\r\ntype parserOptions<T> =\r\n | {\r\n raw: true\r\n }\r\n | {\r\n raw: false\r\n serializer: (value: T) => string\r\n deserializer: (value: string) => T\r\n }\r\n\r\nconst useLocalStorage = <T>(\r\n key: string,\r\n initialValue?: T,\r\n options?: parserOptions<T>\r\n): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {\r\n if (!isBrowser) {\r\n return [initialValue as T, noop, noop]\r\n }\r\n if (!key) {\r\n throw new Error(\"useLocalStorage key may not be falsy\")\r\n }\r\n\r\n const deserializer = options\r\n ? options.raw\r\n ? (value) => value\r\n : options.deserializer\r\n : JSON.parse\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const initializer = useRef((key: string) => {\r\n try {\r\n const serializer = options\r\n ? options.raw\r\n ? String\r\n : options.serializer\r\n : JSON.stringify\r\n\r\n const localStorageValue = localStorage.getItem(key)\r\n if (localStorageValue !== null) {\r\n return deserializer(localStorageValue)\r\n } else {\r\n initialValue && localStorage.setItem(key, serializer(initialValue))\r\n return initialValue\r\n }\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw. JSON.parse and JSON.stringify\r\n // can throw, too.\r\n return initialValue\r\n }\r\n })\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const [state, setState] = useState<T | undefined>(() =>\r\n initializer.current(key)\r\n )\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n useLayoutEffect(() => setState(initializer.current(key)), [key])\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const set: Dispatch<SetStateAction<T | undefined>> = useCallback(\r\n (valOrFunc) => {\r\n try {\r\n const newState =\r\n typeof valOrFunc === \"function\"\r\n ? (valOrFunc as Function)(state)\r\n : valOrFunc\r\n if (typeof newState === \"undefined\") return\r\n let value: string\r\n\r\n if (options)\r\n if (options.raw)\r\n if (typeof newState === \"string\") value = newState\r\n else value = JSON.stringify(newState)\r\n else if (options.serializer) value = options.serializer(newState)\r\n else value = JSON.stringify(newState)\r\n else value = JSON.stringify(newState)\r\n\r\n localStorage.setItem(key, value)\r\n setState(deserializer(value))\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw. Also JSON.stringify can throw.\r\n }\r\n },\r\n [key, setState]\r\n )\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const remove = useCallback(() => {\r\n try {\r\n localStorage.removeItem(key)\r\n setState(undefined)\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw.\r\n }\r\n }, [key, setState])\r\n\r\n return [state, set, remove]\r\n}\r\n\r\nexport default useLocalStorage\r\n","export const noop = () => {}\r\n\r\nexport function on<T extends Window | Document | HTMLElement | EventTarget>(\r\n obj: T | null,\r\n ...args: Parameters<T[\"addEventListener\"]> | [string, Function | null, ...any]\r\n): void {\r\n if (obj && obj.addEventListener) {\r\n obj.addEventListener(\r\n ...(args as Parameters<HTMLElement[\"addEventListener\"]>)\r\n )\r\n }\r\n}\r\n\r\nexport function off<T extends Window | Document | HTMLElement | EventTarget>(\r\n obj: T | null,\r\n ...args:\r\n | Parameters<T[\"removeEventListener\"]>\r\n | [string, Function | null, ...any]\r\n): void {\r\n if (obj && obj.removeEventListener) {\r\n obj.removeEventListener(\r\n ...(args as Parameters<HTMLElement[\"removeEventListener\"]>)\r\n )\r\n }\r\n}\r\n\r\nexport const isBrowser = typeof window !== \"undefined\"\r\n\r\nexport const isNavigator = typeof navigator !== \"undefined\"\r\n","import { useCallback, useContext } from \"react\"\r\nimport type { OBSRequestTypes, OBSResponseTypes } from \"obs-websocket-js\"\r\nimport { ObsContext } from \"../providers/obsProvider\"\r\n\r\nexport type { OBSRequestTypes, OBSResponseTypes }\r\n\r\nexport function useObs() {\r\n const { status, call } = useContext(ObsContext)\r\n\r\n return {\r\n status,\r\n connected: status === \"connected\",\r\n call,\r\n }\r\n}\r\n\r\nexport function useObsCall<T extends keyof OBSRequestTypes>(requestType: T) {\r\n const { call } = useContext(ObsContext)\r\n\r\n return useCallback(\r\n (requestData?: OBSRequestTypes[T]) => call(requestType, requestData),\r\n [call, requestType]\r\n )\r\n}\r\n","import { useCallback, useContext } from \"react\"\r\nimport { useSparkleConfig } from \"../context/sparkle-config-context\"\r\nimport { SparkleAuthContext } from \"../providers/authProvider\"\r\n\r\nexport type RunTaskOptions = {\r\n /** Surcharge ponctuelle du corps (fusionné avec `payload`). */\r\n merge?: Record<string, unknown>\r\n}\r\n\r\n/**\r\n * Appelle une tâche (`createTask` / `id` de tâche) via le proxy Next configuré\r\n * dans `SparkleProvider` (`taskProxyPath`), sans exposer l’URL de la runtime.\r\n *\r\n * Requiert `projectId`, `fileId` et `userId` (propriétaire) dans la config du provider.\r\n */\r\nexport function useTask() {\r\n const config = useSparkleConfig()\r\n const { viewerSessionToken } = useContext(SparkleAuthContext)\r\n\r\n const runTask = useCallback(\r\n async (taskId: string, payload?: unknown, options?: RunTaskOptions) => {\r\n const ownerUserId = config.userId\r\n const { projectId, fileId, taskProxyPath } = config\r\n if (!ownerUserId?.trim()) {\r\n throw new Error(\"[useTask] SparkleProvider: userId (propriétaire) manquant\")\r\n }\r\n if (!projectId?.trim() || !fileId?.trim()) {\r\n throw new Error(\r\n \"[useTask] SparkleProvider: projectId et fileId sont requis pour invoquer une tâche\"\r\n )\r\n }\r\n if (!taskId?.trim()) {\r\n throw new Error(\"[useTask] taskId vide\")\r\n }\r\n\r\n const path =\r\n taskProxyPath.startsWith(\"http://\") || taskProxyPath.startsWith(\"https://\")\r\n ? taskProxyPath\r\n : taskProxyPath.startsWith(\"/\")\r\n ? taskProxyPath\r\n : `/${taskProxyPath}`\r\n\r\n const body: Record<string, unknown> = {\r\n taskId: taskId.trim(),\r\n ownerUserId,\r\n projectId,\r\n fileId,\r\n payload:\r\n options?.merge && payload !== undefined && typeof payload === \"object\"\r\n ? { ...(payload as object), ...options.merge }\r\n : options?.merge && payload === undefined\r\n ? options.merge\r\n : payload ?? {},\r\n }\r\n\r\n const res = await fetch(path, {\r\n method: \"POST\",\r\n headers: {\r\n \"Content-Type\": \"application/json\",\r\n ...(viewerSessionToken\r\n ? { Authorization: `Bearer ${viewerSessionToken}` }\r\n : {}),\r\n },\r\n credentials: \"include\",\r\n body: JSON.stringify(body),\r\n })\r\n\r\n const text = await res.text()\r\n let json: unknown = undefined\r\n if (text) {\r\n try {\r\n json = JSON.parse(text) as unknown\r\n } catch {\r\n json = text\r\n }\r\n }\r\n\r\n if (!res.ok) {\r\n const msg =\r\n typeof json === \"object\" && json !== null && \"error\" in json\r\n ? String((json as { error?: unknown }).error)\r\n : text || res.statusText\r\n throw new Error(`[useTask] ${res.status}: ${msg}`)\r\n }\r\n\r\n return json\r\n },\r\n [config, viewerSessionToken]\r\n )\r\n\r\n return { runTask }\r\n}\r\n"],"mappings":"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","names":["React","useMemo","useEffect","useState","createContext","useContext","SparkleConfigContext","SparkleConfigProvider","value","children","useSparkleConfig","ctx","React","createContext","useEffect","useState","SPARKLE_PUBLIC_VIEWER_MSG","SparkleAuthContext","AuthProvider","children","config","viewerSessionToken","setViewerSessionToken","onMessage","event","data","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","z","obsConfig","sparkleConfig","ClientGate","children","ready","setReady","useState","useEffect","defaultConfig","sparkleConfig","SparkleProvider","config","mergedConfig","obsUrl","useMemo","inner","React","SparkleConfigProvider","AuthProvider","WebSocketProvider","ObsProvider","useContext","useEffect","useMemo","useRealtime","key","defaultValue","context","useContext","RealtimeContext","keys","useMemo","useEffect","k","useContext","useAuth","accessToken","viewerSessionToken","useContext","SparkleAuthContext","useContext","useEffect","SparkleHookKeys","useHook","hook","context","useContext","RealtimeContext","useEffect","ApiClient","StaticAuthProvider","useEffect","useState","useCallback","useState","useRef","useLayoutEffect","noop","isBrowser","useLocalStorage","key","initialValue","options","isBrowser","noop","deserializer","value","initializer","useRef","serializer","localStorageValue","state","setState","useState","useLayoutEffect","set","useCallback","valOrFunc","newState","remove","useLocalStorage_default","useTwitch","asUser","apiClient","setApiClient","useState","twitch","useLocalStorage_default","useEffect","ApiClient","StaticAuthProvider","useCallback","useContext","useObs","status","call","useContext","ObsContext","useObsCall","requestType","useCallback","requestData","useCallback","useContext","useTask","config","useSparkleConfig","viewerSessionToken","useContext","SparkleAuthContext","useCallback","taskId","payload","options","ownerUserId","projectId","fileId","taskProxyPath","path","body","res","text","json","msg"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/context/sparkle-config-context.tsx","../src/providers/authProvider.tsx","../src/providers/realtimeProvider.tsx","../src/lib/websocket.ts","../src/providers/obsProvider.tsx","../src/hooks/useRealtime.ts","../src/hooks/useAuth.ts","../src/hooks/useHook.ts","../src/hooks/useTwitch.ts","../src/utils/hooks/useLocalStorage.ts","../src/utils/utils.ts","../src/hooks/useObs.ts","../src/hooks/useInvokeTask.ts"],"sourcesContent":["import React, { useMemo, type ReactNode, useEffect, useState } from \"react\"\r\nimport { SparkleConfigProvider } from \"./context/sparkle-config-context\"\r\nimport { AuthProvider } from \"./providers/authProvider\"\r\nimport { WebSocketProvider } from \"./providers/realtimeProvider\"\r\nimport { ObsProvider } from \"./providers/obsProvider\"\r\nimport { SparkleConfig } from \"./utils/config\"\r\n\r\nconst ClientGate = ({ children }: { children: ReactNode }) => {\r\n const [ready, setReady] = useState(false)\r\n\r\n useEffect(() => {\r\n setReady(true)\r\n }, [])\r\n\r\n if (!ready) return null\r\n return children\r\n}\r\n\r\nexport interface SparkleProviderProps {\r\n config?: SparkleConfig\r\n children: ReactNode\r\n}\r\n\r\nconst defaultConfig: SparkleConfig = {\r\n userId: \"\",\r\n wsUrl: \"wss://sparkle-bot-v2.fly.dev/ws\",\r\n viewerSessionToken: null,\r\n}\r\n\r\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\r\n const mergedConfig = useMemo(\r\n () => ({ ...defaultConfig, ...config }),\r\n [config]\r\n )\r\n\r\n const obsUrl = useMemo(() => {\r\n if (!mergedConfig.obs?.clientId) return \"\"\r\n const base =\r\n mergedConfig.obs.bridgeUrl ?? \"wss://sparkle-bot-v2.fly.dev/obs-bridge\"\r\n return `${base}?role=client&clientId=${encodeURIComponent(mergedConfig.obs.clientId)}`\r\n }, [mergedConfig.obs?.clientId, mergedConfig.obs?.bridgeUrl])\r\n\r\n const inner = (\r\n <AuthProvider>\r\n <WebSocketProvider>\r\n <ClientGate>{children}</ClientGate>\r\n </WebSocketProvider>\r\n </AuthProvider>\r\n )\r\n\r\n const tree = obsUrl ? (\r\n <ObsProvider url={obsUrl}>{inner}</ObsProvider>\r\n ) : (\r\n inner\r\n )\r\n\r\n return (\r\n <SparkleConfigProvider value={mergedConfig}>{tree}</SparkleConfigProvider>\r\n )\r\n}\r\n","import * as React from \"react\"\r\nimport { createContext, useContext, type ReactNode } from \"react\"\r\nimport type { SparkleConfig } from \"../utils/config\"\r\n\r\nconst defaultConfig: SparkleConfig = {\r\n userId: \"\",\r\n wsUrl: \"wss://sparkle-bot-v2.fly.dev/ws\",\r\n viewerSessionToken: null,\r\n}\r\n\r\nexport const SparkleConfigContext = createContext<SparkleConfig>(defaultConfig)\r\n\r\nexport function SparkleConfigProvider({\r\n value,\r\n children,\r\n}: {\r\n value: SparkleConfig\r\n children: ReactNode\r\n}) {\r\n return (\r\n <SparkleConfigContext.Provider value={value}>\r\n {children}\r\n </SparkleConfigContext.Provider>\r\n )\r\n}\r\n\r\nexport function useSparkleConfig(): SparkleConfig {\r\n return useContext(SparkleConfigContext)\r\n}\r\n","import * as React from \"react\"\r\n\r\nimport { type ReactNode, createContext, useEffect, useMemo, useState } from \"react\"\r\n\r\nimport { useSparkleConfig } from \"../context/sparkle-config-context\"\r\n\r\n/** Message parent → iframe widget (page publique `/u/...`). */\r\nexport const SPARKLE_PUBLIC_VIEWER_MSG = {\r\n source: \"sparkle-public-parent\",\r\n type: \"SPARKLE_VIEWER_TOKEN\",\r\n} as const\r\n\r\ninterface AuthContextType {\r\n token: string | undefined | null\r\n viewerSessionToken: string | null\r\n}\r\n\r\nexport const SparkleAuthContext = createContext<AuthContextType>({\r\n token: null,\r\n viewerSessionToken: null,\r\n})\r\n\r\ninterface AuthProviderProps {\r\n children: ReactNode\r\n}\r\n\r\nexport const AuthProvider = ({ children }: AuthProviderProps) => {\r\n const config = useSparkleConfig()\r\n const [viewerSessionToken, setViewerSessionToken] = useState<string | null>(\r\n config.viewerSessionToken ?? null\r\n )\r\n\r\n useEffect(() => {\r\n setViewerSessionToken(config.viewerSessionToken ?? null)\r\n }, [config.viewerSessionToken])\r\n\r\n useEffect(() => {\r\n const onMessage = (event: MessageEvent) => {\r\n if (event.origin !== window.location.origin) return\r\n const data = event.data as\r\n | {\r\n source?: string\r\n type?: string\r\n token?: string | null\r\n }\r\n | undefined\r\n if (\r\n !data ||\r\n data.source !== SPARKLE_PUBLIC_VIEWER_MSG.source ||\r\n data.type !== SPARKLE_PUBLIC_VIEWER_MSG.type\r\n ) {\r\n return\r\n }\r\n setViewerSessionToken(\r\n typeof data.token === \"string\" && data.token.length > 0\r\n ? data.token\r\n : null\r\n )\r\n }\r\n window.addEventListener(\"message\", onMessage)\r\n return () => window.removeEventListener(\"message\", onMessage)\r\n }, [])\r\n\r\n const value = useMemo(\r\n () => ({\r\n token: config.userId,\r\n viewerSessionToken,\r\n }),\r\n [config.userId, viewerSessionToken]\r\n )\r\n\r\n return (\r\n <SparkleAuthContext.Provider value={value}>\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 { useSparkleConfig } from \"../context/sparkle-config-context\"\r\nimport { UpdateMessage, WebSocketClient } from \"../lib/websocket\"\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 children: ReactNode\r\n}\r\n\r\nexport const WebSocketProvider = ({ children }: WebSocketProviderProps) => {\r\n const config = useSparkleConfig()\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 }, [config.wsUrl])\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 { 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 { useContext } from \"react\"\r\nimport { SparkleAuthContext } from \"../providers/authProvider\"\r\n\r\nexport function useAuth() {\r\n const ctx = useContext(SparkleAuthContext)\r\n\r\n return {\r\n token: ctx.token,\r\n viewerSessionToken: ctx.viewerSessionToken,\r\n }\r\n}\r\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 { ApiClient } from \"@twurple/api\"\r\nimport { StaticAuthProvider } from \"@twurple/auth\"\r\nimport { useEffect, useState } from \"react\"\r\nimport useLocalStorage from \"../utils/hooks/useLocalStorage\"\r\n\r\nexport function useTwitch(asUser?: string | number) {\r\n const [apiClient, setApiClient] = useState<ApiClient | null>(null)\r\n const [twitch] = useLocalStorage<string>(\"twitch\")\r\n\r\n useEffect(() => {\r\n console.log(\"env\", process.env.TWITCH_CLIENT_ID)\r\n\r\n console.log(\"init\")\r\n\r\n const apiClient = new ApiClient({\r\n authProvider: new StaticAuthProvider(\r\n \"u9lt242tz2pn5hl5x444ls2xllnvip\",\r\n twitch!,\r\n [\r\n \"bits:read\",\r\n \"channel:manage:moderators\",\r\n \"channel:manage:polls\",\r\n \"channel:manage:predictions\",\r\n \"channel:manage:redemptions\",\r\n \"channel:manage:vips\",\r\n\r\n \"channel:read:polls\",\r\n \"channel:read:predictions\",\r\n \"channel:read:redemptions\",\r\n \"channel:read:subscriptions\",\r\n \"channel:read:vips\",\r\n \"moderation:read\",\r\n \"moderator:manage:announcements\",\r\n \"channel:moderate\",\r\n \"moderator:read:blocked_terms\",\r\n \"moderator:read:chatters\",\r\n \"moderator:read:followers\",\r\n \"user:read:email\",\r\n ]\r\n ),\r\n })\r\n\r\n setApiClient(apiClient)\r\n\r\n return () => {\r\n setApiClient(null)\r\n }\r\n }, [])\r\n\r\n return {\r\n apiClient,\r\n }\r\n}\r\n","import {\r\n Dispatch,\r\n SetStateAction,\r\n useCallback,\r\n useState,\r\n useRef,\r\n useLayoutEffect,\r\n} from \"react\"\r\nimport { isBrowser, noop } from \"../utils\"\r\n\r\ntype parserOptions<T> =\r\n | {\r\n raw: true\r\n }\r\n | {\r\n raw: false\r\n serializer: (value: T) => string\r\n deserializer: (value: string) => T\r\n }\r\n\r\nconst useLocalStorage = <T>(\r\n key: string,\r\n initialValue?: T,\r\n options?: parserOptions<T>\r\n): [T | undefined, Dispatch<SetStateAction<T | undefined>>, () => void] => {\r\n if (!isBrowser) {\r\n return [initialValue as T, noop, noop]\r\n }\r\n if (!key) {\r\n throw new Error(\"useLocalStorage key may not be falsy\")\r\n }\r\n\r\n const deserializer = options\r\n ? options.raw\r\n ? (value) => value\r\n : options.deserializer\r\n : JSON.parse\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const initializer = useRef((key: string) => {\r\n try {\r\n const serializer = options\r\n ? options.raw\r\n ? String\r\n : options.serializer\r\n : JSON.stringify\r\n\r\n const localStorageValue = localStorage.getItem(key)\r\n if (localStorageValue !== null) {\r\n return deserializer(localStorageValue)\r\n } else {\r\n initialValue && localStorage.setItem(key, serializer(initialValue))\r\n return initialValue\r\n }\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw. JSON.parse and JSON.stringify\r\n // can throw, too.\r\n return initialValue\r\n }\r\n })\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const [state, setState] = useState<T | undefined>(() =>\r\n initializer.current(key)\r\n )\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n useLayoutEffect(() => setState(initializer.current(key)), [key])\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const set: Dispatch<SetStateAction<T | undefined>> = useCallback(\r\n (valOrFunc) => {\r\n try {\r\n const newState =\r\n typeof valOrFunc === \"function\"\r\n ? (valOrFunc as Function)(state)\r\n : valOrFunc\r\n if (typeof newState === \"undefined\") return\r\n let value: string\r\n\r\n if (options)\r\n if (options.raw)\r\n if (typeof newState === \"string\") value = newState\r\n else value = JSON.stringify(newState)\r\n else if (options.serializer) value = options.serializer(newState)\r\n else value = JSON.stringify(newState)\r\n else value = JSON.stringify(newState)\r\n\r\n localStorage.setItem(key, value)\r\n setState(deserializer(value))\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw. Also JSON.stringify can throw.\r\n }\r\n },\r\n [key, setState]\r\n )\r\n\r\n // eslint-disable-next-line react-hooks/rules-of-hooks\r\n const remove = useCallback(() => {\r\n try {\r\n localStorage.removeItem(key)\r\n setState(undefined)\r\n } catch {\r\n // If user is in private mode or has storage restriction\r\n // localStorage can throw.\r\n }\r\n }, [key, setState])\r\n\r\n return [state, set, remove]\r\n}\r\n\r\nexport default useLocalStorage\r\n","export const noop = () => {}\r\n\r\nexport function on<T extends Window | Document | HTMLElement | EventTarget>(\r\n obj: T | null,\r\n ...args: Parameters<T[\"addEventListener\"]> | [string, Function | null, ...any]\r\n): void {\r\n if (obj && obj.addEventListener) {\r\n obj.addEventListener(\r\n ...(args as Parameters<HTMLElement[\"addEventListener\"]>)\r\n )\r\n }\r\n}\r\n\r\nexport function off<T extends Window | Document | HTMLElement | EventTarget>(\r\n obj: T | null,\r\n ...args:\r\n | Parameters<T[\"removeEventListener\"]>\r\n | [string, Function | null, ...any]\r\n): void {\r\n if (obj && obj.removeEventListener) {\r\n obj.removeEventListener(\r\n ...(args as Parameters<HTMLElement[\"removeEventListener\"]>)\r\n )\r\n }\r\n}\r\n\r\nexport const isBrowser = typeof window !== \"undefined\"\r\n\r\nexport const isNavigator = typeof navigator !== \"undefined\"\r\n","import { useCallback, useContext } from \"react\"\r\nimport type { OBSRequestTypes, OBSResponseTypes } from \"obs-websocket-js\"\r\nimport { ObsContext } from \"../providers/obsProvider\"\r\n\r\nexport type { OBSRequestTypes, OBSResponseTypes }\r\n\r\nexport function useObs() {\r\n const { status, call } = useContext(ObsContext)\r\n\r\n return {\r\n status,\r\n connected: status === \"connected\",\r\n call,\r\n }\r\n}\r\n\r\nexport function useObsCall<T extends keyof OBSRequestTypes>(requestType: T) {\r\n const { call } = useContext(ObsContext)\r\n\r\n return useCallback(\r\n (requestData?: OBSRequestTypes[T]) => call(requestType, requestData),\r\n [call, requestType]\r\n )\r\n}\r\n","import { useCallback, useContext, useMemo } from \"react\"\r\n\r\nimport { SparkleAuthContext } from \"../providers/authProvider\"\r\nimport { useSparkleConfig } from \"../context/sparkle-config-context\"\r\n\r\nexport type InvokeTaskResult = {\r\n ok: boolean\r\n status: number\r\n}\r\n\r\n/**\r\n * Appelle un handler TASK (`Handlers.value` = identifiant `createTask`) via le proxy Next.\r\n * - Si `config.publicInvoke.publicProfileTwitchSlug` est défini : `POST /api/public/u/[slug]/invoke-task`.\r\n * - Sinon : `POST` sur `publicInvoke.invokePath` (défaut `/api/public-widget/invoke`) avec `ownerUserId` dans le corps.\r\n * Les cookies de session Better Auth sont envoyés (`credentials: \"include\"`). Le jeton viewer\r\n * (`viewerSessionToken`, ex. postMessage parent) est transmis pour le scope utilisateur côté handler.\r\n */\r\nexport function useInvokeTask() {\r\n const { publicInvoke } = useSparkleConfig()\r\n const { viewerSessionToken } = useContext(SparkleAuthContext)\r\n\r\n const invoke = useCallback(\r\n async (task: string, payload?: unknown): Promise<InvokeTaskResult> => {\r\n if (\r\n !publicInvoke?.projectId ||\r\n !publicInvoke.widgetFileId ||\r\n !publicInvoke.pageOwnerUserId\r\n ) {\r\n throw new Error(\r\n \"useInvokeTask: config.publicInvoke (projectId, widgetFileId, pageOwnerUserId) est requis\"\r\n )\r\n }\r\n\r\n const slug = publicInvoke.publicProfileTwitchSlug?.trim()\r\n const url = slug\r\n ? `/api/public/u/${encodeURIComponent(slug)}/invoke-task`\r\n : (publicInvoke.invokePath ?? \"/api/public-widget/invoke\").trim()\r\n\r\n const body = slug\r\n ? JSON.stringify({\r\n projectId: publicInvoke.projectId,\r\n fileId: publicInvoke.widgetFileId,\r\n taskId: task,\r\n payload: payload ?? {},\r\n viewerToken: viewerSessionToken,\r\n })\r\n : JSON.stringify({\r\n ownerUserId: publicInvoke.pageOwnerUserId,\r\n projectId: publicInvoke.projectId,\r\n fileId: publicInvoke.widgetFileId,\r\n task,\r\n payload: payload ?? {},\r\n viewerToken: viewerSessionToken,\r\n })\r\n\r\n const res = await fetch(url, {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body,\r\n })\r\n\r\n return { ok: res.ok, status: res.status }\r\n },\r\n [publicInvoke, viewerSessionToken]\r\n )\r\n\r\n return useMemo(\r\n () => ({ invoke, ready: Boolean(publicInvoke) }),\r\n [invoke, publicInvoke]\r\n )\r\n}\r\n"],"mappings":"AAAA,OAAOA,GAAS,WAAAC,EAAyB,aAAAC,GAAW,YAAAC,OAAgB,QCApE,UAAYC,MAAW,QACvB,OAAS,iBAAAC,EAAe,cAAAC,MAAkC,QAG1D,IAAMC,EAA+B,CACnC,OAAQ,GACR,MAAO,kCACP,mBAAoB,IACtB,EAEaC,EAAuBH,EAA6BE,CAAa,EAEvE,SAASE,EAAsB,CACpC,MAAAC,EACA,SAAAC,CACF,EAGG,CACD,OACE,gBAACH,EAAqB,SAArB,CAA8B,MAAOE,GACnCC,CACH,CAEJ,CAEO,SAASC,GAAkC,CAChD,OAAON,EAAWE,CAAoB,CACxC,CC5BA,UAAYK,MAAW,QAEvB,OAAyB,iBAAAC,EAAe,aAAAC,EAAW,WAAAC,EAAS,YAAAC,MAAgB,QAKrE,IAAMC,EAA4B,CACvC,OAAQ,wBACR,KAAM,sBACR,EAOaC,EAAqBC,EAA+B,CAC/D,MAAO,KACP,mBAAoB,IACtB,CAAC,EAMYC,EAAe,CAAC,CAAE,SAAAC,CAAS,IAAyB,CAC/D,IAAMC,EAASC,EAAiB,EAC1B,CAACC,EAAoBC,CAAqB,EAAIC,EAClDJ,EAAO,oBAAsB,IAC/B,EAEAK,EAAU,IAAM,CACdF,EAAsBH,EAAO,oBAAsB,IAAI,CACzD,EAAG,CAACA,EAAO,kBAAkB,CAAC,EAE9BK,EAAU,IAAM,CACd,IAAMC,EAAaC,GAAwB,CACzC,GAAIA,EAAM,SAAW,OAAO,SAAS,OAAQ,OAC7C,IAAMC,EAAOD,EAAM,KAQjB,CAACC,GACDA,EAAK,SAAWb,EAA0B,QAC1Ca,EAAK,OAASb,EAA0B,MAI1CQ,EACE,OAAOK,EAAK,OAAU,UAAYA,EAAK,MAAM,OAAS,EAClDA,EAAK,MACL,IACN,CACF,EACA,cAAO,iBAAiB,UAAWF,CAAS,EACrC,IAAM,OAAO,oBAAoB,UAAWA,CAAS,CAC9D,EAAG,CAAC,CAAC,EAEL,IAAMG,EAAQC,EACZ,KAAO,CACL,MAAOV,EAAO,OACd,mBAAAE,CACF,GACA,CAACF,EAAO,OAAQE,CAAkB,CACpC,EAEA,OACE,gBAACN,EAAmB,SAAnB,CAA4B,MAAOa,GACjCV,CACH,CAEJ,EC5EA,OAAOY,GAAoB,eAAAC,EAAa,cAAAC,EAAY,UAAAC,MAAc,QAElE,OAAS,iBAAAC,GAAe,aAAAC,EAAW,YAAAC,MAAgB,QCFnD,OAAOC,MAAkB,SACzB,OAAOC,MAAO,MAEd,IAAMC,EAAkBD,EAAE,OAAO,CAC/B,KAAMA,EAAE,QAAQ,WAAW,EAC3B,SAAUA,EAAE,MAAMA,EAAE,OAAO,CAAC,CAC9B,CAAC,EAEKE,EAAaF,EAAE,OAAO,CAC1B,KAAMA,EAAE,QAAQ,MAAM,EACtB,SAAUA,EAAE,MAAMA,EAAE,OAAO,CAAC,CAC9B,CAAC,EAEKG,EAAoBH,EAAE,OAAO,CACjC,KAAMA,EAAE,QAAQ,aAAa,EAC7B,SAAUA,EAAE,OAAO,CACrB,CAAC,EAEKI,GAAeJ,EAAE,OAAO,CAC5B,MAAOA,EAAE,QAAQ,QAAQ,EACzB,KAAMA,EAAE,KAAK,CAAC,QAAS,MAAM,CAAC,EAC9B,KAAMA,EAAE,OAAO,CACb,IAAKA,EAAE,OAAO,EACd,MAAOA,EAAE,OAAO,CAClB,CAAC,CACH,CAAC,EAEKK,GAAgBL,EAAE,MAAM,CAACC,EAAiBE,EAAmBD,CAAU,CAAC,EAQjEI,EAAN,cAA8BP,CAAa,CAUhD,YAAYQ,EAAa,CACvB,MAAM,EAVR,KAAQ,OAA2B,KACnC,KAAQ,MAAmB,CAAC,EAC5B,KAAQ,cAA0B,CAAC,EACnC,KAAQ,MAAkB,CAAC,EAC3B,KAAQ,iBAAmB,GAG3B,KAAQ,IAAqB,KAI3B,KAAK,IAAMA,CACb,CAEQ,KAAKC,EAAe,CACtB,KAAK,QAAQ,aAAe,UAAU,KACxC,KAAK,QAAQ,KAAK,KAAK,UAAUA,CAAI,CAAC,EAEtC,KAAK,MAAM,KAAKA,CAAI,CAExB,CAEA,aAAaC,EAAa,CACpB,KAAK,mBACT,KAAK,iBAAmB,GACxB,KAAK,IAAMA,EACX,KAAK,QAAQ,EACf,CAEQ,SAAU,CAChB,IAAMC,EAAmB,KAAK,IAAM,QAAU,KAAK,IACnD,KAAK,OAAS,IAAI,UAAUA,CAAgB,EAE5C,KAAK,OAAO,OAAS,IAAM,CACzB,KAAK,MAAM,QAASF,GAAS,KAAK,KAAKA,CAAI,CAAC,EAC5C,KAAK,MAAQ,CAAC,CAChB,EAEA,KAAK,OAAO,UAAaG,GAAU,CACjC,IAAMH,EAAO,KAAK,MAAMG,EAAM,IAAI,EAClC,KAAK,KAAK,SAAUH,CAAI,CAC1B,EAEA,KAAK,OAAO,QAAU,IAAM,CAC1B,KAAK,UAAU,CACjB,CACF,CAEA,WAAY,CACV,WAAW,IAAM,CACf,KAAK,MAAQ,CACX,CACE,KAAM,YACN,SAAU,KAAK,aACjB,EACA,CACE,KAAM,OACN,SAAU,KAAK,KACjB,CACF,EAEA,QAAQ,IAAI,kBAAkB,EAE9B,KAAK,QAAQ,CACf,EAAG,GAAI,CACT,CAEA,UAAUI,EAAoB,CAC5B,IAAMC,EAAcD,EAAS,OAC1BE,GAAY,CAAC,KAAK,cAAc,SAASA,CAAO,CACnD,EAEKD,EAAY,SACjB,KAAK,cAAgB,CAAC,GAAG,KAAK,cAAe,GAAGA,CAAW,EAE3D,QAAQ,IAAI,YAAaD,CAAQ,EAEjC,KAAK,KAAK,CACR,KAAM,YACN,SAAAA,CACF,CAAC,EACH,CAEA,KAAKG,EAAc,CACjB,IAAMF,EAAc,CAACE,CAAI,EAAE,OACxBD,GAAY,CAAC,KAAK,MAAM,SAASA,CAAO,CAC3C,EAEKD,EAAY,SACjB,KAAK,MAAQ,CAAC,GAAG,KAAK,MAAO,GAAGA,CAAW,EAE3C,KAAK,KAAK,CACR,KAAM,OACN,SAAU,CAACE,CAAI,CACjB,CAAC,EACH,CAEA,YAAYH,EAAkB,CAC5B,KAAK,KAAK,CAAE,KAAM,cAAe,SAAAA,CAAS,CAAC,CAC7C,CAEA,YAAa,CACN,KAAK,SAEV,KAAK,OAAO,QAAU,IAAM,CAAC,EAC7B,KAAK,OAAO,MAAM,EACpB,CACF,EDjIO,IAAMI,EAAkBC,GAAoC,CACjE,UAAW,IAAM,CAAC,EAClB,OAAQ,IAAM,CAAC,EACf,OAAQ,CAAC,EACT,MAAO,CAAC,CACV,CAAC,EAUYC,EAAoB,CAAC,CAAE,SAAAC,CAAS,IAA8B,CACzE,IAAMC,EAASC,EAAiB,EAC1B,CAACC,EAAQC,CAAS,EAAIC,EAAgB,CAAC,CAAC,EACxC,CAACC,EAAOC,CAAQ,EAAIF,EAA8B,CAAC,CAAC,EAEpDG,EAASC,EAAwB,IAAI,EAErC,CAAE,MAAAC,CAAM,EAAIC,EAAWC,CAAkB,EAE/CC,EAAU,KACRL,EAAO,QAAU,IAAIM,EAAgBb,EAAO,KAAK,EAEjDO,EAAO,QAAQ,GAAG,SAAWO,GAA2B,CACtD,GAAM,CAAE,KAAAC,EAAM,KAAAC,CAAK,EAAIF,EAEvB,GAAIE,IAAS,OAAQ,CACnB,IAAMC,EAAU,KAAK,MAAMF,EAAK,KAAK,EAErCT,EAAUY,IAAU,CAClB,GAAGA,EACH,CAACD,EAAQ,IAAI,EAAGA,EAAQ,OAC1B,EAAE,EACF,QAAQ,IAAI,gBAAiBA,CAAO,CACtC,KAAO,CACL,IAAIE,EAAaJ,EAAK,MAEtB,QAAQ,IAAI,QAASI,CAAK,EAE1B,GAAI,CACF,IAAMC,EAAM,WAAWL,EAAK,KAAK,EACjC,GAAI,CAAC,MAAMK,CAAG,GAAK,OAAOA,CAAG,IAAM,OAAOL,EAAK,KAAK,EAAE,KAAK,EACzDI,EAAQC,MAER,IAAI,CACFD,EAAQ,KAAK,MAAMJ,EAAK,KAAK,CAC/B,MAAQ,CACNI,EAAQJ,EAAK,KACf,CAEJ,MAAQ,CACNI,EAAQJ,EAAK,KACf,CAEAZ,EAAWe,IAAU,CAAE,GAAGA,EAAM,CAACH,EAAK,GAAG,EAAGI,CAAM,EAAE,EACpD,QAAQ,IAAI,iBAAkBjB,CAAM,CACtC,CACF,CAAC,EAEM,IAAM,CACXK,EAAO,SAAS,WAAW,CAC7B,GACC,CAACP,EAAO,KAAK,CAAC,EAEjBY,EAAU,IAAM,CACV,CAACL,EAAO,SAAW,CAACE,GAExBF,EAAO,QAAQ,aAAaE,CAAK,CACnC,EAAG,CAACA,CAAK,CAAC,EAEV,IAAMY,EAAYC,EACfC,GAAmB,CAClBhB,EAAO,SAAS,UAAUgB,CAAI,CAChC,EACA,CAAChB,CAAM,CACT,EAEMiB,EAASF,EACZG,GAAiB,CAChBlB,EAAO,SAAS,KAAKkB,CAAI,CAC3B,EACA,CAAClB,CAAM,CACT,EAEA,OACEmB,EAAA,cAAC9B,EAAgB,SAAhB,CACC,MAAO,CACL,UAAAyB,EACA,OAAAG,EACA,OAAAtB,EACA,MAAAG,CACF,GAECN,CACH,CAEJ,EElHA,OAAO4B,IACL,iBAAAC,GACA,eAAAC,GACA,aAAAC,GACA,UAAAC,EACA,YAAAC,OAEK,QAcA,IAAMC,EAAaL,GAA8B,CACtD,OAAQ,eACR,KAAM,IAAM,QAAQ,OAAO,IAAI,MAAM,yBAAyB,CAAC,CACjE,CAAC,EAOM,SAASM,EAAY,CAAE,IAAAC,EAAK,SAAAC,CAAS,EAAqB,CAC/D,GAAM,CAACC,EAAQC,CAAS,EAAIN,GAAoB,cAAc,EACxDO,EAASR,EAA4B,IAAI,EACzCS,EAAeT,EAAO,EAAK,EAEjCD,GAAU,IAAM,CACd,GAAI,CAACK,EAAK,OAEVK,EAAa,QAAU,GACvB,IAAIC,EAmCJ,OAjCc,SAAY,CACxB,GAAM,CAAE,QAASC,CAAa,EAAI,KAAM,QAAO,kBAAkB,EAC3DC,EAAM,IAAID,EAChBH,EAAO,QAAUI,EAEjB,IAAMC,EAAY,SAAY,CAC5B,GAAI,CAAAJ,EAAa,QACjB,CAAAF,EAAU,YAAY,EACtB,GAAI,CACF,MAAMK,EAAI,QAAQR,CAAG,EAChBK,EAAa,SAASF,EAAU,WAAW,CAClD,MAAQ,CACDE,EAAa,UAChBF,EAAU,OAAO,EACjBG,EAAiB,WAAWG,EAAW,GAAI,EAE/C,EACF,EAEAD,EAAI,GAAG,mBAA2B,IAAM,CACjCH,EAAa,UAChBF,EAAU,cAAc,EACxBG,EAAiB,WAAWG,EAAW,GAAI,EAE/C,CAAC,EAEDD,EAAI,GAAG,kBAA0B,IAAM,CAChCH,EAAa,SAASF,EAAU,OAAO,CAC9C,CAAC,EAED,MAAMM,EAAU,CAClB,GAEM,EAEC,IAAM,CACXJ,EAAa,QAAU,GACvB,aAAaC,CAAe,EAC5BF,EAAO,SAAS,WAAW,EAC3BA,EAAO,QAAU,KACjBD,EAAU,cAAc,CAC1B,CACF,EAAG,CAACH,CAAG,CAAC,EAER,IAAMU,EAAOhB,GACX,MACEiB,EACAC,IACiC,CACjC,IAAMJ,EAAMJ,EAAO,QACnB,GAAI,CAACI,EAAK,MAAM,IAAI,MAAM,mBAAmB,EAC7C,OAAOA,EAAI,KAAKG,EAAaC,CAAW,CAC1C,EACA,CAAC,CACH,EAEA,OACEpB,GAAA,cAACM,EAAW,SAAX,CAAoB,MAAO,CAAE,OAAAI,EAAQ,KAAAQ,CAAK,GACxCT,CACH,CAEJ,CLhGA,IAAMY,GAAa,CAAC,CAAE,SAAAC,CAAS,IAA+B,CAC5D,GAAM,CAACC,EAAOC,CAAQ,EAAIC,GAAS,EAAK,EAMxC,OAJAC,GAAU,IAAM,CACdF,EAAS,EAAI,CACf,EAAG,CAAC,CAAC,EAEAD,EACED,EADY,IAErB,EAOMK,GAA+B,CACnC,OAAQ,GACR,MAAO,kCACP,mBAAoB,IACtB,EAEaC,GAAkB,CAAC,CAAE,SAAAN,EAAU,OAAAO,CAAO,IAA4B,CAC7E,IAAMC,EAAeC,EACnB,KAAO,CAAE,GAAGJ,GAAe,GAAGE,CAAO,GACrC,CAACA,CAAM,CACT,EAEMG,EAASD,EAAQ,IAChBD,EAAa,KAAK,SAGhB,GADLA,EAAa,IAAI,WAAa,yCAClB,yBAAyB,mBAAmBA,EAAa,IAAI,QAAQ,CAAC,GAH5C,GAIvC,CAACA,EAAa,KAAK,SAAUA,EAAa,KAAK,SAAS,CAAC,EAEtDG,EACJC,EAAA,cAACC,EAAA,KACCD,EAAA,cAACE,EAAA,KACCF,EAAA,cAACb,GAAA,KAAYC,CAAS,CACxB,CACF,EASF,OACEY,EAAA,cAACG,EAAA,CAAsB,MAAOP,GAPnBE,EACXE,EAAA,cAACI,EAAA,CAAY,IAAKN,GAASC,CAAM,EAEjCA,CAIkD,CAEtD,EM3DA,OAAS,cAAAM,GAAY,aAAAC,GAAW,WAAAC,OAAe,QAGxC,SAASC,GACdC,EACAC,EACK,CACL,IAAMC,EAAUC,GAAWC,CAAe,EAEpCC,EAAOC,GAAQ,IAAM,CAACN,CAAG,EAAG,CAACA,CAAG,CAAC,EAEvC,GAAI,CAACE,EACH,MAAM,IAAI,MAAM,mDAAmD,EAGrE,OAAAK,GAAU,IAAM,CACdL,EAAQ,UAAU,CAACF,CAAG,CAAC,CACzB,EAAG,CAACK,CAAI,CAAC,EAEFA,EAAK,IAAKG,GAAON,EAAQ,OAAOM,CAAC,GAAKP,CAAkB,CACjE,CCpBA,OAAS,cAAAQ,OAAkB,QAGpB,SAASC,IAAU,CACxB,IAAMC,EAAMC,GAAWC,CAAkB,EAEzC,MAAO,CACL,MAAOF,EAAI,MACX,mBAAoBA,EAAI,kBAC1B,CACF,CCVA,OAAS,cAAAG,GAAY,aAAAC,OAA0B,QAIxC,IAAMC,GAAkB,CAC7B,sBACA,qBACA,oBACA,yBACA,6BACA,8BACA,oBAEA,eAEA,gBACA,iBACA,gBAEA,gBACA,gBACA,gBAEA,0BACA,2BAEA,aACA,gBACA,WAEA,mBACA,sBACA,kBACA,iBAEA,gBACA,mBACA,gBACA,kBACA,6BACF,EAIO,SAASC,GACdC,EAC6B,CAC7B,IAAMC,EAAUC,GAAWC,CAAe,EAE1C,GAAI,CAACF,EACH,MAAM,IAAI,MAAM,+CAA+C,EAGjE,OAAAG,GAAU,IAAM,CACdH,EAAQ,OAAOD,CAAI,CACrB,EAAG,CAACA,CAAI,CAAC,EAEFC,EAAQ,MAAMD,CAAI,CAC3B,CC1DA,OAAS,aAAAK,OAAiB,eAC1B,OAAS,sBAAAC,OAA0B,gBACnC,OAAS,aAAAC,GAAW,YAAAC,OAAgB,QCFpC,OAGE,eAAAC,EACA,YAAAC,GACA,UAAAC,GACA,mBAAAC,OACK,QCPA,IAAMC,EAAO,IAAM,CAAC,EA0BpB,IAAMC,EAAY,OAAO,OAAW,IDN3C,IAAMC,GAAkB,CACtBC,EACAC,EACAC,IACyE,CACzE,GAAI,CAACC,EACH,MAAO,CAACF,EAAmBG,EAAMA,CAAI,EAEvC,GAAI,CAACJ,EACH,MAAM,IAAI,MAAM,sCAAsC,EAGxD,IAAMK,EAAeH,EACjBA,EAAQ,IACLI,GAAUA,EACXJ,EAAQ,aACV,KAAK,MAGHK,EAAcC,GAAQR,GAAgB,CAC1C,GAAI,CACF,IAAMS,EAAaP,EACfA,EAAQ,IACN,OACAA,EAAQ,WACV,KAAK,UAEHQ,EAAoB,aAAa,QAAQV,CAAG,EAClD,OAAIU,IAAsB,KACjBL,EAAaK,CAAiB,GAErCT,GAAgB,aAAa,QAAQD,EAAKS,EAAWR,CAAY,CAAC,EAC3DA,EAEX,MAAQ,CAIN,OAAOA,CACT,CACF,CAAC,EAGK,CAACU,EAAOC,CAAQ,EAAIC,GAAwB,IAChDN,EAAY,QAAQP,CAAG,CACzB,EAGAc,GAAgB,IAAMF,EAASL,EAAY,QAAQP,CAAG,CAAC,EAAG,CAACA,CAAG,CAAC,EAG/D,IAAMe,EAA+CC,EAClDC,GAAc,CACb,GAAI,CACF,IAAMC,EACJ,OAAOD,GAAc,WAChBA,EAAuBN,CAAK,EAC7BM,EACN,GAAI,OAAOC,EAAa,IAAa,OACrC,IAAIZ,EAEAJ,EACEA,EAAQ,IACN,OAAOgB,GAAa,SAAUZ,EAAQY,EACrCZ,EAAQ,KAAK,UAAUY,CAAQ,EAC7BhB,EAAQ,WAAYI,EAAQJ,EAAQ,WAAWgB,CAAQ,EAC3DZ,EAAQ,KAAK,UAAUY,CAAQ,EACjCZ,EAAQ,KAAK,UAAUY,CAAQ,EAEpC,aAAa,QAAQlB,EAAKM,CAAK,EAC/BM,EAASP,EAAaC,CAAK,CAAC,CAC9B,MAAQ,CAGR,CACF,EACA,CAACN,EAAKY,CAAQ,CAChB,EAGMO,EAASH,EAAY,IAAM,CAC/B,GAAI,CACF,aAAa,WAAWhB,CAAG,EAC3BY,EAAS,MAAS,CACpB,MAAQ,CAGR,CACF,EAAG,CAACZ,EAAKY,CAAQ,CAAC,EAElB,MAAO,CAACD,EAAOI,EAAKI,CAAM,CAC5B,EAEOC,EAAQrB,GD5GR,SAASsB,GAAUC,EAA0B,CAClD,GAAM,CAACC,EAAWC,CAAY,EAAIC,GAA2B,IAAI,EAC3D,CAACC,CAAM,EAAIC,EAAwB,QAAQ,EAEjD,OAAAC,GAAU,IAAM,CACd,QAAQ,IAAI,MAAO,QAAQ,IAAI,gBAAgB,EAE/C,QAAQ,IAAI,MAAM,EAElB,IAAML,EAAY,IAAIM,GAAU,CAC9B,aAAc,IAAIC,GAChB,iCACAJ,EACA,CACE,YACA,4BACA,uBACA,6BACA,6BACA,sBAEA,qBACA,2BACA,2BACA,6BACA,oBACA,kBACA,iCACA,mBACA,+BACA,0BACA,2BACA,iBACF,CACF,CACF,CAAC,EAED,OAAAF,EAAaD,CAAS,EAEf,IAAM,CACXC,EAAa,IAAI,CACnB,CACF,EAAG,CAAC,CAAC,EAEE,CACL,UAAAD,CACF,CACF,CGpDA,OAAS,eAAAQ,GAAa,cAAAC,MAAkB,QAMjC,SAASC,IAAS,CACvB,GAAM,CAAE,OAAAC,EAAQ,KAAAC,CAAK,EAAIC,EAAWC,CAAU,EAE9C,MAAO,CACL,OAAAH,EACA,UAAWA,IAAW,YACtB,KAAAC,CACF,CACF,CAEO,SAASG,GAA4CC,EAAgB,CAC1E,GAAM,CAAE,KAAAJ,CAAK,EAAIC,EAAWC,CAAU,EAEtC,OAAOG,GACJC,GAAqCN,EAAKI,EAAaE,CAAW,EACnE,CAACN,EAAMI,CAAW,CACpB,CACF,CCvBA,OAAS,eAAAG,GAAa,cAAAC,GAAY,WAAAC,OAAe,QAiB1C,SAASC,IAAgB,CAC9B,GAAM,CAAE,aAAAC,CAAa,EAAIC,EAAiB,EACpC,CAAE,mBAAAC,CAAmB,EAAIC,GAAWC,CAAkB,EAEtDC,EAASC,GACb,MAAOC,EAAcC,IAAiD,CACpE,GACE,CAACR,GAAc,WACf,CAACA,EAAa,cACd,CAACA,EAAa,gBAEd,MAAM,IAAI,MACR,0FACF,EAGF,IAAMS,EAAOT,EAAa,yBAAyB,KAAK,EAClDU,EAAMD,EACR,iBAAiB,mBAAmBA,CAAI,CAAC,gBACxCT,EAAa,YAAc,6BAA6B,KAAK,EAE5DW,EACF,KAAK,UADIF,EACM,CACb,UAAWT,EAAa,UACxB,OAAQA,EAAa,aACrB,OAAQO,EACR,QAASC,GAAW,CAAC,EACrB,YAAaN,CACf,EACe,CACb,YAAaF,EAAa,gBAC1B,UAAWA,EAAa,UACxB,OAAQA,EAAa,aACrB,KAAAO,EACA,QAASC,GAAW,CAAC,EACrB,YAAaN,CACf,CARC,EAUCU,EAAM,MAAM,MAAMF,EAAK,CAC3B,OAAQ,OACR,YAAa,UACb,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAAC,CACF,CAAC,EAED,MAAO,CAAE,GAAIC,EAAI,GAAI,OAAQA,EAAI,MAAO,CAC1C,EACA,CAACZ,EAAcE,CAAkB,CACnC,EAEA,OAAOW,GACL,KAAO,CAAE,OAAAR,EAAQ,MAAO,EAAQL,CAAc,GAC9C,CAACK,EAAQL,CAAY,CACvB,CACF","names":["React","useMemo","useEffect","useState","React","createContext","useContext","defaultConfig","SparkleConfigContext","SparkleConfigProvider","value","children","useSparkleConfig","React","createContext","useEffect","useMemo","useState","SPARKLE_PUBLIC_VIEWER_MSG","SparkleAuthContext","createContext","AuthProvider","children","config","useSparkleConfig","viewerSessionToken","setViewerSessionToken","useState","useEffect","onMessage","event","data","value","useMemo","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","useSparkleConfig","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","ClientGate","children","ready","setReady","useState","useEffect","defaultConfig","SparkleProvider","config","mergedConfig","useMemo","obsUrl","inner","React","AuthProvider","WebSocketProvider","SparkleConfigProvider","ObsProvider","useContext","useEffect","useMemo","useRealtime","key","defaultValue","context","useContext","RealtimeContext","keys","useMemo","useEffect","k","useContext","useAuth","ctx","useContext","SparkleAuthContext","useContext","useEffect","SparkleHookKeys","useHook","hook","context","useContext","RealtimeContext","useEffect","ApiClient","StaticAuthProvider","useEffect","useState","useCallback","useState","useRef","useLayoutEffect","noop","isBrowser","useLocalStorage","key","initialValue","options","isBrowser","noop","deserializer","value","initializer","useRef","serializer","localStorageValue","state","setState","useState","useLayoutEffect","set","useCallback","valOrFunc","newState","remove","useLocalStorage_default","useTwitch","asUser","apiClient","setApiClient","useState","twitch","useLocalStorage_default","useEffect","ApiClient","StaticAuthProvider","useCallback","useContext","useObs","status","call","useContext","ObsContext","useObsCall","requestType","useCallback","requestData","useCallback","useContext","useMemo","useInvokeTask","publicInvoke","useSparkleConfig","viewerSessionToken","useContext","SparkleAuthContext","invoke","useCallback","task","payload","slug","url","body","res","useMemo"]}
|