@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.
- package/README.md +863 -0
- package/dist/deep-equal.d.ts +10 -0
- package/dist/deep-equal.d.ts.map +1 -0
- package/dist/deep-equal.js +28 -0
- package/dist/hooks.d.ts +207 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +321 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/provider.d.ts +56 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +52 -0
- package/dist/store.d.ts +33 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +72 -0
- package/dist/use-interaction.d.ts +35 -0
- package/dist/use-interaction.d.ts.map +1 -0
- package/dist/use-interaction.js +46 -0
- package/dist/use-media-cache.d.ts +56 -0
- package/dist/use-media-cache.d.ts.map +1 -0
- package/dist/use-media-cache.js +100 -0
- package/dist/use-proxy.d.ts +68 -0
- package/dist/use-proxy.d.ts.map +1 -0
- package/dist/use-proxy.js +90 -0
- package/dist/use-static.d.ts +36 -0
- package/dist/use-static.d.ts.map +1 -0
- package/dist/use-static.js +45 -0
- package/dist/use-storage.d.ts +72 -0
- package/dist/use-storage.d.ts.map +1 -0
- package/dist/use-storage.js +161 -0
- package/package.json +58 -0
|
@@ -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"}
|
package/dist/provider.js
ADDED
|
@@ -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
|
+
}
|
package/dist/store.d.ts
ADDED
|
@@ -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
|