@zaflun/lumio-sdk 0.6.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import React, { CSSProperties } from 'react';
2
- import { LumioEventType, LumioActionType, Sound } from '@zaflun/lumio-protocol';
2
+ import { LumioEventType, LumioEventPayloadMap, LumioActionType, Sound } from '@zaflun/lumio-protocol';
3
3
  export { Sound } from '@zaflun/lumio-protocol';
4
4
 
5
5
  interface RenderOptions {
6
- target: "editor" | "layer" | "interactive";
6
+ target: "editor" | "layer";
7
7
  }
8
8
  declare const Lumio: {
9
9
  render(element: React.ReactElement, options: RenderOptions): void;
@@ -338,19 +338,37 @@ declare function useVisionState<T>(key: string, defaultValue: T): [T, (value: T)
338
338
 
339
339
  interface ExtensionContext {
340
340
  extensionId: string | null;
341
- target: "editor" | "layer" | "interactive" | null;
341
+ target: "editor" | "layer" | null;
342
342
  overlayId: string | null;
343
- interactiveUrl: string | null;
344
343
  }
344
+ /**
345
+ * The install this surface renders. Set from `lifecycle.init`, which the host
346
+ * sends before the bundle runs, so the first render already has it.
347
+ */
345
348
  declare function useExtensionContext(): ExtensionContext;
346
349
 
347
- declare function useLumioEvent(eventType: LumioEventType): unknown | null;
350
+ /**
351
+ * The latest payload of one platform event type, or `null` until the first
352
+ * one arrives. The payload type follows `type`, e.g.
353
+ * `useLumioEvent("twitch:cheer")?.bits` is a `number`.
354
+ */
355
+ declare function useLumioEvent<K extends LumioEventType>(eventType: K): LumioEventPayloadMap[K] | null;
356
+
357
+ /**
358
+ * Calls `cb` with the payload of every message a handler of this install
359
+ * pushes on `topic` with `ctx.realtime.push(topic, payload)`. Runs on every
360
+ * rendered surface of the install (editor and layer). The host delivers what
361
+ * arrives while the surface is open; nothing is replayed.
362
+ *
363
+ * `cb` may change between renders without re-subscribing.
364
+ */
365
+ declare function useRealtime<T = unknown>(topic: string, cb: (payload: T) => void): void;
348
366
 
349
367
  interface LumioConfig {
350
368
  extension_id?: string;
351
369
  install_id?: string;
352
370
  account_id?: string;
353
- target?: "editor" | "layer" | "interactive";
371
+ target?: "editor" | "layer";
354
372
  permissions?: string[];
355
373
  settings?: Record<string, unknown>;
356
374
  }
@@ -361,6 +379,11 @@ declare global {
361
379
  __lumio_on_config?: (fn: (config: LumioConfig) => void) => () => void;
362
380
  }
363
381
  }
382
+ /**
383
+ * The install's config. Starts from `lifecycle.init`, which the host sends
384
+ * before the bundle runs, so the first render already has it. A
385
+ * `config.update` from the host replaces only the fields it carries.
386
+ */
364
387
  declare function useLumioConfig(): [LumioConfig, (configOrUpdater: LumioConfig | ((prev: LumioConfig) => LumioConfig)) => void];
365
388
 
366
389
  interface OverlayTheme {
@@ -389,11 +412,12 @@ interface OverlayTheme {
389
412
  * Returns the active overlay/widget theme variables.
390
413
  *
391
414
  * Extensions use this to style themselves consistently with the
392
- * overlay or widget they're rendered in. Only relevant for `layer`
393
- * and `interactive` targets (stream overlay, widget pages).
415
+ * overlay or widget they're rendered in. Only relevant for the `layer`
416
+ * target (stream overlay, widget pages).
394
417
  *
395
- * Theme is provided via lifecycle.init and updated via theme.change when
396
- * the user switches the overlay/widget theme.
418
+ * The theme comes from `lifecycle.init`, which the host sends before the
419
+ * bundle runs, so the first render already has it. A `theme.change` from the
420
+ * host updates it.
397
421
  */
398
422
  declare function useLumioTheme(): OverlayTheme;
399
423
 
@@ -402,10 +426,15 @@ interface LumioIdentity {
402
426
  accountId: string | null;
403
427
  role: string | null;
404
428
  }
429
+ /**
430
+ * Who this surface renders for. Set from `lifecycle.init`, which the host
431
+ * sends before the bundle runs, so the first render already has it.
432
+ */
405
433
  declare function useLumioIdentity(): LumioIdentity;
406
434
 
407
435
  interface UseLumioActionResult {
408
- execute: (params?: Record<string, unknown>) => void;
436
+ /** Runs the action. Resolves once it settled; a failure lands in `error`, never as a rejection. */
437
+ execute: (params?: Record<string, unknown>) => Promise<void>;
409
438
  data: unknown | null;
410
439
  isLoading: boolean;
411
440
  error: string | null;
@@ -690,4 +719,4 @@ interface LocalFontDefinition {
690
719
  }
691
720
  declare function localFont(options: LocalFontOptions): LocalFontDefinition;
692
721
 
693
- export { ANIMATION_PRESETS, Animated, type AnimatedProps, type AnimationPresetName, Box, type BoxProps, Button, type ButtonProps, CompactView, type CompactViewProps, Dropdown, type DropdownOption, type DropdownProps, EditorButton, type EditorButtonProps, EditorChannelRow, type EditorChannelRowProps, EditorCheckbox, type EditorCheckboxProps, EditorColorPicker, type EditorColorPickerProps, EditorDivider, EditorFontPicker, type EditorFontPickerProps, EditorFontRow, type EditorFontRowProps, EditorGroup, type EditorGroupProps, EditorInfo, type EditorInfoProps, EditorInput, type EditorInputProps, EditorLabel, type EditorLabelProps, EditorSelect, type EditorSelectOption, type EditorSelectProps, EditorSlider, type EditorSliderProps, EditorTextarea, type EditorTextareaProps, type ExtensionContext, type FontDisplay, type FontSource, HStack, type HStackProps, IconDisplay, type IconDisplayProps, IconPicker, type IconPickerProps, type IconPickerSize, type IconSource, type IconValue, Image, type ImageProps, type KeyframeBlock, type KeyframeDefinition, List, type ListProps, type LocalFontDefinition, type LocalFontOptions, Lumio, type LumioConfig, type LumioIdentity, NumberField, type NumberFieldProps, type OverlayTheme, type RenderOptions, Slider, type SliderProps, Text, TextArea, type TextAreaProps, TextField, type TextFieldProps, type TextProps, Toggle, type ToggleProps, type UseAnimationResult, type UseIconPickerOptions, type UseIconPickerResult, type UseLumioActionResult, type UseLumioSoundResult, type UseMutationResult, type UseQueryResult, VStack, type VStackProps, type VisionStyle, WorkerBridge, defineKeyframes, flushKeyframes, getPreset, localFont, useAnimation, useEditorAction, useExtensionContext, useExtensionStorage, useIconPicker, useLumioAction, useLumioConfig, useLumioEvent, useLumioIdentity, useLumioSound, useLumioTheme, useMutation, useQuery, useVisionState };
722
+ export { ANIMATION_PRESETS, Animated, type AnimatedProps, type AnimationPresetName, Box, type BoxProps, Button, type ButtonProps, CompactView, type CompactViewProps, Dropdown, type DropdownOption, type DropdownProps, EditorButton, type EditorButtonProps, EditorChannelRow, type EditorChannelRowProps, EditorCheckbox, type EditorCheckboxProps, EditorColorPicker, type EditorColorPickerProps, EditorDivider, EditorFontPicker, type EditorFontPickerProps, EditorFontRow, type EditorFontRowProps, EditorGroup, type EditorGroupProps, EditorInfo, type EditorInfoProps, EditorInput, type EditorInputProps, EditorLabel, type EditorLabelProps, EditorSelect, type EditorSelectOption, type EditorSelectProps, EditorSlider, type EditorSliderProps, EditorTextarea, type EditorTextareaProps, type ExtensionContext, type FontDisplay, type FontSource, HStack, type HStackProps, IconDisplay, type IconDisplayProps, IconPicker, type IconPickerProps, type IconPickerSize, type IconSource, type IconValue, Image, type ImageProps, type KeyframeBlock, type KeyframeDefinition, List, type ListProps, type LocalFontDefinition, type LocalFontOptions, Lumio, type LumioConfig, type LumioIdentity, NumberField, type NumberFieldProps, type OverlayTheme, type RenderOptions, Slider, type SliderProps, Text, TextArea, type TextAreaProps, TextField, type TextFieldProps, type TextProps, Toggle, type ToggleProps, type UseAnimationResult, type UseIconPickerOptions, type UseIconPickerResult, type UseLumioActionResult, type UseLumioSoundResult, type UseMutationResult, type UseQueryResult, VStack, type VStackProps, type VisionStyle, WorkerBridge, defineKeyframes, flushKeyframes, getPreset, localFont, useAnimation, useEditorAction, useExtensionContext, useExtensionStorage, useIconPicker, useLumioAction, useLumioConfig, useLumioEvent, useLumioIdentity, useLumioSound, useLumioTheme, useMutation, useQuery, useRealtime, useVisionState };
package/dist/index.js CHANGED
@@ -1,8 +1,23 @@
1
+ import {
2
+ WorkerBridge,
3
+ getBridge,
4
+ getInitParams
5
+ } from "./chunk-GVAYWB5X.js";
6
+
1
7
  // src/core/lumio.tsx
2
8
  import { createRoot } from "react-dom/client";
3
9
  var RENDER_TIMEOUT_MS = 1e4;
4
10
  var hasRendered = false;
5
11
  var renderTimeout = null;
12
+ function hostOrigin() {
13
+ try {
14
+ if (typeof document !== "undefined" && document.referrer) {
15
+ return new URL(document.referrer).origin;
16
+ }
17
+ } catch {
18
+ }
19
+ return "*";
20
+ }
6
21
  function startRenderTimeout() {
7
22
  if (renderTimeout) return;
8
23
  renderTimeout = setTimeout(() => {
@@ -16,7 +31,7 @@ function startRenderTimeout() {
16
31
  code: "RENDER_TIMEOUT",
17
32
  message: "Extension did not render within 10 seconds"
18
33
  },
19
- "*"
34
+ hostOrigin()
20
35
  );
21
36
  }
22
37
  }, RENDER_TIMEOUT_MS);
@@ -39,121 +54,11 @@ var Lumio = {
39
54
  }
40
55
  const root = createRoot(rootElement);
41
56
  root.render(element);
42
- window.parent.postMessage({ type: "READY" }, "*");
57
+ window.parent.postMessage({ type: "READY" }, hostOrigin());
43
58
  void options;
44
59
  }
45
60
  };
46
61
 
47
- // src/bridge/WorkerBridge.ts
48
- var WorkerBridge = class {
49
- notificationHandlers = /* @__PURE__ */ new Map();
50
- requestHandlers = /* @__PURE__ */ new Map();
51
- pendingRequests = /* @__PURE__ */ new Map();
52
- requestCounter = 0;
53
- requestTimeout;
54
- messageListener = null;
55
- destroyed = false;
56
- constructor(options) {
57
- this.requestTimeout = options?.requestTimeout ?? 3e4;
58
- this.messageListener = this.handleMessage.bind(this);
59
- globalThis.addEventListener("message", this.messageListener);
60
- }
61
- notify(method, params) {
62
- const msg = {
63
- jsonrpc: "2.0",
64
- method,
65
- ...params !== void 0 ? { params } : {}
66
- };
67
- globalThis.postMessage(msg);
68
- }
69
- request(method, params) {
70
- const id = `req_${++this.requestCounter}`;
71
- const msg = {
72
- jsonrpc: "2.0",
73
- id,
74
- method,
75
- ...params !== void 0 ? { params } : {}
76
- };
77
- return new Promise((resolve, reject) => {
78
- const timer = setTimeout(() => {
79
- this.pendingRequests.delete(id);
80
- reject(new Error("Request timeout"));
81
- }, this.requestTimeout);
82
- this.pendingRequests.set(id, { resolve, reject, timer });
83
- globalThis.postMessage(msg);
84
- });
85
- }
86
- onNotification(method, handler) {
87
- if (!this.notificationHandlers.has(method)) {
88
- this.notificationHandlers.set(method, /* @__PURE__ */ new Set());
89
- }
90
- this.notificationHandlers.get(method).add(handler);
91
- return () => {
92
- this.notificationHandlers.get(method)?.delete(handler);
93
- };
94
- }
95
- onRequest(method, handler) {
96
- this.requestHandlers.set(method, handler);
97
- }
98
- destroy() {
99
- this.destroyed = true;
100
- if (this.messageListener) {
101
- globalThis.removeEventListener("message", this.messageListener);
102
- this.messageListener = null;
103
- }
104
- for (const { timer, reject } of this.pendingRequests.values()) {
105
- if (timer) clearTimeout(timer);
106
- reject(new Error("Bridge destroyed"));
107
- }
108
- this.pendingRequests.clear();
109
- this.notificationHandlers.clear();
110
- this.requestHandlers.clear();
111
- }
112
- handleMessage(event) {
113
- if (this.destroyed) return;
114
- const data = event.data;
115
- if (!data || typeof data !== "object" || data.jsonrpc !== "2.0") return;
116
- if ("id" in data && !("method" in data)) {
117
- const pending = this.pendingRequests.get(data.id);
118
- if (pending) {
119
- this.pendingRequests.delete(data.id);
120
- if (pending.timer) clearTimeout(pending.timer);
121
- if (data.error) {
122
- pending.reject(new Error(data.error.message));
123
- } else {
124
- pending.resolve(data.result);
125
- }
126
- }
127
- return;
128
- }
129
- if ("id" in data && "method" in data) {
130
- const handler = this.requestHandlers.get(data.method);
131
- if (handler) {
132
- handler(data.params).then((result) => {
133
- const response = { jsonrpc: "2.0", id: data.id, result };
134
- globalThis.postMessage(response);
135
- }).catch((err) => {
136
- const response = {
137
- jsonrpc: "2.0",
138
- id: data.id,
139
- error: { code: -32603, message: err.message }
140
- };
141
- globalThis.postMessage(response);
142
- });
143
- }
144
- return;
145
- }
146
- if ("method" in data && !("id" in data)) {
147
- const handlers = this.notificationHandlers.get(data.method);
148
- if (handlers) {
149
- for (const handler of handlers) {
150
- handler(data.params);
151
- }
152
- }
153
- }
154
- }
155
- };
156
-
157
62
  // src/components/Box.tsx
158
63
  import React from "react";
159
64
  function Box({ children, style }) {
@@ -352,17 +257,6 @@ function EditorChannelRow(props) {
352
257
 
353
258
  // src/hooks/useEditorAction.ts
354
259
  import { useCallback } from "react";
355
-
356
- // src/renderer/bridge-context.ts
357
- var bridgeInstance = null;
358
- function getBridge() {
359
- if (!bridgeInstance) {
360
- bridgeInstance = new WorkerBridge();
361
- }
362
- return bridgeInstance;
363
- }
364
-
365
- // src/hooks/useEditorAction.ts
366
260
  function useEditorAction() {
367
261
  const dispatch = useCallback((action, payload) => {
368
262
  const bridge = getBridge();
@@ -473,24 +367,25 @@ import { HOST_METHODS as HOST_METHODS3 } from "@zaflun/lumio-protocol";
473
367
  var defaultContext = {
474
368
  extensionId: null,
475
369
  target: null,
476
- overlayId: null,
477
- interactiveUrl: null
370
+ overlayId: null
478
371
  };
372
+ function fromInit(p) {
373
+ return {
374
+ extensionId: p.extensionId ?? null,
375
+ target: p.target ?? null,
376
+ overlayId: p.installId ?? null
377
+ };
378
+ }
479
379
  function useExtensionContext() {
480
- const [context, setContext] = useState4(defaultContext);
380
+ const [context, setContext] = useState4(() => {
381
+ const init = getInitParams();
382
+ return init ? fromInit(init) : defaultContext;
383
+ });
481
384
  useEffect3(() => {
482
385
  const bridge = getBridge();
483
386
  const unsub = bridge.onNotification(
484
387
  HOST_METHODS3.LIFECYCLE_INIT,
485
- (params) => {
486
- const p = params;
487
- setContext({
488
- extensionId: p.extensionId ?? null,
489
- target: p.target ?? null,
490
- overlayId: p.installId ?? null,
491
- interactiveUrl: p.interactiveUrl ?? null
492
- });
493
- }
388
+ (params) => setContext(fromInit(params))
494
389
  );
495
390
  return unsub;
496
391
  }, []);
@@ -501,7 +396,9 @@ function useExtensionContext() {
501
396
  import { useState as useState5, useEffect as useEffect4 } from "react";
502
397
  import { HOST_METHODS as HOST_METHODS4 } from "@zaflun/lumio-protocol";
503
398
  function useLumioEvent(eventType) {
504
- const [eventData, setEventData] = useState5(null);
399
+ const [eventData, setEventData] = useState5(
400
+ null
401
+ );
505
402
  useEffect4(() => {
506
403
  const bridge = getBridge();
507
404
  const unsub = bridge.onNotification(
@@ -518,18 +415,46 @@ function useLumioEvent(eventType) {
518
415
  return eventData;
519
416
  }
520
417
 
418
+ // src/hooks/useRealtime.ts
419
+ import { useEffect as useEffect5, useRef } from "react";
420
+ import { HOST_METHODS as HOST_METHODS5 } from "@zaflun/lumio-protocol";
421
+ function useRealtime(topic, cb) {
422
+ const cbRef = useRef(cb);
423
+ cbRef.current = cb;
424
+ useEffect5(() => {
425
+ const bridge = getBridge();
426
+ return bridge.onNotification(HOST_METHODS5.SERVER_REALTIME, (params) => {
427
+ const p = params;
428
+ if (p && p.topic === topic) {
429
+ cbRef.current(p.payload);
430
+ }
431
+ });
432
+ }, [topic]);
433
+ }
434
+
521
435
  // src/hooks/useLumioConfig.ts
522
- import { useState as useState6, useEffect as useEffect5, useCallback as useCallback5 } from "react";
523
- import { HOST_METHODS as HOST_METHODS5, WORKER_METHODS as WORKER_METHODS3 } from "@zaflun/lumio-protocol";
436
+ import { useState as useState6, useEffect as useEffect6, useCallback as useCallback5 } from "react";
437
+ import { HOST_METHODS as HOST_METHODS6, WORKER_METHODS as WORKER_METHODS3 } from "@zaflun/lumio-protocol";
524
438
  function hasShim() {
525
439
  return typeof window !== "undefined" && typeof window.__lumio_config === "function";
526
440
  }
441
+ function fromInit2(p) {
442
+ return {
443
+ extension_id: p.extensionId,
444
+ install_id: p.installId,
445
+ account_id: p.accountId,
446
+ target: p.target,
447
+ permissions: p.permissions,
448
+ settings: p.settings
449
+ };
450
+ }
527
451
  function useLumioConfig() {
528
452
  const [config, setConfigState] = useState6(() => {
529
453
  if (hasShim()) return window.__lumio_config();
530
- return {};
454
+ const init = getInitParams();
455
+ return init ? fromInit2(init) : {};
531
456
  });
532
- useEffect5(() => {
457
+ useEffect6(() => {
533
458
  if (hasShim()) {
534
459
  const unsub2 = window.__lumio_on_config((newConfig) => {
535
460
  setConfigState(newConfig);
@@ -539,24 +464,14 @@ function useLumioConfig() {
539
464
  }
540
465
  const bridge = getBridge();
541
466
  const unsub = bridge.onNotification(
542
- HOST_METHODS5.LIFECYCLE_INIT,
543
- (params) => {
544
- const p = params;
545
- setConfigState({
546
- extension_id: p.extensionId,
547
- install_id: p.installId,
548
- account_id: p.accountId,
549
- target: p.target,
550
- permissions: p.permissions,
551
- settings: p.settings
552
- });
553
- }
467
+ HOST_METHODS6.LIFECYCLE_INIT,
468
+ (params) => setConfigState(fromInit2(params))
554
469
  );
555
470
  const unsubConfig = bridge.onNotification(
556
- HOST_METHODS5.CONFIG_UPDATE,
471
+ HOST_METHODS6.CONFIG_UPDATE,
557
472
  (params) => {
558
473
  const p = params;
559
- setConfigState(p);
474
+ setConfigState((prev) => ({ ...prev, ...p }));
560
475
  }
561
476
  );
562
477
  return () => {
@@ -583,8 +498,8 @@ function useLumioConfig() {
583
498
  }
584
499
 
585
500
  // src/hooks/useLumioTheme.ts
586
- import { useState as useState7, useEffect as useEffect6 } from "react";
587
- import { HOST_METHODS as HOST_METHODS6 } from "@zaflun/lumio-protocol";
501
+ import { useState as useState7, useEffect as useEffect7 } from "react";
502
+ import { HOST_METHODS as HOST_METHODS7 } from "@zaflun/lumio-protocol";
588
503
  var DEFAULT_THEME = {
589
504
  colors: {
590
505
  primary: "#6366f1",
@@ -608,11 +523,14 @@ var DEFAULT_THEME = {
608
523
  spacing: 8
609
524
  };
610
525
  function useLumioTheme() {
611
- const [theme, setTheme] = useState7(DEFAULT_THEME);
612
- useEffect6(() => {
526
+ const [theme, setTheme] = useState7(() => {
527
+ const initTheme = getInitParams()?.theme;
528
+ return initTheme ? { ...DEFAULT_THEME, ...initTheme } : DEFAULT_THEME;
529
+ });
530
+ useEffect7(() => {
613
531
  const bridge = getBridge();
614
532
  const unsubInit = bridge.onNotification(
615
- HOST_METHODS6.LIFECYCLE_INIT,
533
+ HOST_METHODS7.LIFECYCLE_INIT,
616
534
  (params) => {
617
535
  const p = params;
618
536
  if (p.theme) {
@@ -621,7 +539,7 @@ function useLumioTheme() {
621
539
  }
622
540
  );
623
541
  const unsubTheme = bridge.onNotification(
624
- HOST_METHODS6.THEME_CHANGE,
542
+ HOST_METHODS7.THEME_CHANGE,
625
543
  (params) => {
626
544
  const p = params;
627
545
  if (p.theme) {
@@ -638,27 +556,30 @@ function useLumioTheme() {
638
556
  }
639
557
 
640
558
  // src/hooks/useLumioIdentity.ts
641
- import { useState as useState8, useEffect as useEffect7 } from "react";
642
- import { HOST_METHODS as HOST_METHODS7 } from "@zaflun/lumio-protocol";
559
+ import { useState as useState8, useEffect as useEffect8 } from "react";
560
+ import { HOST_METHODS as HOST_METHODS8 } from "@zaflun/lumio-protocol";
643
561
  var defaultIdentity = {
644
562
  userId: null,
645
563
  accountId: null,
646
564
  role: null
647
565
  };
566
+ function fromInit3(p) {
567
+ return {
568
+ userId: p.userId ?? null,
569
+ accountId: p.accountId ?? null,
570
+ role: p.role ?? null
571
+ };
572
+ }
648
573
  function useLumioIdentity() {
649
- const [identity, setIdentity] = useState8(defaultIdentity);
650
- useEffect7(() => {
574
+ const [identity, setIdentity] = useState8(() => {
575
+ const init = getInitParams();
576
+ return init ? fromInit3(init) : defaultIdentity;
577
+ });
578
+ useEffect8(() => {
651
579
  const bridge = getBridge();
652
580
  const unsub = bridge.onNotification(
653
- HOST_METHODS7.LIFECYCLE_INIT,
654
- (params) => {
655
- const p = params;
656
- setIdentity({
657
- userId: p.userId ?? null,
658
- accountId: p.accountId ?? null,
659
- role: p.role ?? null
660
- });
661
- }
581
+ HOST_METHODS8.LIFECYCLE_INIT,
582
+ (params) => setIdentity(fromInit3(params))
662
583
  );
663
584
  return unsub;
664
585
  }, []);
@@ -666,27 +587,27 @@ function useLumioIdentity() {
666
587
  }
667
588
 
668
589
  // src/hooks/useLumioAction.ts
669
- import { useState as useState9, useCallback as useCallback6, useRef } from "react";
590
+ import { useState as useState9, useCallback as useCallback6 } from "react";
591
+ import { WORKER_METHODS as WORKER_METHODS4 } from "@zaflun/lumio-protocol";
670
592
  function useLumioAction(action) {
671
593
  const [data, setData] = useState9(null);
672
594
  const [isLoading, setIsLoading] = useState9(false);
673
595
  const [error, setError] = useState9(null);
674
- const actionRef = useRef(action);
675
- actionRef.current = action;
676
596
  const execute = useCallback6(
677
- (params = {}) => {
597
+ async (params = {}) => {
678
598
  setIsLoading(true);
679
599
  setError(null);
680
- const bridge = getBridge();
681
- bridge.request("action.execute", { action, params }).then((result) => {
682
- setIsLoading(false);
683
- setData(result ?? null);
600
+ const request = { action, params };
601
+ try {
602
+ const result = await getBridge().request(WORKER_METHODS4.ACTION_EXECUTE, request);
603
+ setData(result?.data ?? null);
684
604
  setError(null);
685
- }).catch((err) => {
686
- setIsLoading(false);
605
+ } catch (err) {
687
606
  setError(err instanceof Error ? err.message : "Unknown error");
688
607
  setData(null);
689
- });
608
+ } finally {
609
+ setIsLoading(false);
610
+ }
690
611
  },
691
612
  [action]
692
613
  );
@@ -695,27 +616,27 @@ function useLumioAction(action) {
695
616
 
696
617
  // src/hooks/useLumioSound.ts
697
618
  import { useCallback as useCallback7 } from "react";
698
- import { WORKER_METHODS as WORKER_METHODS4 } from "@zaflun/lumio-protocol";
619
+ import { WORKER_METHODS as WORKER_METHODS5 } from "@zaflun/lumio-protocol";
699
620
  function useLumioSound() {
700
621
  const playSound = useCallback7(async (soundId, volume) => {
701
622
  const bridge = getBridge();
702
- await bridge.request(WORKER_METHODS4.SOUND_PLAY, { sound_id: soundId, volume });
623
+ await bridge.request(WORKER_METHODS5.SOUND_PLAY, { sound_id: soundId, volume });
703
624
  }, []);
704
625
  const stopSound = useCallback7(async (soundId) => {
705
626
  const bridge = getBridge();
706
- await bridge.request(WORKER_METHODS4.SOUND_STOP, { sound_id: soundId });
627
+ await bridge.request(WORKER_METHODS5.SOUND_STOP, { sound_id: soundId });
707
628
  }, []);
708
629
  const getSounds = useCallback7(async () => {
709
630
  const bridge = getBridge();
710
- const result = await bridge.request(WORKER_METHODS4.SOUND_LIST, {});
631
+ const result = await bridge.request(WORKER_METHODS5.SOUND_LIST, {});
711
632
  return result ?? [];
712
633
  }, []);
713
634
  return { playSound, stopSound, getSounds };
714
635
  }
715
636
 
716
637
  // src/hooks/useQuery.ts
717
- import { useState as useState10, useEffect as useEffect8, useCallback as useCallback8, useRef as useRef2 } from "react";
718
- import { WORKER_METHODS as WORKER_METHODS5 } from "@zaflun/lumio-protocol";
638
+ import { useState as useState10, useEffect as useEffect9, useCallback as useCallback8, useRef as useRef2 } from "react";
639
+ import { WORKER_METHODS as WORKER_METHODS6 } from "@zaflun/lumio-protocol";
719
640
  function useQuery(functionName, params) {
720
641
  const [data, setData] = useState10(null);
721
642
  const [isLoading, setIsLoading] = useState10(false);
@@ -726,7 +647,7 @@ function useQuery(functionName, params) {
726
647
  setIsLoading(true);
727
648
  setError(null);
728
649
  const bridge = getBridge();
729
- bridge.request(WORKER_METHODS5.SERVER_QUERY, { fn: functionName, params: params ?? {} }).then((result) => {
650
+ bridge.request(WORKER_METHODS6.SERVER_QUERY, { fn: functionName, params: params ?? {} }).then((result) => {
730
651
  setIsLoading(false);
731
652
  setData(result ?? null);
732
653
  setError(null);
@@ -736,7 +657,7 @@ function useQuery(functionName, params) {
736
657
  setData(null);
737
658
  });
738
659
  }, [functionName, params]);
739
- useEffect8(() => {
660
+ useEffect9(() => {
740
661
  sendQuery();
741
662
  }, [sendQuery]);
742
663
  return { data, isLoading, error, refetch: sendQuery };
@@ -744,7 +665,7 @@ function useQuery(functionName, params) {
744
665
 
745
666
  // src/hooks/useMutation.ts
746
667
  import { useState as useState11, useCallback as useCallback9 } from "react";
747
- import { WORKER_METHODS as WORKER_METHODS6 } from "@zaflun/lumio-protocol";
668
+ import { WORKER_METHODS as WORKER_METHODS7 } from "@zaflun/lumio-protocol";
748
669
  function useMutation(functionName) {
749
670
  const [data, setData] = useState11(null);
750
671
  const [isLoading, setIsLoading] = useState11(false);
@@ -754,7 +675,7 @@ function useMutation(functionName) {
754
675
  setIsLoading(true);
755
676
  setError(null);
756
677
  const bridge = getBridge();
757
- return bridge.request(WORKER_METHODS6.SERVER_MUTATION, { fn: functionName, params }).then((result) => {
678
+ return bridge.request(WORKER_METHODS7.SERVER_MUTATION, { fn: functionName, params }).then((result) => {
758
679
  setIsLoading(false);
759
680
  setData(result ?? null);
760
681
  setError(null);
@@ -869,7 +790,7 @@ function getPreset(name) {
869
790
  }
870
791
 
871
792
  // src/animation/Animated.tsx
872
- import React31, { useEffect as useEffect9, useRef as useRef3, useState as useState12 } from "react";
793
+ import React31, { useEffect as useEffect10, useRef as useRef3, useState as useState12 } from "react";
873
794
  function resolveAnimation(name) {
874
795
  if (name === void 0) return void 0;
875
796
  try {
@@ -899,7 +820,7 @@ function Animated({
899
820
  timerRef.current = null;
900
821
  }
901
822
  }
902
- useEffect9(() => {
823
+ useEffect10(() => {
903
824
  const prevVisible = prevVisibleRef.current;
904
825
  prevVisibleRef.current = visible;
905
826
  if (visible && !prevVisible) {
@@ -910,7 +831,7 @@ function Animated({
910
831
  setPhase("out");
911
832
  }
912
833
  }, [visible]);
913
- useEffect9(() => {
834
+ useEffect10(() => {
914
835
  if (phase === "in") {
915
836
  const totalDuration = duration + delay;
916
837
  if (loopProp !== void 0) {
@@ -1007,12 +928,21 @@ function formatFromPath(path) {
1007
928
  if (lower.endsWith(".woff")) return "woff";
1008
929
  return "truetype";
1009
930
  }
931
+ var FAMILY_RE = /^[\w -]+$/;
932
+ function escapeCssUrl(src) {
933
+ return src.replace(/[\\"\n\r\f]/g, (c) => `\\${c}`);
934
+ }
1010
935
  function buildFontFace(family, src, display, weight, style) {
936
+ if (!FAMILY_RE.test(family)) {
937
+ throw new Error(
938
+ `Invalid font family "${family}": only letters, digits, spaces, "-" and "_" are allowed.`
939
+ );
940
+ }
1011
941
  const format = formatFromPath(src);
1012
942
  const lines = [
1013
943
  "@font-face {",
1014
944
  ` font-family: "${family}";`,
1015
- ` src: url("${src}") format("${format}");`,
945
+ ` src: url("${escapeCssUrl(src)}") format("${format}");`,
1016
946
  ` font-display: ${display};`
1017
947
  ];
1018
948
  if (weight !== void 0) {
@@ -1105,5 +1035,6 @@ export {
1105
1035
  useLumioTheme,
1106
1036
  useMutation,
1107
1037
  useQuery,
1038
+ useRealtime,
1108
1039
  useVisionState
1109
1040
  };