@appstrata/react 0.1.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.
@@ -0,0 +1,56 @@
1
+ /**
2
+ * AppStrataProvider - React Context provider for AppStrata.
3
+ *
4
+ * Wraps the app and provides the SignagePlayer instance and ContextStore
5
+ * to all child components via React Context.
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * import { AppStrataProvider } from "@appstrata/react";
10
+ *
11
+ * root.render(
12
+ * <AppStrataProvider>
13
+ * <App />
14
+ * </AppStrataProvider>
15
+ * );
16
+ * ```
17
+ */
18
+ import type { ReactNode } from "react";
19
+ import type { SignagePlayer } from "@appstrata/core";
20
+ import { ContextStore } from "./store.js";
21
+ /**
22
+ * Internal context value shape.
23
+ * @internal
24
+ */
25
+ export interface AppStrataContextValue {
26
+ player: SignagePlayer;
27
+ store: ContextStore;
28
+ }
29
+ /**
30
+ * React Context for AppStrata.
31
+ * @internal
32
+ */
33
+ export declare const AppStrataContext: import("react").Context<AppStrataContextValue | null>;
34
+ /**
35
+ * Props for AppStrataProvider.
36
+ */
37
+ export interface AppStrataProviderProps {
38
+ children: ReactNode;
39
+ /**
40
+ * Optional custom SignagePlayer instance.
41
+ * If not provided, uses getPlayer() from @appstrata/web (default singleton).
42
+ * Useful for testing or providing a custom player implementation.
43
+ */
44
+ player?: SignagePlayer;
45
+ }
46
+ /**
47
+ * AppStrataProvider - Required wrapper for all AppStrata React hooks.
48
+ *
49
+ * Creates a ContextStore and wires the player's onInit and onContextChange
50
+ * events to update the store. All hooks read from this store.
51
+ *
52
+ * The player instance is obtained via getPlayer() which returns a singleton,
53
+ * so no useState is needed.
54
+ */
55
+ export declare function AppStrataProvider({ children, player: playerProp }: AppStrataProviderProps): import("react/jsx-runtime").JSX.Element;
56
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,iBAAiB,CAAC;AAEjE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,YAAY,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,uDAAoD,CAAC;AAElF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE,sBAAsB,2CA4BzF"}
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * AppStrataProvider - React Context provider for AppStrata.
4
+ *
5
+ * Wraps the app and provides the SignagePlayer instance and ContextStore
6
+ * to all child components via React Context.
7
+ *
8
+ * Usage:
9
+ * ```tsx
10
+ * import { AppStrataProvider } from "@appstrata/react";
11
+ *
12
+ * root.render(
13
+ * <AppStrataProvider>
14
+ * <App />
15
+ * </AppStrataProvider>
16
+ * );
17
+ * ```
18
+ */
19
+ import { createContext, useEffect, useMemo } from "react";
20
+ import { getPlayer } from "@appstrata/web";
21
+ import { ContextStore } from "./store.js";
22
+ /**
23
+ * React Context for AppStrata.
24
+ * @internal
25
+ */
26
+ export const AppStrataContext = createContext(null);
27
+ /**
28
+ * AppStrataProvider - Required wrapper for all AppStrata React hooks.
29
+ *
30
+ * Creates a ContextStore and wires the player's onInit and onContextChange
31
+ * events to update the store. All hooks read from this store.
32
+ *
33
+ * The player instance is obtained via getPlayer() which returns a singleton,
34
+ * so no useState is needed.
35
+ */
36
+ export function AppStrataProvider({ children, player: playerProp }) {
37
+ // Use provided player or default to singleton from @appstrata/web
38
+ const player = playerProp ?? getPlayer();
39
+ // Create store once - stable reference
40
+ const store = useMemo(() => new ContextStore(), []);
41
+ useEffect(() => {
42
+ // Wire player events to store
43
+ player.onInit((context) => {
44
+ store.setContext(context);
45
+ });
46
+ player.onContextChange?.((context) => {
47
+ store.setContext(context);
48
+ });
49
+ }, [player, store]);
50
+ const value = useMemo(() => ({ player, store }), [player, store]);
51
+ return (_jsx(AppStrataContext.Provider, { value: value, children: children }));
52
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ContextStore - Unified store for AppContext with granular subscriptions.
3
+ *
4
+ * Receives the complete AppContext on every update (from onInit and onContextChange)
5
+ * and uses deep comparison to determine which parts actually changed.
6
+ * This enables granular re-renders: components using useConfig() only re-render
7
+ * when config changes, not when device info changes.
8
+ *
9
+ * Compatible with React's useSyncExternalStore API.
10
+ *
11
+ * @internal
12
+ */
13
+ import type { AppContext, Resource, DeviceInfo } from "@appstrata/core";
14
+ type Listener = () => void;
15
+ export declare class ContextStore {
16
+ private context;
17
+ private listeners;
18
+ private configListeners;
19
+ private resourcesListeners;
20
+ private deviceListeners;
21
+ getSnapshot: () => AppContext | null;
22
+ getConfigSnapshot: () => Record<string, unknown> | null;
23
+ getResourcesSnapshot: () => Resource[] | null;
24
+ getDeviceSnapshot: () => DeviceInfo | null;
25
+ subscribe: (listener: Listener) => (() => void);
26
+ subscribeConfig: (listener: Listener) => (() => void);
27
+ subscribeResources: (listener: Listener) => (() => void);
28
+ subscribeDevice: (listener: Listener) => (() => void);
29
+ setContext(next: AppContext): void;
30
+ private notify;
31
+ }
32
+ export {};
33
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGxE,KAAK,QAAQ,GAAG,MAAM,IAAI,CAAC;AAE3B,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,eAAe,CAAuB;IAM9C,WAAW,QAAO,UAAU,GAAG,IAAI,CAAiB;IACpD,iBAAiB,QAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAiC;IACvF,oBAAoB,QAAO,QAAQ,EAAE,GAAG,IAAI,CAAoC;IAChF,iBAAiB,QAAO,UAAU,GAAG,IAAI,CAAiC;IAM1E,SAAS,GAAI,UAAU,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAG5C;IAEF,eAAe,GAAI,UAAU,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAGlD;IAEF,kBAAkB,GAAI,UAAU,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAGrD;IAEF,eAAe,GAAI,UAAU,QAAQ,KAAG,CAAC,MAAM,IAAI,CAAC,CAGlD;IAMF,UAAU,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAmBlC,OAAO,CAAC,MAAM;CAKf"}
package/dist/store.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * ContextStore - Unified store for AppContext with granular subscriptions.
3
+ *
4
+ * Receives the complete AppContext on every update (from onInit and onContextChange)
5
+ * and uses deep comparison to determine which parts actually changed.
6
+ * This enables granular re-renders: components using useConfig() only re-render
7
+ * when config changes, not when device info changes.
8
+ *
9
+ * Compatible with React's useSyncExternalStore API.
10
+ *
11
+ * @internal
12
+ */
13
+ import { deepEqual } from "./deep-equal.js";
14
+ export class ContextStore {
15
+ constructor() {
16
+ this.context = null;
17
+ this.listeners = new Set();
18
+ this.configListeners = new Set();
19
+ this.resourcesListeners = new Set();
20
+ this.deviceListeners = new Set();
21
+ // ═══════════════════════════════════════════════════════════════════
22
+ // SNAPSHOT GETTERS (for useSyncExternalStore)
23
+ // ═══════════════════════════════════════════════════════════════════
24
+ this.getSnapshot = () => this.context;
25
+ this.getConfigSnapshot = () => this.context?.config ?? null;
26
+ this.getResourcesSnapshot = () => this.context?.resources ?? null;
27
+ this.getDeviceSnapshot = () => this.context?.device ?? null;
28
+ // ═══════════════════════════════════════════════════════════════════
29
+ // SUBSCRIBE METHODS (for useSyncExternalStore)
30
+ // ═══════════════════════════════════════════════════════════════════
31
+ this.subscribe = (listener) => {
32
+ this.listeners.add(listener);
33
+ return () => this.listeners.delete(listener);
34
+ };
35
+ this.subscribeConfig = (listener) => {
36
+ this.configListeners.add(listener);
37
+ return () => this.configListeners.delete(listener);
38
+ };
39
+ this.subscribeResources = (listener) => {
40
+ this.resourcesListeners.add(listener);
41
+ return () => this.resourcesListeners.delete(listener);
42
+ };
43
+ this.subscribeDevice = (listener) => {
44
+ this.deviceListeners.add(listener);
45
+ return () => this.deviceListeners.delete(listener);
46
+ };
47
+ }
48
+ // ═══════════════════════════════════════════════════════════════════
49
+ // CONTEXT UPDATE
50
+ // ═══════════════════════════════════════════════════════════════════
51
+ setContext(next) {
52
+ const prev = this.context;
53
+ this.context = next;
54
+ // Always notify full context subscribers
55
+ this.notify(this.listeners);
56
+ // Only notify granular subscribers if their data actually changed
57
+ if (!deepEqual(next.config, prev?.config)) {
58
+ this.notify(this.configListeners);
59
+ }
60
+ if (!deepEqual(next.resources, prev?.resources)) {
61
+ this.notify(this.resourcesListeners);
62
+ }
63
+ if (!deepEqual(next.device, prev?.device)) {
64
+ this.notify(this.deviceListeners);
65
+ }
66
+ }
67
+ notify(listeners) {
68
+ for (const listener of listeners) {
69
+ listener();
70
+ }
71
+ }
72
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * useInteraction - React hook for the AppStrata Interaction API.
3
+ *
4
+ * Event subscription hook that auto-subscribes on mount and
5
+ * unsubscribes on unmount. No-op if the "interaction" capability
6
+ * is unsupported.
7
+ */
8
+ import type { InteractionEvent } from "@appstrata/core";
9
+ /**
10
+ * Return type for useInteraction.
11
+ */
12
+ export interface UseInteractionResult {
13
+ /** Whether the player supports the "interaction" capability (null = not yet initialized) */
14
+ supported: boolean | null;
15
+ }
16
+ /**
17
+ * Subscribe to interaction events (hardware buttons, remote controls, sensors).
18
+ *
19
+ * Auto-subscribes on mount, auto-unsubscribes on unmount.
20
+ * No-op if the "interaction" capability is unsupported.
21
+ *
22
+ * @param handler - Event handler called for each interaction event
23
+ * @returns Object with `supported` boolean
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * const { supported } = useInteraction((event) => {
28
+ * if (event.type === "key" && event.key === "enter") {
29
+ * handleSelect();
30
+ * }
31
+ * });
32
+ * ```
33
+ */
34
+ export declare function useInteraction(handler: (event: InteractionEvent) => void): UseInteractionResult;
35
+ //# sourceMappingURL=use-interaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-interaction.d.ts","sourceRoot":"","sources":["../src/use-interaction.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAMxD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,4FAA4F;IAC5F,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;CAC3B;AAMD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,CAAC,KAAK,EAAE,gBAAgB,KAAK,IAAI,GACzC,oBAAoB,CAmBtB"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * useInteraction - React hook for the AppStrata Interaction API.
3
+ *
4
+ * Event subscription hook that auto-subscribes on mount and
5
+ * unsubscribes on unmount. No-op if the "interaction" capability
6
+ * is unsupported.
7
+ */
8
+ import { useEffect, useRef } from "react";
9
+ import { usePlayer, useCapability } from "./hooks.js";
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // HOOK
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ /**
14
+ * Subscribe to interaction events (hardware buttons, remote controls, sensors).
15
+ *
16
+ * Auto-subscribes on mount, auto-unsubscribes on unmount.
17
+ * No-op if the "interaction" capability is unsupported.
18
+ *
19
+ * @param handler - Event handler called for each interaction event
20
+ * @returns Object with `supported` boolean
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * const { supported } = useInteraction((event) => {
25
+ * if (event.type === "key" && event.key === "enter") {
26
+ * handleSelect();
27
+ * }
28
+ * });
29
+ * ```
30
+ */
31
+ export function useInteraction(handler) {
32
+ const player = usePlayer();
33
+ const supported = useCapability("interaction");
34
+ // Use ref to always call the latest handler without re-subscribing
35
+ const handlerRef = useRef(handler);
36
+ handlerRef.current = handler;
37
+ useEffect(() => {
38
+ if (!supported || !player.interaction)
39
+ return;
40
+ const unsubscribe = player.interaction.onEvent((event) => {
41
+ handlerRef.current(event);
42
+ });
43
+ return unsubscribe;
44
+ }, [supported, player]);
45
+ return { supported };
46
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * useMediaCache - React hook for the AppStrata Media Cache API.
3
+ *
4
+ * Data-fetching hook that wraps player.mediaCache.download.
5
+ * When the capability is unsupported, automatically falls back to using
6
+ * the original URL as localPath (auto-passthrough), so the app never
7
+ * needs to branch -- file.localPath always works.
8
+ */
9
+ import type { MediaFile } from "@appstrata/core";
10
+ /**
11
+ * Options for useMediaCache.
12
+ */
13
+ export interface UseMediaCacheOptions {
14
+ /**
15
+ * Custom fallback when mediaCache is unsupported.
16
+ * If not provided, auto-passthrough is used (original URL as localPath).
17
+ */
18
+ fallback?: (url: string) => Promise<MediaFile>;
19
+ }
20
+ /**
21
+ * Return type for useMediaCache.
22
+ */
23
+ export interface UseMediaCacheResult {
24
+ /** Cached file info, or auto-passthrough file when unsupported */
25
+ file: MediaFile | null;
26
+ /** Whether a download is in progress */
27
+ loading: boolean;
28
+ /** Error from the last download attempt */
29
+ error: Error | null;
30
+ /** Whether the player supports the "mediaCache" capability (null = not yet initialized) */
31
+ supported: boolean | null;
32
+ /** Manually trigger a re-download */
33
+ refetch: () => Promise<void>;
34
+ }
35
+ /**
36
+ * Download and cache a media file via the player's Media Cache API.
37
+ *
38
+ * Downloads on mount and when the URL changes. Pass `url: null` to skip.
39
+ *
40
+ * When the capability is unsupported:
41
+ * - Without custom fallback: auto-passthrough (original URL as localPath)
42
+ * - With custom fallback: calls the fallback function
43
+ *
44
+ * @param url - URL of media file, or null to skip
45
+ * @param options - Optional custom fallback
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * // Most convenient: just works everywhere
50
+ * const { file, loading } = useMediaCache("https://example.com/hero.mp4");
51
+ * // file.localPath is always usable
52
+ * if (file) return <video src={file.localPath} />;
53
+ * ```
54
+ */
55
+ export declare function useMediaCache(url: string | null, options?: UseMediaCacheOptions): UseMediaCacheResult;
56
+ //# sourceMappingURL=use-media-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-media-cache.d.ts","sourceRoot":"","sources":["../src/use-media-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAMjD;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;CAChD;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,IAAI,EAAE,SAAS,GAAG,IAAI,CAAC;IACvB,wCAAwC;IACxC,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,2FAA2F;IAC3F,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,qCAAqC;IACrC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAiCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CA0CrB"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * useMediaCache - React hook for the AppStrata Media Cache API.
3
+ *
4
+ * Data-fetching hook that wraps player.mediaCache.download.
5
+ * When the capability is unsupported, automatically falls back to using
6
+ * the original URL as localPath (auto-passthrough), so the app never
7
+ * needs to branch -- file.localPath always works.
8
+ */
9
+ import { useState, useEffect, useCallback, useRef } from "react";
10
+ import { usePlayer, useCapability } from "./hooks.js";
11
+ // ═══════════════════════════════════════════════════════════════════════════
12
+ // HELPERS
13
+ // ═══════════════════════════════════════════════════════════════════════════
14
+ /**
15
+ * Create a passthrough MediaFile using the original URL as localPath.
16
+ * Used when mediaCache is unsupported and no custom fallback is provided.
17
+ */
18
+ function createPassthroughFile(url) {
19
+ // Extract filename from URL, or use a fallback
20
+ let fileName;
21
+ try {
22
+ const urlObj = new URL(url);
23
+ fileName = urlObj.pathname.split("/").pop() || "media";
24
+ }
25
+ catch {
26
+ fileName = "media";
27
+ }
28
+ return {
29
+ md5: "",
30
+ localPath: url,
31
+ fileName,
32
+ size: 0,
33
+ status: "cached",
34
+ };
35
+ }
36
+ // ═══════════════════════════════════════════════════════════════════════════
37
+ // HOOK
38
+ // ═══════════════════════════════════════════════════════════════════════════
39
+ /**
40
+ * Download and cache a media file via the player's Media Cache API.
41
+ *
42
+ * Downloads on mount and when the URL changes. Pass `url: null` to skip.
43
+ *
44
+ * When the capability is unsupported:
45
+ * - Without custom fallback: auto-passthrough (original URL as localPath)
46
+ * - With custom fallback: calls the fallback function
47
+ *
48
+ * @param url - URL of media file, or null to skip
49
+ * @param options - Optional custom fallback
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * // Most convenient: just works everywhere
54
+ * const { file, loading } = useMediaCache("https://example.com/hero.mp4");
55
+ * // file.localPath is always usable
56
+ * if (file) return <video src={file.localPath} />;
57
+ * ```
58
+ */
59
+ export function useMediaCache(url, options) {
60
+ const player = usePlayer();
61
+ const supported = useCapability("mediaCache");
62
+ const [file, setFile] = useState(null);
63
+ const [loading, setLoading] = useState(false);
64
+ const [error, setError] = useState(null);
65
+ const optionsRef = useRef(options);
66
+ optionsRef.current = options;
67
+ const doDownload = useCallback(async () => {
68
+ if (!url)
69
+ return;
70
+ // Wait for player to initialize before resolving
71
+ if (supported === null)
72
+ return;
73
+ setLoading(true);
74
+ setError(null);
75
+ try {
76
+ if (supported && player.mediaCache) {
77
+ const result = await player.mediaCache.download(url);
78
+ setFile(result);
79
+ }
80
+ else if (optionsRef.current?.fallback) {
81
+ const result = await optionsRef.current.fallback(url);
82
+ setFile(result);
83
+ }
84
+ else {
85
+ // Auto-passthrough: use original URL as localPath
86
+ setFile(createPassthroughFile(url));
87
+ }
88
+ }
89
+ catch (err) {
90
+ setError(err instanceof Error ? err : new Error(String(err)));
91
+ }
92
+ finally {
93
+ setLoading(false);
94
+ }
95
+ }, [url, supported, player]);
96
+ useEffect(() => {
97
+ doDownload();
98
+ }, [doDownload]);
99
+ return { file, loading, error, supported, refetch: doDownload };
100
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * useProxy - React hook for the AppStrata Proxy API.
3
+ *
4
+ * Data-fetching hook that wraps player.proxy.fetch with React state management.
5
+ * Supports the full ProxyRequest options (method, headers, body, cache) and
6
+ * an optional fallback function for when the proxy capability is unsupported.
7
+ */
8
+ import type { ProxyRequest } from "@appstrata/core";
9
+ /**
10
+ * Options for useProxy, extending ProxyRequest options.
11
+ */
12
+ export interface UseProxyOptions<T> {
13
+ /** HTTP method (default: GET) */
14
+ method?: ProxyRequest["method"];
15
+ /** Request headers */
16
+ headers?: ProxyRequest["headers"];
17
+ /** Request body (for POST/PUT) */
18
+ body?: ProxyRequest["body"];
19
+ /** Caching options */
20
+ cache?: ProxyRequest["cache"];
21
+ /**
22
+ * Fallback function called when the proxy capability is unsupported.
23
+ * Receives the full ProxyRequest so fallback logic can replicate the request.
24
+ */
25
+ fallback?: (request: ProxyRequest) => Promise<T>;
26
+ }
27
+ /**
28
+ * Return type for useProxy.
29
+ */
30
+ export interface UseProxyResult<T> {
31
+ /** Fetched data (parsed response), null while loading or if unsupported without fallback */
32
+ data: T | null;
33
+ /** Whether a fetch is in progress */
34
+ loading: boolean;
35
+ /** Error from the last fetch attempt */
36
+ error: Error | null;
37
+ /** Whether the player supports the "proxy" capability (null = not yet initialized) */
38
+ supported: boolean | null;
39
+ /** Manually trigger a refetch */
40
+ refetch: () => Promise<void>;
41
+ }
42
+ /**
43
+ * Fetch data through the player's proxy API.
44
+ *
45
+ * Fetches on mount and when the URL changes. Pass `url: null` to skip fetching.
46
+ *
47
+ * @param url - URL to fetch, or null to skip
48
+ * @param options - Proxy request options and optional fallback
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * const { data, loading, error, supported } = useProxy<WeatherData>(
53
+ * "https://api.weather.com/current",
54
+ * {
55
+ * method: "GET",
56
+ * headers: { "Accept": "application/json" },
57
+ * cache: { maxAge: 300, staleIfError: true },
58
+ * fallback: (req) => fetch(req.url, {
59
+ * method: req.method,
60
+ * headers: req.headers,
61
+ * body: req.body,
62
+ * }).then(r => r.json()),
63
+ * }
64
+ * );
65
+ * ```
66
+ */
67
+ export declare function useProxy<T = unknown>(url: string | null, options?: UseProxyOptions<T>): UseProxyResult<T>;
68
+ //# sourceMappingURL=use-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-proxy.d.ts","sourceRoot":"","sources":["../src/use-proxy.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMpD;;GAEG;AACH,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,iCAAiC;IACjC,MAAM,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,sBAAsB;IACtB,OAAO,CAAC,EAAE,YAAY,CAAC,SAAS,CAAC,CAAC;IAClC,kCAAkC;IAClC,IAAI,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC5B,sBAAsB;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC;IAC/B,4FAA4F;IAC5F,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,sFAAsF;IACtF,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAMD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClC,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,OAAO,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GAC3B,cAAc,CAAC,CAAC,CAAC,CAsDnB"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * useProxy - React hook for the AppStrata Proxy API.
3
+ *
4
+ * Data-fetching hook that wraps player.proxy.fetch with React state management.
5
+ * Supports the full ProxyRequest options (method, headers, body, cache) and
6
+ * an optional fallback function for when the proxy capability is unsupported.
7
+ */
8
+ import { useState, useEffect, useCallback, useRef } from "react";
9
+ import { usePlayer, useCapability } from "./hooks.js";
10
+ // ═══════════════════════════════════════════════════════════════════════════
11
+ // HOOK
12
+ // ═══════════════════════════════════════════════════════════════════════════
13
+ /**
14
+ * Fetch data through the player's proxy API.
15
+ *
16
+ * Fetches on mount and when the URL changes. Pass `url: null` to skip fetching.
17
+ *
18
+ * @param url - URL to fetch, or null to skip
19
+ * @param options - Proxy request options and optional fallback
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * const { data, loading, error, supported } = useProxy<WeatherData>(
24
+ * "https://api.weather.com/current",
25
+ * {
26
+ * method: "GET",
27
+ * headers: { "Accept": "application/json" },
28
+ * cache: { maxAge: 300, staleIfError: true },
29
+ * fallback: (req) => fetch(req.url, {
30
+ * method: req.method,
31
+ * headers: req.headers,
32
+ * body: req.body,
33
+ * }).then(r => r.json()),
34
+ * }
35
+ * );
36
+ * ```
37
+ */
38
+ export function useProxy(url, options) {
39
+ const player = usePlayer();
40
+ const supported = useCapability("proxy");
41
+ const [data, setData] = useState(null);
42
+ const [loading, setLoading] = useState(false);
43
+ const [error, setError] = useState(null);
44
+ // Keep options in a ref to avoid re-fetching on every render
45
+ // when options object is recreated
46
+ const optionsRef = useRef(options);
47
+ optionsRef.current = options;
48
+ const doFetch = useCallback(async () => {
49
+ if (!url)
50
+ return;
51
+ // Wait for player to initialize before resolving
52
+ if (supported === null)
53
+ return;
54
+ const currentOptions = optionsRef.current;
55
+ const request = {
56
+ url,
57
+ method: currentOptions?.method,
58
+ headers: currentOptions?.headers,
59
+ body: currentOptions?.body,
60
+ cache: currentOptions?.cache,
61
+ };
62
+ setLoading(true);
63
+ setError(null);
64
+ try {
65
+ if (supported && player.proxy) {
66
+ const response = await player.proxy.fetch(request);
67
+ const json = await response.json();
68
+ setData(json);
69
+ }
70
+ else if (currentOptions?.fallback) {
71
+ const result = await currentOptions.fallback(request);
72
+ setData(result);
73
+ }
74
+ else {
75
+ // Genuinely unsupported, no fallback: leave data as null
76
+ setData(null);
77
+ }
78
+ }
79
+ catch (err) {
80
+ setError(err instanceof Error ? err : new Error(String(err)));
81
+ }
82
+ finally {
83
+ setLoading(false);
84
+ }
85
+ }, [url, supported, player]);
86
+ useEffect(() => {
87
+ doFetch();
88
+ }, [doFetch]);
89
+ return { data, loading, error, supported, refetch: doFetch };
90
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * useStatic - React hook for the AppStrata Static API.
3
+ *
4
+ * Convenience hook that returns capability-checked methods.
5
+ * All methods are safe no-ops if the "static" capability is unsupported.
6
+ */
7
+ /**
8
+ * Return type for useStatic.
9
+ */
10
+ export interface UseStaticResult {
11
+ /** Whether the player supports the "static" capability (null = not yet initialized) */
12
+ supported: boolean | null;
13
+ /** Signal that app is ready for static capture */
14
+ markStatic: () => void;
15
+ /** Signal semi-static with refresh interval */
16
+ markSemiStatic: (refreshIntervalSeconds: number) => void;
17
+ /** Disable static mode */
18
+ disableStatic: () => void;
19
+ }
20
+ /**
21
+ * Access the Static API with safe no-op methods when unsupported.
22
+ *
23
+ * Eliminates the need for `player.static?.markStatic()` optional chaining.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * const { markStatic, markSemiStatic, supported } = useStatic();
28
+ *
29
+ * useEffect(() => {
30
+ * if (isStaticContent) markStatic();
31
+ * else markSemiStatic(3600); // refresh hourly
32
+ * }, [isStaticContent]);
33
+ * ```
34
+ */
35
+ export declare function useStatic(): UseStaticResult;
36
+ //# sourceMappingURL=use-static.d.ts.map