sparkle-react 0.0.36 → 0.0.37

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,9 +6,9 @@ export { OBSRequestTypes, OBSResponseTypes } from 'obs-websocket-js';
6
6
 
7
7
  declare const sparkleConfig: z.ZodObject<{
8
8
  userId: z.ZodString;
9
- apiKey: z.ZodOptional<z.ZodString>;
10
- projectId: z.ZodOptional<z.ZodString>;
11
9
  wsUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
10
+ /** Projet Sparkle — requis pour `useTask` (invoke / invokeUntil côté runtime). */
11
+ projectId: z.ZodOptional<z.ZodString>;
12
12
  obs: z.ZodOptional<z.ZodObject<{
13
13
  clientId: z.ZodString;
14
14
  bridgeUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
@@ -19,77 +19,22 @@ declare const sparkleConfig: z.ZodObject<{
19
19
  clientId: string;
20
20
  bridgeUrl?: string | undefined;
21
21
  }>>;
22
- /** Jeton session viewer (ex. widget embarqué) — transmis aux routes d’invocation publique. */
23
- viewerSessionToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
24
- /** Origine du site Next pour `useTask` (widget sur autre domaine). Sinon : origine du navigateur. */
25
- taskApiBaseUrl: z.ZodOptional<z.ZodString>;
26
- /** Chemin du proxy tâches (défaut `/api/sparkle-react/task`). */
27
- taskApiPath: z.ZodDefault<z.ZodOptional<z.ZodString>>;
28
- /**
29
- * Jeton court émis par `POST /api/sparkle-react/task/bearer` (session app).
30
- * Si défini, `useTask` envoie `Authorization: Bearer …` pour les iframes sans cookies tiers.
31
- */
32
- taskBearerToken: z.ZodOptional<z.ZodNullable<z.ZodString>>;
33
- /** Config pour `useInvokeTask` (widgets publics). */
34
- publicInvoke: z.ZodOptional<z.ZodObject<{
35
- projectId: z.ZodOptional<z.ZodString>;
36
- widgetFileId: z.ZodOptional<z.ZodString>;
37
- pageOwnerUserId: z.ZodOptional<z.ZodString>;
38
- publicProfileTwitchSlug: z.ZodOptional<z.ZodString>;
39
- invokePath: z.ZodOptional<z.ZodString>;
40
- }, "strip", z.ZodTypeAny, {
41
- projectId?: string | undefined;
42
- widgetFileId?: string | undefined;
43
- pageOwnerUserId?: string | undefined;
44
- publicProfileTwitchSlug?: string | undefined;
45
- invokePath?: string | undefined;
46
- }, {
47
- projectId?: string | undefined;
48
- widgetFileId?: string | undefined;
49
- pageOwnerUserId?: string | undefined;
50
- publicProfileTwitchSlug?: string | undefined;
51
- invokePath?: string | undefined;
52
- }>>;
53
22
  }, "strip", z.ZodTypeAny, {
54
23
  userId: string;
55
24
  wsUrl: string;
56
- taskApiPath: string;
57
25
  projectId?: string | undefined;
58
- apiKey?: string | undefined;
59
26
  obs?: {
60
27
  clientId: string;
61
28
  bridgeUrl: string;
62
29
  } | undefined;
63
- viewerSessionToken?: string | null | undefined;
64
- taskApiBaseUrl?: string | undefined;
65
- taskBearerToken?: string | null | undefined;
66
- publicInvoke?: {
67
- projectId?: string | undefined;
68
- widgetFileId?: string | undefined;
69
- pageOwnerUserId?: string | undefined;
70
- publicProfileTwitchSlug?: string | undefined;
71
- invokePath?: string | undefined;
72
- } | undefined;
73
30
  }, {
74
31
  userId: string;
75
- projectId?: string | undefined;
76
- apiKey?: string | undefined;
77
32
  wsUrl?: string | undefined;
33
+ projectId?: string | undefined;
78
34
  obs?: {
79
35
  clientId: string;
80
36
  bridgeUrl?: string | undefined;
81
37
  } | undefined;
82
- viewerSessionToken?: string | null | undefined;
83
- taskApiBaseUrl?: string | undefined;
84
- taskApiPath?: string | undefined;
85
- taskBearerToken?: string | null | undefined;
86
- publicInvoke?: {
87
- projectId?: string | undefined;
88
- widgetFileId?: string | undefined;
89
- pageOwnerUserId?: string | undefined;
90
- publicProfileTwitchSlug?: string | undefined;
91
- invokePath?: string | undefined;
92
- } | undefined;
93
38
  }>;
94
39
  type SparkleConfig = z.infer<typeof sparkleConfig>;
95
40
 
@@ -391,45 +336,13 @@ declare function useObs(): {
391
336
  };
392
337
  declare function useObsCall<T extends keyof OBSRequestTypes>(requestType: T): (requestData?: OBSRequestTypes[T]) => Promise<OBSResponseTypes[T]>;
393
338
 
394
- type SparkleTaskInvokeResult = {
395
- traceId: string;
396
- scheduled?: boolean;
397
- scheduledAt?: string;
398
- };
399
- type UseTaskOptions = {
400
- /**
401
- * Origine du site Next (ex. `https://app.sparkle.example`).
402
- * Si absent : `SparkleConfig.taskApiBaseUrl`, puis l’origine du navigateur (même onglet).
403
- */
404
- baseUrl?: string;
405
- /**
406
- * Chemin du route handler (commence par `/`). Défaut : `SparkleConfig.taskApiPath` ou `/api/sparkle-react/task`.
407
- */
408
- path?: string;
409
- /**
410
- * Projet cible. Défaut : `SparkleConfig.projectId` (requis pour résoudre la tâche côté serveur).
411
- */
412
- projectId?: string;
413
- };
414
- type UseTaskInvokeOptions = {
415
- /** Comme `ctx.invokeUntil` : nombre = secondes ; chaîne = durée (`24h`) ou date ISO. */
416
- delay?: string | number;
417
- };
418
- type UseTaskResult = {
419
- /** Fire-and-forget ; erreurs capturées dans `error` sans rejet. */
420
- mutate: (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => void;
421
- mutateAsync: (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => Promise<SparkleTaskInvokeResult>;
422
- isPending: boolean;
423
- /** Alias pratique pour les libs type react-query */
424
- isLoading: boolean;
425
- error: Error | null;
426
- data: SparkleTaskInvokeResult | undefined;
427
- reset: () => void;
428
- };
429
339
  /**
430
- * Enfile une tâche handler sur le runtime Sparkle (même chemin que `ctx.invoke` / `ctx.invokeUntil`),
431
- * via `POST /api/sparkle-react/task` (session cookie et/ou `taskBearerToken` + `projectId`).
340
+ * Exécute une tâche Sparkle dans la runtime (équivalent à `ctx.invoke` / `ctx.invokeUntil`).
341
+ * En preview Sandpack, les appels passent par `postMessage` vers l’éditeur (session).
432
342
  */
433
- declare function useTask(options?: UseTaskOptions): UseTaskResult;
343
+ declare function useTask(): {
344
+ invoke: (taskId: string, payload?: unknown) => Promise<boolean>;
345
+ invokeUntil: (taskId: string, delaySeconds: number, payload?: unknown) => Promise<boolean>;
346
+ };
434
347
 
435
- export { type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, type SparkleTaskInvokeResult, type UseTaskInvokeOptions, type UseTaskOptions, type UseTaskResult, useAuth, useHook, useObs, useObsCall, useRealtime, useTask, useTwitch };
348
+ export { type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, useAuth, useHook, useObs, useObsCall, useRealtime, useTask, useTwitch };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import w,{useMemo as Q,useEffect as Te,useState as we}from"react";import Ke,{createContext as se,useContext as ie}from"react";var ae={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null,taskBearerToken:null,taskApiPath:"/api/sparkle-react/task"},U=se(ae);function q(){return ie(U)}import*as z from"react";import{createContext as ce}from"react";var v=ce({token:null,viewerSessionToken:null}),H=({children:t,config:r})=>z.createElement(v.Provider,{value:{token:r.userId,viewerSessionToken:r.viewerSessionToken??null}},t);import fe,{useCallback as D,useContext as me,useRef as ke}from"react";import{createContext as ge,useEffect as F,useState as K}from"react";import ue from"events";import p from"zod";var le=p.object({type:p.literal("subscribe"),channels:p.array(p.string())}),pe=p.object({type:p.literal("hook"),channels:p.array(p.string())}),de=p.object({type:p.literal("unsubscribe"),channels:p.string()}),Ze=p.object({event:p.literal("update"),type:p.enum(["value","hook"]),data:p.object({key:p.string(),value:p.string()})}),et=p.union([le,de,pe]),O=class extends ue{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(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(e){let n=e.filter(o=>!this.subscriptions.includes(o));n.length&&(this.subscriptions=[...this.subscriptions,...n],console.log("subscribe",e),this.send({type:"subscribe",channels:e}))}hook(e){let n=[e].filter(o=>!this.hooks.includes(o));n.length&&(this.hooks=[...this.hooks,...n],this.send({type:"hook",channels:[e]}))}unsubscribe(e){this.send({type:"unsubscribe",channels:e})}disconnect(){this.socket&&(this.socket.onclose=()=>{},this.socket.close())}};var T=ge({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),G=({children:t,config:r})=>{let[e,n]=K({}),[o,c]=K({}),s=ke(null),{token:d}=me(v);F(()=>(s.current=new O(r.wsUrl),s.current.on("update",a=>{let{data:i,type:x}=a;if(x==="hook"){let k=JSON.parse(i.value);c(g=>({...g,[k.hook]:k.payload})),console.log("receive hooks",k)}else{let k=i.value;console.log("value",k);try{let g=parseFloat(i.value);if(!isNaN(g)&&String(g)===String(i.value).trim())k=g;else try{k=JSON.parse(i.value)}catch{k=i.value}}catch{k=i.value}n(g=>({...g,[i.key]:k})),console.log("receive values",e)}}),()=>{s.current?.disconnect()}),[]),F(()=>{!s.current||!d||s.current.authenticate(d)},[d]);let f=D(a=>{s.current?.subscribe(a)},[s]),u=D(a=>{s.current?.hook(a)},[s]);return fe.createElement(T.Provider,{value:{subscribe:f,listen:u,values:e,hooks:o}},t)};import he,{createContext as be,useCallback as ye,useEffect as Se,useRef as V,useState as ve}from"react";var E=be({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function Y({url:t,children:r}){let[e,n]=ve("disconnected"),o=V(null),c=V(!1);Se(()=>{if(!t)return;c.current=!1;let d;return(async()=>{let{default:u}=await import("obs-websocket-js"),a=new u;o.current=a;let i=async()=>{if(!c.current){n("connecting");try{await a.connect(t),c.current||n("connected")}catch{c.current||(n("error"),d=setTimeout(i,3e3))}}};a.on("ConnectionClosed",()=>{c.current||(n("disconnected"),d=setTimeout(i,3e3))}),a.on("ConnectionError",()=>{c.current||n("error")}),await i()})(),()=>{c.current=!0,clearTimeout(d),o.current?.disconnect(),o.current=null,n("disconnected")}},[t]);let s=ye(async(d,f)=>{let u=o.current;if(!u)throw new Error("OBS not connected");return u.call(d,f)},[]);return he.createElement(E.Provider,{value:{status:e,call:s}},r)}var xe=({children:t})=>{let[r,e]=we(!1);return Te(()=>{e(!0)},[]),r?t:null},Ce={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null,taskBearerToken:null,taskApiPath:"/api/sparkle-react/task"};function Pe(){return typeof window>"u"?void 0:window.__SPARKLE_PROJECT_ID__?.trim()||void 0}var gt=({children:t,config:r})=>{let e=Q(()=>{let s={...Ce,...r},d=s.projectId?.trim();if(d)return{...s,projectId:d};let f=Pe();return f?{...s,projectId:f}:s},[r]),n=Q(()=>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]),o=w.createElement(H,{config:e},w.createElement(G,{config:e},w.createElement(xe,null,t))),c=n?w.createElement(Y,{url:n},o):o;return w.createElement(U.Provider,{value:e},c)};import{useContext as Re,useEffect as Oe,useMemo as Ee}from"react";function vt(t,r){let e=Re(T),n=Ee(()=>[t],[t]);if(!e)throw new Error("You must use useRealtime inside a SparkleProvider");return Oe(()=>{e.subscribe([t])},[n]),n.map(o=>e.values[o]??r)}import{useContext as Ie}from"react";function Ct(){let{token:t}=Ie(v);return{token:t}}import{useContext as Ae,useEffect as Ne}from"react";var It=["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 At(t){let r=Ae(T);if(!r)throw new Error("You must use useHook inside a SparkleProvider");return Ne(()=>{r.listen(t)},[t]),r.hooks[t]}import{ApiClient as Le}from"@twurple/api";import{StaticAuthProvider as Me}from"@twurple/auth";import{useEffect as Je,useState as We}from"react";import{useCallback as Z,useState as Ue,useRef as _e,useLayoutEffect as je}from"react";var _=()=>{};var X=typeof window<"u";var Be=(t,r,e)=>{if(!X)return[r,_,_];if(!t)throw new Error("useLocalStorage key may not be falsy");let n=e?e.raw?u=>u:e.deserializer:JSON.parse,o=_e(u=>{try{let a=e?e.raw?String:e.serializer:JSON.stringify,i=localStorage.getItem(u);return i!==null?n(i):(r&&localStorage.setItem(u,a(r)),r)}catch{return r}}),[c,s]=Ue(()=>o.current(t));je(()=>s(o.current(t)),[t]);let d=Z(u=>{try{let a=typeof u=="function"?u(c):u;if(typeof a>"u")return;let i;e?e.raw?typeof a=="string"?i=a:i=JSON.stringify(a):e.serializer?i=e.serializer(a):i=JSON.stringify(a):i=JSON.stringify(a),localStorage.setItem(t,i),s(n(i))}catch{}},[t,s]),f=Z(()=>{try{localStorage.removeItem(t),s(void 0)}catch{}},[t,s]);return[c,d,f]},ee=Be;function zt(t){let[r,e]=We(null),[n]=ee("twitch");return Je(()=>{console.log("env",process.env.TWITCH_CLIENT_ID),console.log("init");let o=new Le({authProvider:new Me("u9lt242tz2pn5hl5x444ls2xllnvip",n,["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(o),()=>{e(null)}},[]),{apiClient:r}}import{useCallback as $e,useContext as te}from"react";function Kt(){let{status:t,call:r}=te(E);return{status:t,connected:t==="connected",call:r}}function Gt(t){let{call:r}=te(E);return $e(e=>r(t,e),[r,t])}import{useCallback as I,useMemo as qe,useState as j}from"react";function ne(t){return/failed to fetch|networkerror|load failed|network request failed/i.test(t)}function ze(t){if(t&&typeof t=="object"&&"name"in t&&typeof t.name=="string")return t.name}function re(t,r){return ze(r)==="AbortError"?`${t} \u2014 requ\xEAte annul\xE9e (AbortSignal)`:ne(t)?`${t} \u2014 probable CORS (origine iframe non autoris\xE9e sur /api/sparkle-react/task), TLS, requ\xEAte annul\xE9e, ou corps non lisible cross-origin`:t}function He(t,r,e){let n=t.trim();if(!n)throw new Error(`task_empty_response_body http_${r}`);try{return JSON.parse(n)}catch{let o=e?.split(";")[0]?.trim()??"",c=o&&o!=="application/json"?` content-type=${o}`:"";throw new Error(`task_response_not_json http_${r}${c}`)}}function De(t,r){let e=r.startsWith("/")?r:`/${r}`;if(typeof window>"u")return e;let n=(t?.replace(/\/$/,"")??"").trim()||(window.location?.origin&&!window.location.origin.startsWith("null")?window.location.origin:"");return n?new URL(e,`${n}/`).href:e}function Xt(t){let{taskApiBaseUrl:r,taskApiPath:e,projectId:n,taskBearerToken:o}=q(),[c,s]=j(void 0),[d,f]=j(null),[u,a]=j(!1),i=t?.baseUrl??r,x=t?.path??e??"/api/sparkle-react/task",k=t?.projectId??n,g=I(()=>De(i,x),[i,x]),B=I(()=>{s(void 0),f(null),a(!1)},[]),C=I(async(A,N,P)=>{let M=k?.trim();if(!M){let m=new Error("missing_project_id");throw f(m),m}a(!0),f(null);try{let m={task:A,projectId:M,payload:N??{}};P?.delay!==void 0&&(m.delay=P.delay);let y=o?.trim(),J={"Content-Type":"application/json"};y&&(J.Authorization=`Bearer ${y}`,m.taskBearerToken=y);let h;try{h=await fetch(g(),{method:"POST",headers:J,body:JSON.stringify(m)})}catch(b){let S=b instanceof Error?b.message:String(b),R=new Error(re(S,b));throw f(R),R}let oe=await h.text(),l=He(oe,h.status,h.headers.get("content-type"));if(!h.ok){let b="error"in l&&typeof l.error=="string"?l.error:`http_${h.status}`,S="detail"in l&&typeof l.detail=="string"&&l.detail?` (${l.detail})`:"",R=`${b} [http ${h.status}]${S}`,$=new Error(R);throw f($),$}if(!l||typeof l!="object"||!("ok"in l)||l.ok!==!0){let b=l&&typeof l=="object"&&"error"in l&&typeof l.error=="string"?`${l.error} [http ${h.status}]`:`task_failed [http ${h.status}]`,S=new Error(b);throw f(S),S}let W={traceId:l.traceId,...l.scheduled?{scheduled:!0,scheduledAt:l.scheduledAt}:{}};return s(W),W}catch(m){let y=m instanceof Error?m.name==="TypeError"&&ne(m.message)?new Error(re(m.message,m)):m:new Error(String(m));throw f(y),y}finally{a(!1)}},[g,k,o]),L=I((A,N,P)=>{C(A,N,P).catch(()=>{})},[C]);return qe(()=>({mutate:L,mutateAsync:C,isPending:u,isLoading:u,error:d,data:c,reset:B}),[L,C,u,d,c,B])}export{It as SparkleHookKeys,gt as SparkleProvider,Ct as useAuth,At as useHook,Kt as useObs,Gt as useObsCall,vt as useRealtime,Xt as useTask,zt as useTwitch};
1
+ import k,{useMemo as re,useEffect as oe,useState as ne}from"react";import*as w from"react";import{createContext as W}from"react";var g=W({token:null}),T=({children:t,config:r})=>w.createElement(g.Provider,{value:{token:r.userId}},t);import $,{useCallback as x,useContext as G,useRef as K}from"react";import{createContext as Y,useEffect as C,useState as P}from"react";import D from"events";import l from"zod";var H=l.object({type:l.literal("subscribe"),channels:l.array(l.string())}),J=l.object({type:l.literal("hook"),channels:l.array(l.string())}),_=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()})}),Ie=l.union([H,_,J]),b=class extends D{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 h=Y({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),E=({children:t,config:r})=>{let[e,o]=P({}),[n,c]=P({}),s=K(null),{token:p}=G(g);C(()=>(s.current=new b(r.wsUrl),s.current.on("update",u=>{let{data:a,type:j}=u;if(j==="hook"){let d=JSON.parse(a.value);c(m=>({...m,[d.hook]:d.payload})),console.log("receive hooks",d)}else{let d=a.value;console.log("value",d);try{let m=parseFloat(a.value);if(!isNaN(m)&&String(m)===String(a.value).trim())d=m;else try{d=JSON.parse(a.value)}catch{d=a.value}}catch{d=a.value}o(m=>({...m,[a.key]:d})),console.log("receive values",e)}}),()=>{s.current?.disconnect()}),[]),C(()=>{!s.current||!p||s.current.authenticate(p)},[p]);let f=x(u=>{s.current?.subscribe(u)},[s]),i=x(u=>{s.current?.hook(u)},[s]);return $.createElement(h.Provider,{value:{subscribe:f,listen:i,values:e,hooks:n}},t)};import F,{createContext as V,useCallback as Q,useEffect as X,useRef as O,useState as Z}from"react";var v=V({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function R({url:t,children:r}){let[e,o]=Z("disconnected"),n=O(null),c=O(!1);X(()=>{if(!t)return;c.current=!1;let p;return(async()=>{let{default:i}=await import("obs-websocket-js"),u=new i;n.current=u;let a=async()=>{if(!c.current){o("connecting");try{await u.connect(t),c.current||o("connected")}catch{c.current||(o("error"),p=setTimeout(a,3e3))}}};u.on("ConnectionClosed",()=>{c.current||(o("disconnected"),p=setTimeout(a,3e3))}),u.on("ConnectionError",()=>{c.current||o("error")}),await a()})(),()=>{c.current=!0,clearTimeout(p),n.current?.disconnect(),n.current=null,o("disconnected")}},[t]);let s=Q(async(p,f)=>{let i=n.current;if(!i)throw new Error("OBS not connected");return i.call(p,f)},[]);return F.createElement(v.Provider,{value:{status:e,call:s}},r)}import{createContext as ee,useContext as te}from"react";var y=ee(null);function B(){let t=te(y);if(!t)throw new Error("Sparkle hooks require SparkleProvider");return t}var se=({children:t})=>{let[r,e]=ne(!1);return oe(()=>{e(!0)},[]),r?t:null},ie={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws"},Fe=({children:t,config:r})=>{let e={...ie,...r},o=re(()=>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(y.Provider,{value:e},k.createElement(T,{config:e},k.createElement(E,{config:e},k.createElement(se,null,t))));return o?k.createElement(R,{url:o},n):n};import{useContext as ae,useEffect as ce,useMemo as ue}from"react";function et(t,r){let e=ae(h),o=ue(()=>[t],[t]);if(!e)throw new Error("You must use useRealtime inside a SparkleProvider");return ce(()=>{e.subscribe([t])},[o]),o.map(n=>e.values[n]??r)}import{useContext as le}from"react";function nt(){let{token:t}=le(g);return{token:t}}import{useContext as pe,useEffect as de}from"react";var ut=["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 lt(t){let r=pe(h);if(!r)throw new Error("You must use useHook inside a SparkleProvider");return de(()=>{r.listen(t)},[t]),r.hooks[t]}import{ApiClient as ke}from"@twurple/api";import{StaticAuthProvider as be}from"@twurple/auth";import{useEffect as ve,useState as ye}from"react";import{useCallback as M,useState as fe,useRef as me,useLayoutEffect as ge}from"react";var S=()=>{};var I=typeof window<"u";var he=(t,r,e)=>{if(!I)return[r,S,S];if(!t)throw new Error("useLocalStorage key may not be falsy");let o=e?e.raw?i=>i:e.deserializer:JSON.parse,n=me(i=>{try{let u=e?e.raw?String:e.serializer:JSON.stringify,a=localStorage.getItem(i);return a!==null?o(a):(r&&localStorage.setItem(i,u(r)),r)}catch{return r}}),[c,s]=fe(()=>n.current(t));ge(()=>s(n.current(t)),[t]);let p=M(i=>{try{let u=typeof i=="function"?i(c):i;if(typeof u>"u")return;let a;e?e.raw?typeof u=="string"?a=u:a=JSON.stringify(u):e.serializer?a=e.serializer(u):a=JSON.stringify(u):a=JSON.stringify(u),localStorage.setItem(t,a),s(o(a))}catch{}},[t,s]),f=M(()=>{try{localStorage.removeItem(t),s(void 0)}catch{}},[t,s]);return[c,p,f]},N=he;function wt(t){let[r,e]=ye(null),[o]=N("twitch");return ve(()=>{console.log("env",process.env.TWITCH_CLIENT_ID),console.log("init");let n=new ke({authProvider:new be("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 Se,useContext as A}from"react";function Pt(){let{status:t,call:r}=A(v);return{status:t,connected:t==="connected",call:r}}function Et(t){let{call:r}=A(v);return Se(e=>r(t,e),[r,t])}import{useCallback as L,useMemo as we}from"react";var Te="sparkle-invoke-task",xe="sparkle-invoke-task-result";function Ce(){return typeof crypto<"u"&&"randomUUID"in crypto?crypto.randomUUID():`${Date.now()}-${Math.random().toString(16).slice(2)}`}function U(t,r){if(typeof window>"u")return Promise.reject(new Error("useTask requires a browser environment"));let e=window.top??window.parent;return!e||e===window?Promise.reject(new Error("useTask: aucun parent \u2014 utilisez la preview \xE9diteur ou une page int\xE9gr\xE9e.")):new Promise((o,n)=>{let c=Ce(),s=f=>{let i=f.data;!i||i.type!==xe||i.requestId!==c||(window.removeEventListener("message",s),window.clearTimeout(p),i.ok?o(!!i.result):n(new Error(typeof i.error=="string"?i.error:"invoke_failed")))},p=window.setTimeout(()=>{window.removeEventListener("message",s),n(new Error("useTask: d\xE9lai d\xE9pass\xE9"))},45e3);window.addEventListener("message",s),e.postMessage({type:Te,requestId:c,projectId:t,...r},"*")})}function q(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}async function z(t,r){let e=await fetch(`/api/projects/${t}/invoke-task`,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!e.ok){let o=await e.json().catch(()=>null);throw new Error(o?.error??e.statusText)}return!0}function It(){let r=B().projectId,e=L(async(n,c)=>{if(!r?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let s={kind:"invoke",taskId:n,payload:c};return q()?U(r,s):z(r,s)},[r]),o=L(async(n,c,s)=>{if(!r?.trim())throw new Error("useTask: renseignez `config.projectId` sur SparkleProvider.");let p={kind:"invokeUntil",taskId:n,payload:s,delaySeconds:c};return q()?U(r,p):z(r,p)},[r]);return we(()=>({invoke:e,invokeUntil:o}),[e,o])}export{ut as SparkleHookKeys,Fe as SparkleProvider,nt as useAuth,lt as useHook,Pt as useObs,Et as useObsCall,et as useRealtime,It as useTask,wt as useTwitch};
2
2
  //# sourceMappingURL=index.mjs.map
@@ -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/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 { SparkleConfigContext } 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 taskBearerToken: null,\r\n taskApiPath: \"/api/sparkle-react/task\",\r\n}\r\n\r\nfunction resolveProjectIdFromWindow(): string | undefined {\r\n if (typeof window === \"undefined\") return undefined\r\n const fromGlobal = (\r\n window as unknown as { __SPARKLE_PROJECT_ID__?: string }\r\n ).__SPARKLE_PROJECT_ID__?.trim()\r\n return fromGlobal || undefined\r\n}\r\n\r\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\r\n const mergedConfig = useMemo(() => {\r\n const base = { ...defaultConfig, ...config }\r\n const explicit = base.projectId?.trim()\r\n if (explicit) return { ...base, projectId: explicit }\r\n const inferred = resolveProjectIdFromWindow()\r\n if (inferred) return { ...base, projectId: inferred }\r\n return base\r\n }, [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 <AuthProvider config={mergedConfig}>\r\n <WebSocketProvider config={mergedConfig}>\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 <SparkleConfigContext.Provider value={mergedConfig}>\r\n {tree}\r\n </SparkleConfigContext.Provider>\r\n )\r\n}\r\n","import React, { 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 taskBearerToken: null,\r\n taskApiPath: \"/api/sparkle-react/task\",\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 } from \"react\"\r\n\r\nimport { SparkleConfig } from \"../utils/config\"\r\n\r\ninterface AuthContextType {\r\n token: string | undefined | null\r\n viewerSessionToken: string | null | undefined\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 return (\r\n <SparkleAuthContext.Provider\r\n value={{\r\n token: config.userId,\r\n viewerSessionToken: config.viewerSessionToken ?? null,\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 { 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 } = useContext(SparkleAuthContext)\r\n\r\n return {\r\n token: accessToken,\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, useMemo, useState } from \"react\"\r\n\r\nimport { useSparkleConfig } from \"../context/sparkle-config-context\"\r\n\r\nexport type SparkleTaskInvokeResult = {\r\n traceId: string\r\n scheduled?: boolean\r\n scheduledAt?: string\r\n}\r\n\r\nexport type UseTaskOptions = {\r\n /**\r\n * Origine du site Next (ex. `https://app.sparkle.example`).\r\n * Si absent : `SparkleConfig.taskApiBaseUrl`, puis l’origine du navigateur (même onglet).\r\n */\r\n baseUrl?: string\r\n /**\r\n * Chemin du route handler (commence par `/`). Défaut : `SparkleConfig.taskApiPath` ou `/api/sparkle-react/task`.\r\n */\r\n path?: string\r\n /**\r\n * Projet cible. Défaut : `SparkleConfig.projectId` (requis pour résoudre la tâche côté serveur).\r\n */\r\n projectId?: string\r\n}\r\n\r\nexport type UseTaskInvokeOptions = {\r\n /** Comme `ctx.invokeUntil` : nombre = secondes ; chaîne = durée (`24h`) ou date ISO. */\r\n delay?: string | number\r\n}\r\n\r\nexport type UseTaskResult = {\r\n /** Fire-and-forget ; erreurs capturées dans `error` sans rejet. */\r\n mutate: (\r\n task: string,\r\n payload?: unknown,\r\n invokeOptions?: UseTaskInvokeOptions\r\n ) => void\r\n mutateAsync: (\r\n task: string,\r\n payload?: unknown,\r\n invokeOptions?: UseTaskInvokeOptions\r\n ) => Promise<SparkleTaskInvokeResult>\r\n isPending: boolean\r\n /** Alias pratique pour les libs type react-query */\r\n isLoading: boolean\r\n error: Error | null\r\n data: SparkleTaskInvokeResult | undefined\r\n reset: () => void\r\n}\r\n\r\ntype TaskSuccessJson = {\r\n ok: true\r\n traceId: string\r\n scheduled?: boolean\r\n scheduledAt?: string\r\n}\r\ntype TaskErrorJson = { ok: false; error?: string; detail?: string }\r\n\r\nfunction isLikelyNetworkOrCorsFetchFailure(message: string): boolean {\r\n return /failed to fetch|networkerror|load failed|network request failed/i.test(\r\n message\r\n )\r\n}\r\n\r\nfunction errorName(e: unknown): string | undefined {\r\n if (\r\n e &&\r\n typeof e === \"object\" &&\r\n \"name\" in e &&\r\n typeof (e as { name: unknown }).name === \"string\"\r\n ) {\r\n return (e as { name: string }).name\r\n }\r\n return undefined\r\n}\r\n\r\nfunction enrichFetchFailureMessage(message: string, cause?: unknown): string {\r\n if (errorName(cause) === \"AbortError\") {\r\n return `${message} — requête annulée (AbortSignal)`\r\n }\r\n if (!isLikelyNetworkOrCorsFetchFailure(message)) return message\r\n return `${message} — probable CORS (origine iframe non autorisée sur /api/sparkle-react/task), TLS, requête annulée, ou corps non lisible cross-origin`\r\n}\r\n\r\nfunction parseTaskResponseJson(\r\n text: string,\r\n status: number,\r\n contentType: string | null\r\n): TaskSuccessJson | TaskErrorJson {\r\n const trimmed = text.trim()\r\n if (!trimmed) {\r\n throw new Error(`task_empty_response_body http_${status}`)\r\n }\r\n try {\r\n return JSON.parse(trimmed) as TaskSuccessJson | TaskErrorJson\r\n } catch {\r\n const ct = contentType?.split(\";\")[0]?.trim() ?? \"\"\r\n const hint = ct && ct !== \"application/json\" ? ` content-type=${ct}` : \"\"\r\n throw new Error(`task_response_not_json http_${status}${hint}`)\r\n }\r\n}\r\n\r\nfunction buildTaskUrl(baseUrl: string | undefined, path: string): string {\r\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`\r\n if (typeof window === \"undefined\") {\r\n return normalizedPath\r\n }\r\n const origin =\r\n (baseUrl?.replace(/\\/$/, \"\") ?? \"\").trim() ||\r\n (window.location?.origin && !window.location.origin.startsWith(\"null\")\r\n ? window.location.origin\r\n : \"\")\r\n if (!origin) {\r\n return normalizedPath\r\n }\r\n return new URL(normalizedPath, `${origin}/`).href\r\n}\r\n\r\n/**\r\n * Enfile une tâche handler sur le runtime Sparkle (même chemin que `ctx.invoke` / `ctx.invokeUntil`),\r\n * via `POST /api/sparkle-react/task` (session cookie et/ou `taskBearerToken` + `projectId`).\r\n */\r\nexport function useTask(options?: UseTaskOptions): UseTaskResult {\r\n const {\r\n taskApiBaseUrl,\r\n taskApiPath,\r\n projectId: configProjectId,\r\n taskBearerToken,\r\n } = useSparkleConfig()\r\n const [data, setData] = useState<SparkleTaskInvokeResult | undefined>(\r\n undefined\r\n )\r\n const [error, setError] = useState<Error | null>(null)\r\n const [isPending, setIsPending] = useState(false)\r\n\r\n const baseUrl = options?.baseUrl ?? taskApiBaseUrl\r\n const path = options?.path ?? taskApiPath ?? \"/api/sparkle-react/task\"\r\n const projectId = options?.projectId ?? configProjectId\r\n\r\n const resolveUrl = useCallback(\r\n () => buildTaskUrl(baseUrl, path),\r\n [baseUrl, path]\r\n )\r\n\r\n const reset = useCallback(() => {\r\n setData(undefined)\r\n setError(null)\r\n setIsPending(false)\r\n }, [])\r\n\r\n const mutateAsync = useCallback(\r\n async (\r\n task: string,\r\n payload?: unknown,\r\n invokeOptions?: UseTaskInvokeOptions\r\n ): Promise<SparkleTaskInvokeResult> => {\r\n const pid = projectId?.trim()\r\n if (!pid) {\r\n const err = new Error(\"missing_project_id\")\r\n setError(err)\r\n throw err\r\n }\r\n\r\n setIsPending(true)\r\n setError(null)\r\n try {\r\n const body: Record<string, unknown> = {\r\n task,\r\n projectId: pid,\r\n payload: payload ?? {},\r\n }\r\n if (invokeOptions?.delay !== undefined) {\r\n body.delay = invokeOptions.delay\r\n }\r\n\r\n const bearer = taskBearerToken?.trim()\r\n const headers: Record<string, string> = {\r\n \"Content-Type\": \"application/json\",\r\n }\r\n if (bearer) {\r\n headers.Authorization = `Bearer ${bearer}`\r\n /** Secours si un proxy / navigateur supprime `Authorization` en cross-origin. */\r\n body.taskBearerToken = bearer\r\n }\r\n\r\n let res: Response\r\n try {\r\n res = await fetch(resolveUrl(), {\r\n method: \"POST\",\r\n headers,\r\n body: JSON.stringify(body),\r\n })\r\n } catch (e) {\r\n const raw = e instanceof Error ? e.message : String(e)\r\n const err = new Error(enrichFetchFailureMessage(raw, e))\r\n setError(err)\r\n throw err\r\n }\r\n\r\n const responseText = await res.text()\r\n const json = parseTaskResponseJson(\r\n responseText,\r\n res.status,\r\n res.headers.get(\"content-type\")\r\n )\r\n\r\n if (!res.ok) {\r\n const base =\r\n \"error\" in json && typeof json.error === \"string\"\r\n ? json.error\r\n : `http_${res.status}`\r\n const detail =\r\n \"detail\" in json && typeof json.detail === \"string\" && json.detail\r\n ? ` (${json.detail})`\r\n : \"\"\r\n const msg = `${base} [http ${res.status}]${detail}`\r\n const err = new Error(msg)\r\n setError(err)\r\n throw err\r\n }\r\n\r\n if (\r\n !json ||\r\n typeof json !== \"object\" ||\r\n !(\"ok\" in json) ||\r\n json.ok !== true\r\n ) {\r\n const msg =\r\n json &&\r\n typeof json === \"object\" &&\r\n \"error\" in json &&\r\n typeof json.error === \"string\"\r\n ? `${json.error} [http ${res.status}]`\r\n : `task_failed [http ${res.status}]`\r\n const err = new Error(msg)\r\n setError(err)\r\n throw err\r\n }\r\n\r\n const result: SparkleTaskInvokeResult = {\r\n traceId: json.traceId,\r\n ...(json.scheduled\r\n ? { scheduled: true, scheduledAt: json.scheduledAt }\r\n : {}),\r\n }\r\n setData(result)\r\n return result\r\n } catch (e) {\r\n const err =\r\n e instanceof Error\r\n ? e.name === \"TypeError\" &&\r\n isLikelyNetworkOrCorsFetchFailure(e.message)\r\n ? new Error(enrichFetchFailureMessage(e.message, e as unknown))\r\n : e\r\n : new Error(String(e))\r\n setError(err)\r\n throw err\r\n } finally {\r\n setIsPending(false)\r\n }\r\n },\r\n [resolveUrl, projectId, taskBearerToken]\r\n )\r\n\r\n const mutate = useCallback(\r\n (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => {\r\n void mutateAsync(task, payload, invokeOptions).catch(() => {\r\n /* état déjà dans error */\r\n })\r\n },\r\n [mutateAsync]\r\n )\r\n\r\n return useMemo(\r\n () => ({\r\n mutate,\r\n mutateAsync,\r\n isPending,\r\n isLoading: isPending,\r\n error,\r\n data,\r\n reset,\r\n }),\r\n [mutate, mutateAsync, isPending, error, data, reset]\r\n )\r\n}\r\n"],"mappings":"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","names":["React","useMemo","useEffect","useState","React","createContext","useContext","defaultConfig","SparkleConfigContext","useSparkleConfig","useContext","SparkleConfigContext","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","ClientGate","children","ready","setReady","useState","useEffect","defaultConfig","resolveProjectIdFromWindow","SparkleProvider","config","mergedConfig","useMemo","base","explicit","inferred","obsUrl","inner","React","AuthProvider","WebSocketProvider","tree","ObsProvider","SparkleConfigContext","useContext","useEffect","useMemo","useRealtime","key","defaultValue","context","useContext","RealtimeContext","keys","useMemo","useEffect","k","useContext","useAuth","accessToken","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","useMemo","useState","isLikelyNetworkOrCorsFetchFailure","message","errorName","e","enrichFetchFailureMessage","cause","parseTaskResponseJson","text","status","contentType","trimmed","ct","hint","buildTaskUrl","baseUrl","path","normalizedPath","origin","useTask","options","taskApiBaseUrl","taskApiPath","configProjectId","taskBearerToken","useSparkleConfig","data","setData","useState","error","setError","isPending","setIsPending","projectId","resolveUrl","useCallback","reset","mutateAsync","task","payload","invokeOptions","pid","err","body","bearer","headers","res","raw","responseText","json","base","detail","msg","result","mutate","useMemo"]}
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/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 { AuthProvider } from \"./providers/authProvider\"\r\nimport { WebSocketProvider } from \"./providers/realtimeProvider\"\r\nimport { ObsProvider } from \"./providers/obsProvider\"\r\nimport { SparkleConfigContext } from \"./sparkle-config-context\"\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}\r\n\r\nexport const SparkleProvider = ({ children, config }: SparkleProviderProps) => {\r\n const mergedConfig = { ...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 <SparkleConfigContext.Provider 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 </SparkleConfigContext.Provider>\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 * 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 { useContext } from \"react\"\r\nimport { SparkleAuthContext } from \"../providers/authProvider\"\r\n\r\nexport function useAuth() {\r\n const { token: accessToken } = useContext(SparkleAuthContext)\r\n\r\n return {\r\n token: accessToken,\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, 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"],"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","useContext","useAuth","accessToken","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","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"]}
package/package.json CHANGED
@@ -1,17 +1,11 @@
1
1
  {
2
2
  "name": "sparkle-react",
3
- "version": "0.0.36",
3
+ "version": "0.0.37",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
7
7
  "main": "./dist/index.mjs",
8
8
  "types": "./dist/index.d.mts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.mts",
12
- "import": "./dist/index.mjs"
13
- }
14
- },
15
9
  "license": "MIT",
16
10
  "devDependencies": {
17
11
  "@types/jsonwebtoken": "^9.0.5",