sparkle-react 0.0.32 → 0.0.34

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,18 +6,9 @@ export { OBSRequestTypes, OBSResponseTypes } from 'obs-websocket-js';
6
6
 
7
7
  declare const sparkleConfig: z.ZodObject<{
8
8
  userId: z.ZodString;
9
- wsUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
10
- /** Projet Sparkle (runtime `serverId`) — requis pour `useTask` côté widget public. */
9
+ apiKey: z.ZodOptional<z.ZodString>;
11
10
  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>;
11
+ wsUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
21
12
  obs: z.ZodOptional<z.ZodObject<{
22
13
  clientId: z.ZodString;
23
14
  bridgeUrl: z.ZodDefault<z.ZodOptional<z.ZodString>>;
@@ -28,28 +19,70 @@ declare const sparkleConfig: z.ZodObject<{
28
19
  clientId: string;
29
20
  bridgeUrl?: string | undefined;
30
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
+ /** Config pour `useInvokeTask` (widgets publics). */
29
+ publicInvoke: z.ZodOptional<z.ZodObject<{
30
+ projectId: z.ZodOptional<z.ZodString>;
31
+ widgetFileId: z.ZodOptional<z.ZodString>;
32
+ pageOwnerUserId: z.ZodOptional<z.ZodString>;
33
+ publicProfileTwitchSlug: z.ZodOptional<z.ZodString>;
34
+ invokePath: z.ZodOptional<z.ZodString>;
35
+ }, "strip", z.ZodTypeAny, {
36
+ projectId?: string | undefined;
37
+ widgetFileId?: string | undefined;
38
+ pageOwnerUserId?: string | undefined;
39
+ publicProfileTwitchSlug?: string | undefined;
40
+ invokePath?: string | undefined;
41
+ }, {
42
+ projectId?: string | undefined;
43
+ widgetFileId?: string | undefined;
44
+ pageOwnerUserId?: string | undefined;
45
+ publicProfileTwitchSlug?: string | undefined;
46
+ invokePath?: string | undefined;
47
+ }>>;
31
48
  }, "strip", z.ZodTypeAny, {
32
49
  userId: string;
33
50
  wsUrl: string;
34
- taskProxyPath: string;
51
+ taskApiPath: string;
35
52
  projectId?: string | undefined;
36
- fileId?: string | undefined;
37
- viewerSessionToken?: string | undefined;
53
+ apiKey?: string | undefined;
38
54
  obs?: {
39
55
  clientId: string;
40
56
  bridgeUrl: string;
41
57
  } | undefined;
58
+ viewerSessionToken?: string | null | undefined;
59
+ taskApiBaseUrl?: string | undefined;
60
+ publicInvoke?: {
61
+ projectId?: string | undefined;
62
+ widgetFileId?: string | undefined;
63
+ pageOwnerUserId?: string | undefined;
64
+ publicProfileTwitchSlug?: string | undefined;
65
+ invokePath?: string | undefined;
66
+ } | undefined;
42
67
  }, {
43
68
  userId: string;
44
- wsUrl?: string | undefined;
45
69
  projectId?: string | undefined;
46
- fileId?: string | undefined;
47
- taskProxyPath?: string | undefined;
48
- viewerSessionToken?: string | undefined;
70
+ apiKey?: string | undefined;
71
+ wsUrl?: string | undefined;
49
72
  obs?: {
50
73
  clientId: string;
51
74
  bridgeUrl?: string | undefined;
52
75
  } | undefined;
76
+ viewerSessionToken?: string | null | undefined;
77
+ taskApiBaseUrl?: string | undefined;
78
+ taskApiPath?: string | undefined;
79
+ publicInvoke?: {
80
+ projectId?: string | undefined;
81
+ widgetFileId?: string | undefined;
82
+ pageOwnerUserId?: string | undefined;
83
+ publicProfileTwitchSlug?: string | undefined;
84
+ invokePath?: string | undefined;
85
+ } | undefined;
53
86
  }>;
54
87
  type SparkleConfig = z.infer<typeof sparkleConfig>;
55
88
 
@@ -66,9 +99,7 @@ type RealtimeValue = string | number | boolean | {
66
99
  declare function useRealtime<T extends RealtimeValue>(key: string, defaultValue?: T): T[];
67
100
 
68
101
  declare function useAuth(): {
69
- /** Identifiant Sparkle du propriétaire du widget (historique : `token`). */
70
102
  token: string | null | undefined;
71
- viewerSessionToken: string | null;
72
103
  };
73
104
 
74
105
  /** Version allégée des hooks pour le prompt LLM (sans événements absents de EventTypeMap). */
@@ -347,30 +378,51 @@ declare function useTwitch(asUser?: string | number): {
347
378
  };
348
379
 
349
380
  declare function useObs(): {
350
- status: "error" | "disconnected" | "connecting" | "connected";
381
+ status: "disconnected" | "connecting" | "connected" | "error";
351
382
  connected: boolean;
352
383
  call: <T extends keyof OBSRequestTypes>(requestType: T, requestData?: OBSRequestTypes[T]) => Promise<OBSResponseTypes[T]>;
353
384
  };
354
385
  declare function useObsCall<T extends keyof OBSRequestTypes>(requestType: T): (requestData?: OBSRequestTypes[T]) => Promise<OBSResponseTypes[T]>;
355
386
 
356
- type RunTaskOptions = {
357
- /** Surcharge ponctuelle du corps (fusionné avec `payload`). */
358
- merge?: Record<string, unknown>;
387
+ type SparkleTaskInvokeResult = {
388
+ traceId: string;
389
+ scheduled?: boolean;
390
+ scheduledAt?: string;
359
391
  };
360
- /**
361
- * Appelle une tâche (`createTask` / `id` de tâche) via le proxy Next configuré
362
- * dans `SparkleProvider` (`taskProxyPath`), sans exposer l’URL de la runtime.
363
- *
364
- * Requiert `projectId`, `fileId` et `userId` (propriétaire) dans la config du provider.
365
- */
366
- declare function useTask(): {
367
- runTask: (taskId: string, payload?: unknown, options?: RunTaskOptions) => Promise<unknown>;
392
+ type UseTaskOptions = {
393
+ /**
394
+ * Origine du site Next (ex. `https://app.sparkle.example`).
395
+ * Si absent : `SparkleConfig.taskApiBaseUrl`, puis l’origine du navigateur (même onglet).
396
+ */
397
+ baseUrl?: string;
398
+ /**
399
+ * Chemin du route handler (commence par `/`). Défaut : `SparkleConfig.taskApiPath` ou `/api/sparkle-react/task`.
400
+ */
401
+ path?: string;
402
+ /**
403
+ * Projet cible. Défaut : `SparkleConfig.projectId` (requis pour résoudre la tâche côté serveur).
404
+ */
405
+ projectId?: string;
368
406
  };
369
-
370
- /** Message parent iframe widget (page publique `/u/...`). */
371
- declare const SPARKLE_PUBLIC_VIEWER_MSG: {
372
- readonly source: "sparkle-public-parent";
373
- readonly type: "SPARKLE_VIEWER_TOKEN";
407
+ type UseTaskInvokeOptions = {
408
+ /** Comme `ctx.invokeUntil` : nombre = secondes ; chaîne = durée (`24h`) ou date ISO. */
409
+ delay?: string | number;
410
+ };
411
+ type UseTaskResult = {
412
+ /** Fire-and-forget ; erreurs capturées dans `error` sans rejet. */
413
+ mutate: (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => void;
414
+ mutateAsync: (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => Promise<SparkleTaskInvokeResult>;
415
+ isPending: boolean;
416
+ /** Alias pratique pour les libs type react-query */
417
+ isLoading: boolean;
418
+ error: Error | null;
419
+ data: SparkleTaskInvokeResult | undefined;
420
+ reset: () => void;
374
421
  };
422
+ /**
423
+ * Enfile une tâche handler sur le runtime Sparkle (même chemin que `ctx.invoke` / `ctx.invokeUntil`),
424
+ * via `POST /api/sparkle-react/task` (session + `projectId`).
425
+ */
426
+ declare function useTask(options?: UseTaskOptions): UseTaskResult;
375
427
 
376
- export { type RunTaskOptions, SPARKLE_PUBLIC_VIEWER_MSG, type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, useAuth, useHook, useObs, useObsCall, useRealtime, useTask, useTwitch };
428
+ export { type SparkleHookKey, SparkleHookKeys, SparkleProvider, type SparkleProviderProps, type SparkleTaskInvokeResult, type UseTaskInvokeOptions, type UseTaskOptions, type UseTaskResult, useAuth, useHook, useObs, useObsCall, useRealtime, useTask, useTwitch };
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import b,{useMemo as ie,useEffect as ae,useState as ce}from"react";import{createContext as H,useContext as J}from"react";var C=H(null);function P({value:t,children:r}){return React.createElement(C.Provider,{value:t},r)}function E(){let t=J(C);if(!t)throw new Error("useSparkleConfig doit \xEAtre utilis\xE9 dans un SparkleProvider");return t}import*as O from"react";import{createContext as D,useEffect as R,useState as K}from"react";var T={source:"sparkle-public-parent",type:"SPARKLE_VIEWER_TOKEN"},h=D({token:null,viewerSessionToken:null}),I=({children:t,config:r})=>{let[e,o]=K(r.viewerSessionToken??null);return R(()=>{o(r.viewerSessionToken??null)},[r.viewerSessionToken]),R(()=>{let n=u=>{if(u.origin!==window.location.origin)return;let s=u.data;!s||s.source!==T.source||s.type!==T.type||o(typeof s.token=="string"&&s.token.length>0?s.token:null)};return window.addEventListener("message",n),()=>window.removeEventListener("message",n)},[]),O.createElement(h.Provider,{value:{token:r.userId,viewerSessionToken:e}},t)};import Y,{useCallback as N,useContext as Q,useRef as X}from"react";import{createContext as Z,useEffect as A,useState as L}from"react";import V from"events";import l from"zod";var $=l.object({type:l.literal("subscribe"),channels:l.array(l.string())}),G=l.object({type:l.literal("hook"),channels:l.array(l.string())}),F=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()})}),Me=l.union([$,F,G]),v=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 S=Z({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),B=({children:t,config:r})=>{let[e,o]=L({}),[n,u]=L({}),s=X(null),{token:f}=Q(h);A(()=>(s.current=new v(r.wsUrl),s.current.on("update",c=>{let{data:i,type:k}=c;if(k==="hook"){let p=JSON.parse(i.value);u(d=>({...d,[p.hook]:p.payload})),console.log("receive hooks",p)}else{let p=i.value;console.log("value",p);try{let d=parseFloat(i.value);if(!isNaN(d)&&String(d)===String(i.value).trim())p=d;else try{p=JSON.parse(i.value)}catch{p=i.value}}catch{p=i.value}o(d=>({...d,[i.key]:p})),console.log("receive values",e)}}),()=>{s.current?.disconnect()}),[]),A(()=>{!s.current||!f||s.current.authenticate(f)},[f]);let g=N(c=>{s.current?.subscribe(c)},[s]),a=N(c=>{s.current?.hook(c)},[s]);return Y.createElement(S.Provider,{value:{subscribe:g,listen:a,values:e,hooks:n}},t)};import ee,{createContext as te,useCallback as re,useEffect as oe,useRef as M,useState as ne}from"react";var y=te({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function W({url:t,children:r}){let[e,o]=ne("disconnected"),n=M(null),u=M(!1);oe(()=>{if(!t)return;u.current=!1;let f;return(async()=>{let{default:a}=await import("obs-websocket-js"),c=new a;n.current=c;let i=async()=>{if(!u.current){o("connecting");try{await c.connect(t),u.current||o("connected")}catch{u.current||(o("error"),f=setTimeout(i,3e3))}}};c.on("ConnectionClosed",()=>{u.current||(o("disconnected"),f=setTimeout(i,3e3))}),c.on("ConnectionError",()=>{u.current||o("error")}),await i()})(),()=>{u.current=!0,clearTimeout(f),n.current?.disconnect(),n.current=null,o("disconnected")}},[t]);let s=re(async(f,g)=>{let a=n.current;if(!a)throw new Error("OBS not connected");return a.call(f,g)},[]);return ee.createElement(y.Provider,{value:{status:e,call:s}},r)}import{z as m}from"zod";var se=m.object({clientId:m.string(),bridgeUrl:m.string().optional().default("wss://sparkle-bot-v2.fly.dev/obs-bridge")}).optional(),x=m.object({userId:m.string(),wsUrl:m.string().optional().default("wss://sparkle-bot-v2.fly.dev/ws"),projectId:m.string().min(1).optional(),fileId:m.string().min(1).optional(),taskProxyPath:m.string().optional().default("/api/public-widget/task"),viewerSessionToken:m.string().min(1).optional(),obs:se});var ue=({children:t})=>{let[r,e]=ce(!1);return ae(()=>{e(!0)},[]),r?t:null},le=x.parse({userId:""}),tt=({children:t,config:r})=>{let e=x.parse({...le,...r}),o=ie(()=>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=b.createElement(P,{value:e},b.createElement(I,{config:e},b.createElement(B,{config:e},b.createElement(ue,null,t))));return o?b.createElement(W,{url:o},n):n};import{useContext as pe,useEffect as de,useMemo as fe}from"react";function it(t,r){let e=pe(S),o=fe(()=>[t],[t]);if(!e)throw new Error("You must use useRealtime inside a SparkleProvider");return de(()=>{e.subscribe([t])},[o]),o.map(n=>e.values[n]??r)}import{useContext as me}from"react";function lt(){let{token:t,viewerSessionToken:r}=me(h);return{token:t,viewerSessionToken:r}}import{useContext as ge,useEffect as he}from"react";var gt=["notification.follow","notification.cheer","notification.tips","notification.subscribe","notification.subscribe.end","notification.subscribe.gift","notification.raid","chat.message","stream.online","stream.offline","stream.update","reward.create","reward.update","reward.remove","reward.redemption.claim","reward.redemption.update","poll.begin","poll.progress","poll.end","prediction.begin","prediction.progress","prediction.lock","prediction.end","moderator.add","moderator.remove","moderator.ban","moderator.unban","moderator.shield_mode.begin"];function ht(t){let r=ge(S);if(!r)throw new Error("You must use useHook inside a SparkleProvider");return he(()=>{r.listen(t)},[t]),r.hooks[t]}import{ApiClient as ye}from"@twurple/api";import{StaticAuthProvider as Te}from"@twurple/auth";import{useEffect as xe,useState as we}from"react";import{useCallback as q,useState as ke,useRef as Se,useLayoutEffect as be}from"react";var w=()=>{};var j=typeof window<"u";var ve=(t,r,e)=>{if(!j)return[r,w,w];if(!t)throw new Error("useLocalStorage key may not be falsy");let o=e?e.raw?a=>a:e.deserializer:JSON.parse,n=Se(a=>{try{let c=e?e.raw?String:e.serializer:JSON.stringify,i=localStorage.getItem(a);return i!==null?o(i):(r&&localStorage.setItem(a,c(r)),r)}catch{return r}}),[u,s]=ke(()=>n.current(t));be(()=>s(n.current(t)),[t]);let f=q(a=>{try{let c=typeof a=="function"?a(u):a;if(typeof c>"u")return;let i;e?e.raw?typeof c=="string"?i=c:i=JSON.stringify(c):e.serializer?i=e.serializer(c):i=JSON.stringify(c):i=JSON.stringify(c),localStorage.setItem(t,i),s(o(i))}catch{}},[t,s]),g=q(()=>{try{localStorage.removeItem(t),s(void 0)}catch{}},[t,s]);return[u,f,g]},z=ve;function Rt(t){let[r,e]=we(null),[o]=z("twitch");return xe(()=>{console.log("env",process.env.TWITCH_CLIENT_ID),console.log("init");let n=new ye({authProvider:new Te("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 Ce,useContext as U}from"react";function At(){let{status:t,call:r}=U(y);return{status:t,connected:t==="connected",call:r}}function Lt(t){let{call:r}=U(y);return Ce(e=>r(t,e),[r,t])}import{useCallback as Pe,useContext as Ee}from"react";function qt(){let t=E(),{viewerSessionToken:r}=Ee(h);return{runTask:Pe(async(o,n,u)=>{let s=t.userId,{projectId:f,fileId:g,taskProxyPath:a}=t;if(!s?.trim())throw new Error("[useTask] SparkleProvider: userId (propri\xE9taire) manquant");if(!f?.trim()||!g?.trim())throw new Error("[useTask] SparkleProvider: projectId et fileId sont requis pour invoquer une t\xE2che");if(!o?.trim())throw new Error("[useTask] taskId vide");let c=a.startsWith("http://")||a.startsWith("https://")||a.startsWith("/")?a:`/${a}`,i={taskId:o.trim(),ownerUserId:s,projectId:f,fileId:g,payload:u?.merge&&n!==void 0&&typeof n=="object"?{...n,...u.merge}:u?.merge&&n===void 0?u.merge:n??{}},k=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json",...r?{Authorization:`Bearer ${r}`}:{}},credentials:"include",body:JSON.stringify(i)}),p=await k.text(),d;if(p)try{d=JSON.parse(p)}catch{d=p}if(!k.ok){let _=typeof d=="object"&&d!==null&&"error"in d?String(d.error):p||k.statusText;throw new Error(`[useTask] ${k.status}: ${_}`)}return d},[t,r])}}export{T as SPARKLE_PUBLIC_VIEWER_MSG,gt as SparkleHookKeys,tt as SparkleProvider,lt as useAuth,ht as useHook,At as useObs,Lt as useObsCall,it as useRealtime,qt as useTask,Rt as useTwitch};
1
+ import y,{useMemo as K,useEffect as me,useState as ke}from"react";import Le,{createContext as Q,useContext as X}from"react";var Z={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null,taskApiPath:"/api/sparkle-react/task"},A=Q(Z);function M(){return X(A)}import*as W from"react";import{createContext as ee}from"react";var v=ee({token:null,viewerSessionToken:null}),z=({children:t,config:r})=>W.createElement(v.Provider,{value:{token:r.userId,viewerSessionToken:r.viewerSessionToken??null}},t);import se,{useCallback as q,useContext as ie,useRef as ae}from"react";import{createContext as ce,useEffect as J,useState as H}from"react";import te from"events";import u from"zod";var re=u.object({type:u.literal("subscribe"),channels:u.array(u.string())}),oe=u.object({type:u.literal("hook"),channels:u.array(u.string())}),ne=u.object({type:u.literal("unsubscribe"),channels:u.string()}),He=u.object({event:u.literal("update"),type:u.enum(["value","hook"]),data:u.object({key:u.string(),value:u.string()})}),De=u.union([re,ne,oe]),C=class extends te{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 s=JSON.parse(o.data);this.emit("update",s)},this.socket.onclose=()=>{this.reconnect()}}reconnect(){setTimeout(()=>{this.queue=[{type:"subscribe",channels:this.subscriptions},{type:"hook",channels:this.hooks}],console.log("try reconnecting"),this.connect()},5e3)}subscribe(e){let o=e.filter(s=>!this.subscriptions.includes(s));o.length&&(this.subscriptions=[...this.subscriptions,...o],console.log("subscribe",e),this.send({type:"subscribe",channels:e}))}hook(e){let o=[e].filter(s=>!this.hooks.includes(s));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 S=ce({subscribe:()=>{},listen:()=>{},values:{},hooks:{}}),D=({children:t,config:r})=>{let[e,o]=H({}),[s,p]=H({}),a=ae(null),{token:l}=ie(v);J(()=>(a.current=new C(r.wsUrl),a.current.on("update",i=>{let{data:n,type:T}=i;if(T==="hook"){let f=JSON.parse(n.value);p(k=>({...k,[f.hook]:f.payload})),console.log("receive hooks",f)}else{let f=n.value;console.log("value",f);try{let k=parseFloat(n.value);if(!isNaN(k)&&String(k)===String(n.value).trim())f=k;else try{f=JSON.parse(n.value)}catch{f=n.value}}catch{f=n.value}o(k=>({...k,[n.key]:f})),console.log("receive values",e)}}),()=>{a.current?.disconnect()}),[]),J(()=>{!a.current||!l||a.current.authenticate(l)},[l]);let m=q(i=>{a.current?.subscribe(i)},[a]),c=q(i=>{a.current?.hook(i)},[a]);return se.createElement(S.Provider,{value:{subscribe:m,listen:c,values:e,hooks:s}},t)};import le,{createContext as ue,useCallback as pe,useEffect as de,useRef as _,useState as fe}from"react";var P=ue({status:"disconnected",call:()=>Promise.reject(new Error("ObsProvider not mounted"))});function $({url:t,children:r}){let[e,o]=fe("disconnected"),s=_(null),p=_(!1);de(()=>{if(!t)return;p.current=!1;let l;return(async()=>{let{default:c}=await import("obs-websocket-js"),i=new c;s.current=i;let n=async()=>{if(!p.current){o("connecting");try{await i.connect(t),p.current||o("connected")}catch{p.current||(o("error"),l=setTimeout(n,3e3))}}};i.on("ConnectionClosed",()=>{p.current||(o("disconnected"),l=setTimeout(n,3e3))}),i.on("ConnectionError",()=>{p.current||o("error")}),await n()})(),()=>{p.current=!0,clearTimeout(l),s.current?.disconnect(),s.current=null,o("disconnected")}},[t]);let a=pe(async(l,m)=>{let c=s.current;if(!c)throw new Error("OBS not connected");return c.call(l,m)},[]);return le.createElement(P.Provider,{value:{status:e,call:a}},r)}var ge=({children:t})=>{let[r,e]=ke(!1);return me(()=>{e(!0)},[]),r?t:null},he={userId:"",wsUrl:"wss://sparkle-bot-v2.fly.dev/ws",viewerSessionToken:null,taskApiPath:"/api/sparkle-react/task"},st=({children:t,config:r})=>{let e=K(()=>({...he,...r}),[r]),o=K(()=>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]),s=y.createElement(z,{config:e},y.createElement(D,{config:e},y.createElement(ge,null,t))),p=o?y.createElement($,{url:o},s):s;return y.createElement(A.Provider,{value:e},p)};import{useContext as be,useEffect as ve,useMemo as Se}from"react";function ut(t,r){let e=be(S),o=Se(()=>[t],[t]);if(!e)throw new Error("You must use useRealtime inside a SparkleProvider");return ve(()=>{e.subscribe([t])},[o]),o.map(s=>e.values[s]??r)}import{useContext as ye}from"react";function mt(){let{token:t}=ye(v);return{token:t}}import{useContext as Te,useEffect as xe}from"react";var vt=["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 St(t){let r=Te(S);if(!r)throw new Error("You must use useHook inside a SparkleProvider");return xe(()=>{r.listen(t)},[t]),r.hooks[t]}import{ApiClient as Re}from"@twurple/api";import{StaticAuthProvider as Ee}from"@twurple/auth";import{useEffect as Ie,useState as Ae}from"react";import{useCallback as G,useState as we,useRef as Ce,useLayoutEffect as Pe}from"react";var U=()=>{};var F=typeof window<"u";var Oe=(t,r,e)=>{if(!F)return[r,U,U];if(!t)throw new Error("useLocalStorage key may not be falsy");let o=e?e.raw?c=>c:e.deserializer:JSON.parse,s=Ce(c=>{try{let i=e?e.raw?String:e.serializer:JSON.stringify,n=localStorage.getItem(c);return n!==null?o(n):(r&&localStorage.setItem(c,i(r)),r)}catch{return r}}),[p,a]=we(()=>s.current(t));Pe(()=>a(s.current(t)),[t]);let l=G(c=>{try{let i=typeof c=="function"?c(p):c;if(typeof i>"u")return;let n;e?e.raw?typeof i=="string"?n=i:n=JSON.stringify(i):e.serializer?n=e.serializer(i):n=JSON.stringify(i):n=JSON.stringify(i),localStorage.setItem(t,n),a(o(n))}catch{}},[t,a]),m=G(()=>{try{localStorage.removeItem(t),a(void 0)}catch{}},[t,a]);return[p,l,m]},V=Oe;function Ut(t){let[r,e]=Ae(null),[o]=V("twitch");return Ie(()=>{console.log("env",process.env.TWITCH_CLIENT_ID),console.log("init");let s=new Re({authProvider:new Ee("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(s),()=>{e(null)}},[]),{apiClient:r}}import{useCallback as Ue,useContext as Y}from"react";function Lt(){let{status:t,call:r}=Y(P);return{status:t,connected:t==="connected",call:r}}function Mt(t){let{call:r}=Y(P);return Ue(e=>r(t,e),[r,t])}import{useCallback as O,useMemo as Ne,useState as N}from"react";function je(t,r){let e=r.startsWith("/")?r:`/${r}`;if(typeof window>"u")return e;let o=(t?.replace(/\/$/,"")??"").trim()||(window.location?.origin&&!window.location.origin.startsWith("null")?window.location.origin:"");return o?new URL(e,`${o}/`).href:e}function Jt(t){let{taskApiBaseUrl:r,taskApiPath:e,projectId:o}=M(),[s,p]=N(void 0),[a,l]=N(null),[m,c]=N(!1),i=t?.baseUrl??r,n=t?.path??e??"/api/sparkle-react/task",T=t?.projectId??o,f=O(()=>je(i,n),[i,n]),k=O(()=>{p(void 0),l(null),c(!1)},[]),x=O(async(R,E,w)=>{let B=T?.trim();if(!B){let g=new Error("missing_project_id");throw l(g),g}c(!0),l(null);try{let g={task:R,projectId:B,payload:E??{}};w?.delay!==void 0&&(g.delay=w.delay);let h=await fetch(f(),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify(g)}),d=await h.json();if(!h.ok){let I="error"in d&&typeof d.error=="string"?d.error:`http_${h.status}`,b=new Error(I);throw l(b),b}if(!d||typeof d!="object"||!("ok"in d)||d.ok!==!0){let I=d&&typeof d=="object"&&"error"in d&&typeof d.error=="string"?d.error:"task_failed",b=new Error(I);throw l(b),b}let L={traceId:d.traceId,...d.scheduled?{scheduled:!0,scheduledAt:d.scheduledAt}:{}};return p(L),L}catch(g){let h=g instanceof Error?g:new Error(String(g));throw l(h),h}finally{c(!1)}},[f,T]),j=O((R,E,w)=>{x(R,E,w).catch(()=>{})},[x]);return Ne(()=>({mutate:j,mutateAsync:x,isPending:m,isLoading:m,error:a,data:s,reset:k}),[j,x,m,a,s,k])}export{vt as SparkleHookKeys,st as SparkleProvider,mt as useAuth,St as useHook,Lt as useObs,Mt as useObsCall,ut as useRealtime,Jt as useTask,Ut 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/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/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 taskApiPath: \"/api/sparkle-react/task\",\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 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 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: (task: string, payload?: unknown, invokeOptions?: UseTaskInvokeOptions) => 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 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 + `projectId`).\r\n */\r\nexport function useTask(options?: UseTaskOptions): UseTaskResult {\r\n const { taskApiBaseUrl, taskApiPath, projectId: configProjectId } = useSparkleConfig()\r\n const [data, setData] = useState<SparkleTaskInvokeResult | undefined>(undefined)\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 res = await fetch(resolveUrl(), {\r\n method: \"POST\",\r\n credentials: \"include\",\r\n headers: { \"Content-Type\": \"application/json\" },\r\n body: JSON.stringify(body),\r\n })\r\n\r\n const json = (await res.json()) as TaskSuccessJson | TaskErrorJson\r\n\r\n if (!res.ok) {\r\n const msg =\r\n \"error\" in json && typeof json.error === \"string\"\r\n ? json.error\r\n : `http_${res.status}`\r\n const err = new Error(msg)\r\n setError(err)\r\n throw err\r\n }\r\n\r\n if (!json || typeof json !== \"object\" || !(\"ok\" in json) || json.ok !== true) {\r\n const msg =\r\n json && typeof json === \"object\" && \"error\" in json && typeof json.error === \"string\"\r\n ? json.error\r\n : \"task_failed\"\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 ? { scheduled: true, scheduledAt: json.scheduledAt } : {}),\r\n }\r\n setData(result)\r\n return result\r\n } catch (e) {\r\n const err = e instanceof Error ? e : 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]\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","SparkleProvider","config","mergedConfig","useMemo","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","buildTaskUrl","baseUrl","path","normalizedPath","origin","useTask","options","taskApiBaseUrl","taskApiPath","configProjectId","useSparkleConfig","data","setData","useState","error","setError","isPending","setIsPending","projectId","resolveUrl","useCallback","reset","mutateAsync","task","payload","invokeOptions","pid","err","body","res","json","msg","result","e","mutate","useMemo"]}
package/package.json CHANGED
@@ -1,11 +1,17 @@
1
1
  {
2
2
  "name": "sparkle-react",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
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
+ },
9
15
  "license": "MIT",
10
16
  "devDependencies": {
11
17
  "@types/jsonwebtoken": "^9.0.5",