@bravostudioai/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/bin/encore-lib.js +3 -0
- package/dist/_virtual/_commonjsHelpers.js +7 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/_virtual/main.js +8 -0
- package/dist/_virtual/main.js.map +1 -0
- package/dist/_virtual/main2.js +5 -0
- package/dist/_virtual/main2.js.map +1 -0
- package/dist/app.js +9 -0
- package/dist/app.js.map +1 -0
- package/dist/cli/commands/download.js +82 -0
- package/dist/cli/commands/download.js.map +1 -0
- package/dist/cli/commands/generate.js +1526 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/components/DynamicComponent.js +24 -0
- package/dist/components/DynamicComponent.js.map +1 -0
- package/dist/components/EncoreApp.js +259 -0
- package/dist/components/EncoreApp.js.map +1 -0
- package/dist/components/EncoreErrorBoundary.js +33 -0
- package/dist/components/EncoreErrorBoundary.js.map +1 -0
- package/dist/components/EncoreLoadingFallback.js +20 -0
- package/dist/components/EncoreLoadingFallback.js.map +1 -0
- package/dist/components.js +1454 -0
- package/dist/components.js.map +1 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/contexts/EncoreActionContext.js +6 -0
- package/dist/contexts/EncoreActionContext.js.map +1 -0
- package/dist/contexts/EncoreAppContext.js +9 -0
- package/dist/contexts/EncoreAppContext.js.map +1 -0
- package/dist/contexts/EncoreBindingContext.js +6 -0
- package/dist/contexts/EncoreBindingContext.js.map +1 -0
- package/dist/contexts/EncoreComponentIdContext.js +8 -0
- package/dist/contexts/EncoreComponentIdContext.js.map +1 -0
- package/dist/contexts/EncoreRepeatingContainerContext.js +6 -0
- package/dist/contexts/EncoreRepeatingContainerContext.js.map +1 -0
- package/dist/hooks/usePusherUpdates.js +60 -0
- package/dist/hooks/usePusherUpdates.js.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/dynamicModules.js +132 -0
- package/dist/lib/dynamicModules.js.map +1 -0
- package/dist/lib/fetcher.js +58 -0
- package/dist/lib/fetcher.js.map +1 -0
- package/dist/lib/localMode.js +21 -0
- package/dist/lib/localMode.js.map +1 -0
- package/dist/lib/packages.js +18 -0
- package/dist/lib/packages.js.map +1 -0
- package/dist/node_modules/dotenv/lib/main.js +198 -0
- package/dist/node_modules/dotenv/lib/main.js.map +1 -0
- package/dist/node_modules/dotenv/package.json.js +8 -0
- package/dist/node_modules/dotenv/package.json.js.map +1 -0
- package/dist/packages/encore-lib/constants.js +6 -0
- package/dist/packages/encore-lib/constants.js.map +1 -0
- package/dist/src/app.d.ts +5 -0
- package/dist/src/app.d.ts.map +1 -0
- package/dist/src/cli/commands/download.d.ts +2 -0
- package/dist/src/cli/commands/download.d.ts.map +1 -0
- package/dist/src/cli/commands/generate.d.ts +2 -0
- package/dist/src/cli/commands/generate.d.ts.map +1 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.d.ts.map +1 -0
- package/dist/src/components/DynamicComponent.d.ts +12 -0
- package/dist/src/components/DynamicComponent.d.ts.map +1 -0
- package/dist/src/components/EncoreApp.d.ts +27 -0
- package/dist/src/components/EncoreApp.d.ts.map +1 -0
- package/dist/src/components/EncoreErrorBoundary.d.ts +17 -0
- package/dist/src/components/EncoreErrorBoundary.d.ts.map +1 -0
- package/dist/src/components/EncoreLoadingFallback.d.ts +4 -0
- package/dist/src/components/EncoreLoadingFallback.d.ts.map +1 -0
- package/dist/src/components.d.ts +4 -0
- package/dist/src/components.d.ts.map +1 -0
- package/dist/src/contexts/EncoreActionContext.d.ts +13 -0
- package/dist/src/contexts/EncoreActionContext.d.ts.map +1 -0
- package/dist/src/contexts/EncoreAppContext.d.ts +8 -0
- package/dist/src/contexts/EncoreAppContext.d.ts.map +1 -0
- package/dist/src/contexts/EncoreBindingContext.d.ts +5 -0
- package/dist/src/contexts/EncoreBindingContext.d.ts.map +1 -0
- package/dist/src/contexts/EncoreComponentIdContext.d.ts +8 -0
- package/dist/src/contexts/EncoreComponentIdContext.d.ts.map +1 -0
- package/dist/src/contexts/EncoreRepeatingContainerContext.d.ts +21 -0
- package/dist/src/contexts/EncoreRepeatingContainerContext.d.ts.map +1 -0
- package/dist/src/hooks/useAuthRedirect.d.ts +3 -0
- package/dist/src/hooks/useAuthRedirect.d.ts.map +1 -0
- package/dist/src/hooks/usePusherUpdates.d.ts +18 -0
- package/dist/src/hooks/usePusherUpdates.d.ts.map +1 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/lib/dynamicModules.d.ts +8 -0
- package/dist/src/lib/dynamicModules.d.ts.map +1 -0
- package/dist/src/lib/fetcher.d.ts +5 -0
- package/dist/src/lib/fetcher.d.ts.map +1 -0
- package/dist/src/lib/localMode.d.ts +3 -0
- package/dist/src/lib/localMode.d.ts.map +1 -0
- package/dist/src/lib/packages.d.ts +6 -0
- package/dist/src/lib/packages.d.ts.map +1 -0
- package/dist/src/stores/useEncoreState.d.ts +33 -0
- package/dist/src/stores/useEncoreState.d.ts.map +1 -0
- package/dist/stores/useEncoreState.js +70 -0
- package/dist/stores/useEncoreState.js.map +1 -0
- package/package.json +60 -0
- package/src/AGENTS.md +161 -0
- package/src/README.md +110 -0
- package/src/app.ts +5 -0
- package/src/cli/commands/download.ts +133 -0
- package/src/cli/commands/generate.ts +3045 -0
- package/src/cli/index.ts +35 -0
- package/src/components/DynamicComponent.tsx +40 -0
- package/src/components/EncoreApp.tsx +759 -0
- package/src/components/EncoreErrorBoundary.tsx +49 -0
- package/src/components/EncoreLoadingFallback.tsx +25 -0
- package/src/components.tsx +3155 -0
- package/src/contexts/EncoreActionContext.ts +18 -0
- package/src/contexts/EncoreAppContext.ts +13 -0
- package/src/contexts/EncoreBindingContext.ts +6 -0
- package/src/contexts/EncoreComponentIdContext.ts +12 -0
- package/src/contexts/EncoreRepeatingContainerContext.ts +30 -0
- package/src/hooks/useAuthRedirect.ts +63 -0
- package/src/hooks/usePusherUpdates.ts +156 -0
- package/src/index.ts +16 -0
- package/src/lib/dynamicModules.ts +193 -0
- package/src/lib/fetcher.ts +108 -0
- package/src/lib/localMode.ts +30 -0
- package/src/lib/moduleRegistry.ts +24 -0
- package/src/lib/packages.ts +33 -0
- package/src/stores/useEncoreState.ts +121 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export type EncoreActionPayload = {
|
|
4
|
+
bravo: {
|
|
5
|
+
cancel: () => void;
|
|
6
|
+
action: any;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type EncoreActionContextType = {
|
|
11
|
+
onAction?: (payload: EncoreActionPayload) => void | Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const EncoreActionContext = React.createContext<EncoreActionContextType>({});
|
|
15
|
+
|
|
16
|
+
export default EncoreActionContext;
|
|
17
|
+
|
|
18
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type EncoreComponentIdContext = {
|
|
4
|
+
componentId?: string;
|
|
5
|
+
statefulSetId?: string; // ID of the parent StatefulSetComponent
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const EncoreComponentIdContext = React.createContext<EncoreComponentIdContext>(
|
|
9
|
+
{}
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
export default EncoreComponentIdContext;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export type RepeatingContainerControl = {
|
|
4
|
+
currentIndex?: number;
|
|
5
|
+
onIndexChange?: (index: number, containerId: string) => void;
|
|
6
|
+
goToIndex: (index: number) => void;
|
|
7
|
+
getCurrentIndex: () => number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type ControlProps = {
|
|
11
|
+
currentIndex?: number;
|
|
12
|
+
onIndexChange?: (index: number) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type EncoreRepeatingContainerContextValue = {
|
|
16
|
+
registerContainer: (id: string, control: RepeatingContainerControl) => void;
|
|
17
|
+
unregisterContainer: (id: string) => void;
|
|
18
|
+
getControl: (id: string) => RepeatingContainerControl | undefined;
|
|
19
|
+
setControlProps: (
|
|
20
|
+
id: string,
|
|
21
|
+
props: ControlProps | ((prev: ControlProps) => ControlProps)
|
|
22
|
+
) => void;
|
|
23
|
+
getControlProps: (id: string) => ControlProps | undefined;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const EncoreRepeatingContainerContext =
|
|
27
|
+
React.createContext<EncoreRepeatingContainerContextValue | null>(null);
|
|
28
|
+
|
|
29
|
+
export default EncoreRepeatingContainerContext;
|
|
30
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import EncoreAppContext from "../contexts/EncoreAppContext";
|
|
2
|
+
import useEncoreState from "../stores/useEncoreState";
|
|
3
|
+
import { useSearchParams, useNavigate, useLocation } from "react-router-dom";
|
|
4
|
+
import { useContext, useEffect, useMemo } from "react";
|
|
5
|
+
|
|
6
|
+
const useAuthRedirect = () => {
|
|
7
|
+
const [searchParams] = useSearchParams();
|
|
8
|
+
const location = useLocation();
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
|
|
11
|
+
const appId =
|
|
12
|
+
searchParams.get("appId") ||
|
|
13
|
+
location.pathname.split("/apps/")[1]?.split("/")[0];
|
|
14
|
+
const pageId =
|
|
15
|
+
searchParams.get("pageId") ||
|
|
16
|
+
location.pathname.split("/pages/")[1]?.split("/")[0];
|
|
17
|
+
const noRedirect = searchParams.get("noRedirect");
|
|
18
|
+
|
|
19
|
+
const { app } = useContext(EncoreAppContext);
|
|
20
|
+
const accessToken = useEncoreState((state) => state.accessToken);
|
|
21
|
+
|
|
22
|
+
const navMap = app.data?.app?.navigationMap;
|
|
23
|
+
const loginPageId = app.data?.app?.loginPageId;
|
|
24
|
+
const isAuthenticated = accessToken && accessToken?.expireAt > Date.now();
|
|
25
|
+
|
|
26
|
+
// Some apps have multiple pages in the signin flow.
|
|
27
|
+
// Existing apps don't have a "page requires auth" declaration (or the inverse) so we must infer it.
|
|
28
|
+
// We infer it using pages linked from the tagged login page
|
|
29
|
+
const loginPages = useMemo(() => {
|
|
30
|
+
if (!navMap) return [];
|
|
31
|
+
|
|
32
|
+
const findConnectedPages = (
|
|
33
|
+
pageMap: Record<string, { linksTo: string[] }>,
|
|
34
|
+
currentPage: string,
|
|
35
|
+
visited = new Set(),
|
|
36
|
+
): string[] => {
|
|
37
|
+
if (visited.has(currentPage)) return [];
|
|
38
|
+
visited.add(currentPage);
|
|
39
|
+
|
|
40
|
+
const directLinks = pageMap[currentPage]?.linksTo || [];
|
|
41
|
+
const indirectLinks = directLinks.flatMap((link) =>
|
|
42
|
+
findConnectedPages(pageMap, link, visited),
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return [...directLinks, ...indirectLinks];
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = new Set(findConnectedPages(navMap.pages, loginPageId));
|
|
49
|
+
result.add(loginPageId);
|
|
50
|
+
|
|
51
|
+
return Array.from(result);
|
|
52
|
+
}, [navMap]);
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (noRedirect) return;
|
|
56
|
+
if (isAuthenticated) return;
|
|
57
|
+
if (!loginPageId || loginPages.includes(pageId as string)) return;
|
|
58
|
+
|
|
59
|
+
navigate(`/apps/${appId}/pages/${loginPageId}`);
|
|
60
|
+
}, [loginPageId, pageId, isAuthenticated, noRedirect]);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default useAuthRedirect;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import Pusher from "pusher-js";
|
|
3
|
+
import { mutate } from "swr";
|
|
4
|
+
import { clearModuleCache } from "../lib/moduleRegistry";
|
|
5
|
+
|
|
6
|
+
type PusherConfig = {
|
|
7
|
+
key?: string;
|
|
8
|
+
cluster?: string;
|
|
9
|
+
encrypted?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type UsePusherUpdatesOptions = {
|
|
13
|
+
appId: string;
|
|
14
|
+
pageId?: string;
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
onUpdate?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Get Pusher config from environment variables
|
|
20
|
+
const getPusherConfig = (): PusherConfig => {
|
|
21
|
+
return {
|
|
22
|
+
key: "7b0611d0051677eed996",
|
|
23
|
+
cluster: "us2",
|
|
24
|
+
encrypted: true,
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Hook to listen for EncoreApp component updates via Pusher
|
|
30
|
+
* Channel name format: `${appId}-${pageId}`
|
|
31
|
+
* When an update event is received, it invalidates SWR cache and triggers a reload
|
|
32
|
+
*/
|
|
33
|
+
export function usePusherUpdates({
|
|
34
|
+
appId,
|
|
35
|
+
pageId,
|
|
36
|
+
enabled = true,
|
|
37
|
+
onUpdate,
|
|
38
|
+
}: UsePusherUpdatesOptions) {
|
|
39
|
+
const pusherRef = useRef<Pusher | null>(null);
|
|
40
|
+
const channelRef = useRef<ReturnType<Pusher["subscribe"]> | null>(null);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
// Only connect if enabled and we have both appId and pageId
|
|
44
|
+
if (!enabled || !appId || !pageId) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const config = getPusherConfig();
|
|
49
|
+
|
|
50
|
+
// Skip if Pusher key is not configured
|
|
51
|
+
if (!config.key) {
|
|
52
|
+
console.warn("[Pusher] not configured. Pusher updates disabled.");
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Initialize Pusher
|
|
57
|
+
const pusher = new Pusher(config.key, {
|
|
58
|
+
cluster: config.cluster || "us2",
|
|
59
|
+
forceTLS: config.encrypted,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
pusherRef.current = pusher;
|
|
63
|
+
|
|
64
|
+
// Channel name format: appId-pageId (e.g., "01KA23JMNBQ2V9NR7K0VXKT5TF-01KA23JMPBZSG2YRJ6M5X87SKJ")
|
|
65
|
+
const channelName = `${appId}`;
|
|
66
|
+
console.log(`[Pusher] Subscribing to channel: ${channelName}`);
|
|
67
|
+
|
|
68
|
+
// Subscribe to the channel
|
|
69
|
+
const channel = pusher.subscribe(channelName);
|
|
70
|
+
channelRef.current = channel;
|
|
71
|
+
|
|
72
|
+
// Handle subscription success
|
|
73
|
+
channel.bind("pusher:subscription_succeeded", () => {
|
|
74
|
+
console.log(
|
|
75
|
+
`[Pusher] Successfully subscribed to channel: ${channelName}`
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Handle subscription error
|
|
80
|
+
channel.bind("pusher:subscription_error", (status: number) => {
|
|
81
|
+
console.error(
|
|
82
|
+
`[Pusher] Subscription error for channel ${channelName}:`,
|
|
83
|
+
status
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Listen for update events
|
|
88
|
+
// You can customize the event name based on your backend's Pusher event names
|
|
89
|
+
// Common event names: 'update', 'component-updated', 'app-updated', etc.
|
|
90
|
+
const handleUpdate = (data: unknown) => {
|
|
91
|
+
console.log(`[Pusher] Update received for ${channelName}:`, data);
|
|
92
|
+
|
|
93
|
+
// Clear the module cache for the component to force reload
|
|
94
|
+
const componentName = `${appId}/draft/components/${pageId}`;
|
|
95
|
+
clearModuleCache(componentName);
|
|
96
|
+
|
|
97
|
+
// Invalidate SWR cache for both app and page data
|
|
98
|
+
const appUrl = `/devices/apps/${appId}`;
|
|
99
|
+
const pageUrl = `/devices/apps/${appId}/node/${pageId}`;
|
|
100
|
+
|
|
101
|
+
// Mutate both URLs to trigger refetch
|
|
102
|
+
mutate(appUrl).catch((err) => {
|
|
103
|
+
console.error("[Pusher] Error invalidating app cache:", err);
|
|
104
|
+
});
|
|
105
|
+
mutate(pageUrl).catch((err) => {
|
|
106
|
+
console.error("[Pusher] Error invalidating page cache:", err);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Also invalidate URLs with useLocal query param if they exist
|
|
110
|
+
mutate(`${appUrl}?useLocal=1`).catch(() => {
|
|
111
|
+
// Ignore errors for optional URLs
|
|
112
|
+
});
|
|
113
|
+
mutate(`${pageUrl}?useLocal=1`).catch(() => {
|
|
114
|
+
// Ignore errors for optional URLs
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Call the onUpdate callback if provided
|
|
118
|
+
onUpdate?.();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Bind to update events
|
|
122
|
+
// Adjust the event name(s) based on what your backend sends
|
|
123
|
+
channel.bind("update", handleUpdate);
|
|
124
|
+
channel.bind("component-updated", handleUpdate);
|
|
125
|
+
channel.bind("app-updated", handleUpdate);
|
|
126
|
+
|
|
127
|
+
// Cleanup function
|
|
128
|
+
return () => {
|
|
129
|
+
console.log(`[Pusher] Unsubscribing from channel: ${channelName}`);
|
|
130
|
+
|
|
131
|
+
// Unbind all event handlers
|
|
132
|
+
if (channelRef.current) {
|
|
133
|
+
channelRef.current.unbind_all();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Unsubscribe from channel
|
|
137
|
+
if (channelRef.current && pusherRef.current) {
|
|
138
|
+
pusherRef.current.unsubscribe(channelName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Disconnect Pusher if no other channels are subscribed
|
|
142
|
+
if (pusherRef.current) {
|
|
143
|
+
pusherRef.current.disconnect();
|
|
144
|
+
pusherRef.current = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
channelRef.current = null;
|
|
148
|
+
};
|
|
149
|
+
}, [appId, pageId, enabled, onUpdate]);
|
|
150
|
+
|
|
151
|
+
// Return channel ref for advanced usage
|
|
152
|
+
return {
|
|
153
|
+
channel: channelRef.current,
|
|
154
|
+
pusher: pusherRef.current,
|
|
155
|
+
};
|
|
156
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
console.log('Encore Lib Loaded');
|
|
2
|
+
import EncoreApp from "./components/EncoreApp";
|
|
3
|
+
import EncoreErrorBoundary from "./components/EncoreErrorBoundary";
|
|
4
|
+
import EncoreLoadingFallback from "./components/EncoreLoadingFallback";
|
|
5
|
+
import EncoreAppContext from "./contexts/EncoreAppContext";
|
|
6
|
+
import EncoreBindingContext from "./contexts/EncoreBindingContext";
|
|
7
|
+
import useEncoreState from "./stores/useEncoreState";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
EncoreApp,
|
|
11
|
+
EncoreErrorBoundary,
|
|
12
|
+
EncoreLoadingFallback,
|
|
13
|
+
EncoreAppContext,
|
|
14
|
+
EncoreBindingContext,
|
|
15
|
+
useEncoreState,
|
|
16
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import packages from "./packages";
|
|
2
|
+
import { isLocalMode } from "./localMode";
|
|
3
|
+
import { CONST_COMPONENTS_CDN_URL } from "../../constants";
|
|
4
|
+
import { registerModule, getModuleExports, haveModule } from "./moduleRegistry";
|
|
5
|
+
|
|
6
|
+
// Initialize registry with default packages
|
|
7
|
+
Object.keys(packages).forEach((p) => {
|
|
8
|
+
registerModule(p, packages[p]);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export function loadAMDModule(name: string, code: string) {
|
|
12
|
+
return new Promise<any>((resolve, reject) => {
|
|
13
|
+
const define = (deps: string[], factory: (...va: unknown[]) => unknown) => {
|
|
14
|
+
if (typeof deps === "function") {
|
|
15
|
+
factory = deps;
|
|
16
|
+
deps = [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
Promise.all(
|
|
20
|
+
deps.map(async (dep) => {
|
|
21
|
+
if (dep === "require") {
|
|
22
|
+
// Return a mock require function for AMD modules
|
|
23
|
+
return ((id: string) => {
|
|
24
|
+
if (!haveModule(id)) {
|
|
25
|
+
throw new Error(`Module ${id} not found`);
|
|
26
|
+
}
|
|
27
|
+
return getModuleExports(id);
|
|
28
|
+
}) as typeof require;
|
|
29
|
+
}
|
|
30
|
+
if (dep === "exports") return {};
|
|
31
|
+
if (!haveModule(dep)) await fetchAndRegister(dep);
|
|
32
|
+
return getModuleExports(dep);
|
|
33
|
+
})
|
|
34
|
+
)
|
|
35
|
+
.then((resolvedDeps: unknown[]) => {
|
|
36
|
+
const exports = {};
|
|
37
|
+
const module = factory.apply(null, resolvedDeps);
|
|
38
|
+
const result = module || exports;
|
|
39
|
+
resolve({ default: result });
|
|
40
|
+
})
|
|
41
|
+
.catch(reject);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
define.amd = true;
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Patch code to replace hardcoded "undefined" component references
|
|
48
|
+
// This fixes remote component files that were generated before certain components existed
|
|
49
|
+
let patchedCode = code;
|
|
50
|
+
|
|
51
|
+
// Replace t.createElement("undefined", props) for container:slider with e.SliderComponent
|
|
52
|
+
// Note: Components are passed as the second parameter 'e' in AMD modules: define(["react","@/bravo/components"],(function(t,e){...}))
|
|
53
|
+
patchedCode = patchedCode.replace(
|
|
54
|
+
/t\.createElement\("undefined",(\{[^}]*nodeData:\{[^}]*type:"container:slider"[^}]*\}[^}]*\})/g,
|
|
55
|
+
(_match, propsStr) => {
|
|
56
|
+
return `t.createElement(e.SliderComponent,${propsStr}`;
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Fallback for any other undefined components - use the type-based lookup
|
|
61
|
+
patchedCode = patchedCode.replace(
|
|
62
|
+
/t\.createElement\("undefined",(\{[^}]*nodeData:\{([^}]*type:"([^"]+)")?[^}]*\}[^}]*\})/g,
|
|
63
|
+
(_match, propsStr, _typeStr, _type) => {
|
|
64
|
+
const typeMatch = propsStr.match(/type:"([^"]+)"/);
|
|
65
|
+
const componentType = typeMatch ? typeMatch[1] : null;
|
|
66
|
+
const componentLookup = componentType
|
|
67
|
+
? `(e["${componentType}"] || e.DefaultLayerComponent)`
|
|
68
|
+
: "e.DefaultLayerComponent";
|
|
69
|
+
return `t.createElement(${componentLookup},${propsStr}`;
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Final fallback for any remaining "undefined"
|
|
74
|
+
patchedCode = patchedCode.replace(
|
|
75
|
+
/t\.createElement\("undefined"/g,
|
|
76
|
+
"t.createElement(e.DefaultLayerComponent"
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
// Wrap with error boundary
|
|
80
|
+
// Note: Components are already passed as the second parameter 'e' via AMD dependencies,
|
|
81
|
+
// so we don't need to inject them separately
|
|
82
|
+
const wrapper = Function(
|
|
83
|
+
"define",
|
|
84
|
+
`
|
|
85
|
+
try {
|
|
86
|
+
${patchedCode}\n//# sourceURL=dynamic-module://${name}.js
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('[Module Evaluation Error]', {
|
|
89
|
+
moduleName: '${name}',
|
|
90
|
+
error: error.message,
|
|
91
|
+
stack: error.stack,
|
|
92
|
+
lineNumber: error.lineNumber,
|
|
93
|
+
columnNumber: error.columnNumber
|
|
94
|
+
});
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
`
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
console.debug(`[Module Loading] Attempting to load module: ${name}`);
|
|
101
|
+
wrapper(define);
|
|
102
|
+
console.debug(`[Module Loading] Successfully loaded module: ${name}`);
|
|
103
|
+
} catch (e: any) {
|
|
104
|
+
// Enhanced error logging
|
|
105
|
+
console.error("[Module Loading Failed]", {
|
|
106
|
+
moduleName: name,
|
|
107
|
+
error: e,
|
|
108
|
+
errorType: e.constructor.name,
|
|
109
|
+
message: e.message,
|
|
110
|
+
stack: e.stack,
|
|
111
|
+
code: code.slice(0, 500) + (code.length > 500 ? "..." : ""), // Show first 500 chars of code
|
|
112
|
+
});
|
|
113
|
+
reject(e);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function fetchDep(name: string) {
|
|
119
|
+
// Local mode: map Encore component name to local JSX/JS under /flex-layout
|
|
120
|
+
console.log(`🔍 fetchDep called for ${name}. isLocalMode: ${isLocalMode()}`);
|
|
121
|
+
if (isLocalMode()) {
|
|
122
|
+
// Expecting `${appId}/draft/components/${pageId}`
|
|
123
|
+
const m = name.match(/^([^/]+)\/draft\/components\/([^/]+)$/);
|
|
124
|
+
if (m) {
|
|
125
|
+
const appId = m[1];
|
|
126
|
+
const pageId = m[2];
|
|
127
|
+
// Load from public as AMD (do not import() public files; Vite forbids importing from /public)
|
|
128
|
+
const absBase = (import.meta as any)?.env?.VITE_FLEX_LAYOUT_ABS || null;
|
|
129
|
+
const candidates = [
|
|
130
|
+
`/flex-layout/${appId}/${pageId}.js`,
|
|
131
|
+
absBase ? `/@fs/${absBase}/${appId}/${pageId}.js` : null,
|
|
132
|
+
].filter(Boolean) as string[];
|
|
133
|
+
|
|
134
|
+
// Fetch as text and evaluate via AMD define shim
|
|
135
|
+
const errors: string[] = [];
|
|
136
|
+
for (const url of candidates) {
|
|
137
|
+
try {
|
|
138
|
+
const response = await fetch(url);
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
errors.push(`${url}: ${response.status} ${response.statusText}`);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const code = await response.text();
|
|
144
|
+
const amd = await loadAMDModule(
|
|
145
|
+
`${appId}/draft/components/${pageId}`,
|
|
146
|
+
code
|
|
147
|
+
);
|
|
148
|
+
return amd;
|
|
149
|
+
} catch (e: any) {
|
|
150
|
+
errors.push(`${url}: ${e.message || String(e)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
throw new Error(
|
|
154
|
+
`Local component not found for ${name}. Tried: ${candidates.join(
|
|
155
|
+
", "
|
|
156
|
+
)}. Errors: ${errors.join("; ")}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Remote mode: use AMD loader
|
|
161
|
+
const cacheBuster = Math.round(Date.now() / 1000);
|
|
162
|
+
const url = `${CONST_COMPONENTS_CDN_URL}/${name}.js?cacheBuster=${cacheBuster}`;
|
|
163
|
+
console.log(`[Module Loading] Fetching remote component from: ${url}`);
|
|
164
|
+
const text = await fetch(url).then(async (a) => {
|
|
165
|
+
if (!a.ok) {
|
|
166
|
+
if (a.status === 403) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Error: Could not fetch component '${name}'. Try regenerating components.`
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
throw new Error(`Network error (${a.status}): ${await a.text()}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return a.text();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return await loadAMDModule(name, text);
|
|
178
|
+
// } catch (error) {
|
|
179
|
+
// return {
|
|
180
|
+
// default() {
|
|
181
|
+
// console.log(error);
|
|
182
|
+
// throw new Error("Failed to render");
|
|
183
|
+
// },
|
|
184
|
+
// };
|
|
185
|
+
// }
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function fetchAndRegister(dep: string) {
|
|
189
|
+
const result = await fetchDep(dep);
|
|
190
|
+
registerModule(dep, () => ({ exports: result.default }));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export { clearModuleCache } from "./moduleRegistry";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { isLocalMode } from "./localMode";
|
|
3
|
+
import useEncoreState from "../stores/useEncoreState";
|
|
4
|
+
|
|
5
|
+
// Get baseURL from store at runtime instead of build time
|
|
6
|
+
const getAppsServiceUrl = () => {
|
|
7
|
+
return useEncoreState.getState().baseURL;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const bundledResponses: Record<string, string> = {};
|
|
11
|
+
|
|
12
|
+
const fetcher = (url: string) => {
|
|
13
|
+
// Local mode: map Encore service URLs to files in /flex-layout
|
|
14
|
+
const forceLocal = /\buseLocal=1\b/.test(url);
|
|
15
|
+
if (forceLocal || isLocalMode()) {
|
|
16
|
+
const pathOnly = url.split("?")[0];
|
|
17
|
+
const absBase = null;
|
|
18
|
+
// /devices/apps/:appId
|
|
19
|
+
const appMatch = pathOnly.match(/^\/devices\/apps\/([^/]+)$/);
|
|
20
|
+
if (appMatch) {
|
|
21
|
+
const appId = appMatch[1];
|
|
22
|
+
const tryUrls = [
|
|
23
|
+
`/flex-layout/${appId}/${appId}.json`,
|
|
24
|
+
absBase
|
|
25
|
+
? `/@fs/${absBase}/${appId}/${appId}.json`
|
|
26
|
+
: undefined,
|
|
27
|
+
].filter(Boolean) as string[];
|
|
28
|
+
return (async () => {
|
|
29
|
+
for (const u of tryUrls) {
|
|
30
|
+
try {
|
|
31
|
+
const r = await fetch(u);
|
|
32
|
+
if (r.ok) return r.json();
|
|
33
|
+
} catch {
|
|
34
|
+
// try next
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw new Error(
|
|
38
|
+
`Local app.json not found for ${appId} (tried ${tryUrls.join(
|
|
39
|
+
", ",
|
|
40
|
+
)})`,
|
|
41
|
+
);
|
|
42
|
+
})();
|
|
43
|
+
}
|
|
44
|
+
// /devices/apps/:appId/node/:pageId
|
|
45
|
+
const pageMatch = pathOnly.match(
|
|
46
|
+
/^\/devices\/apps\/([^/]+)\/node\/([^/?#]+)$/,
|
|
47
|
+
);
|
|
48
|
+
if (pageMatch) {
|
|
49
|
+
const appId = pageMatch[1];
|
|
50
|
+
const pageId = pageMatch[2];
|
|
51
|
+
// Try a dedicated page JSON first (if present)
|
|
52
|
+
const tryUrls = [
|
|
53
|
+
`/flex-layout/${appId}/${pageId}.json`,
|
|
54
|
+
absBase
|
|
55
|
+
? `/@fs/${absBase}/${appId}/${pageId}.json`
|
|
56
|
+
: undefined,
|
|
57
|
+
].filter(Boolean) as string[];
|
|
58
|
+
return fetch(tryUrls[0])
|
|
59
|
+
.then(async (r) => {
|
|
60
|
+
if (r.ok) return r.json();
|
|
61
|
+
// try secondary url if defined
|
|
62
|
+
if (tryUrls[1]) {
|
|
63
|
+
const r2 = await fetch(tryUrls[1]).catch(() => null);
|
|
64
|
+
if (r2 && r2.ok) return r2.json();
|
|
65
|
+
}
|
|
66
|
+
// Fallback: derive minimal shape from app.json
|
|
67
|
+
const appJson =
|
|
68
|
+
(await fetch(`/flex-layout/${appId}/${appId}.json`)
|
|
69
|
+
.then((a) => (a.ok ? a.json() : null))
|
|
70
|
+
.catch(() => null)) ||
|
|
71
|
+
(absBase
|
|
72
|
+
? await fetch(
|
|
73
|
+
`/@fs/${absBase}/${appId}/${appId}.json`,
|
|
74
|
+
)
|
|
75
|
+
.then((a) => (a.ok ? a.json() : null))
|
|
76
|
+
.catch(() => null)
|
|
77
|
+
: null);
|
|
78
|
+
// Keep shape compatible with current consumers
|
|
79
|
+
const pages =
|
|
80
|
+
appJson?.app?.data?.pages ||
|
|
81
|
+
appJson?.data?.pages ||
|
|
82
|
+
[];
|
|
83
|
+
const page = pages.find((p: any) => p?.id === pageId) || {};
|
|
84
|
+
return { clientData: page };
|
|
85
|
+
})
|
|
86
|
+
.catch(async () => {
|
|
87
|
+
// Final fallback: minimal object
|
|
88
|
+
return { clientData: null };
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (bundledResponses?.[url]) {
|
|
94
|
+
return JSON.parse(bundledResponses?.[url]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Get baseURL at runtime from store
|
|
98
|
+
const appsServiceUrl = getAppsServiceUrl();
|
|
99
|
+
|
|
100
|
+
return axios({
|
|
101
|
+
baseURL: appsServiceUrl,
|
|
102
|
+
url,
|
|
103
|
+
headers: { "x-app-clientrendered": "true" },
|
|
104
|
+
}).then((res) => res.data);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default fetcher;
|
|
108
|
+
export { getAppsServiceUrl as appsServiceUrl };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Centralized toggle for Local Flex-Layout mode
|
|
2
|
+
let overrideMode: "local" | "remote" | null = null;
|
|
3
|
+
|
|
4
|
+
export function setLocalModeOverride(
|
|
5
|
+
value: "local" | "remote" | null,
|
|
6
|
+
): void {
|
|
7
|
+
overrideMode = value;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isLocalMode(): boolean {
|
|
11
|
+
try {
|
|
12
|
+
// Explicit override (per-App instance)
|
|
13
|
+
if (overrideMode === "local") return true;
|
|
14
|
+
if (overrideMode === "remote") return false;
|
|
15
|
+
// Env toggle
|
|
16
|
+
// if (import.meta?.env?.VITE_BRAVO_LOCAL === "1") return true;
|
|
17
|
+
// if (import.meta?.env?.VITE_BRAVO_LOCAL === "true") return true;
|
|
18
|
+
// Query-string toggle
|
|
19
|
+
if (typeof window !== "undefined") {
|
|
20
|
+
const params = new URLSearchParams(window.location.search);
|
|
21
|
+
const v = params.get("useLocal");
|
|
22
|
+
if (v === "1" || v === "true") return true;
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// ignore
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const registry: Record<string, any> = {};
|
|
2
|
+
|
|
3
|
+
export function registerModule(name: string, module: any) {
|
|
4
|
+
registry[name] = module;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getModuleExports(name: string) {
|
|
8
|
+
return registry[name]().exports;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function haveModule(name: string): boolean {
|
|
12
|
+
return name in registry;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Clear a module from the cache to force reload on next fetch
|
|
17
|
+
* @param name Module name (e.g., `${appId}/draft/components/${pageId}`)
|
|
18
|
+
*/
|
|
19
|
+
export function clearModuleCache(name: string): void {
|
|
20
|
+
if (name in registry) {
|
|
21
|
+
delete registry[name];
|
|
22
|
+
console.log(`[Module Cache] Cleared module: ${name}`);
|
|
23
|
+
}
|
|
24
|
+
}
|