dynim-react 1.0.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 +447 -0
- package/dist/builder/BuilderProvider.d.ts +99 -0
- package/dist/builder/BuilderProvider.d.ts.map +1 -0
- package/dist/builder/BuilderProvider.js +339 -0
- package/dist/builder/ChatContext.d.ts +11 -0
- package/dist/builder/ChatContext.d.ts.map +1 -0
- package/dist/builder/ChatContext.js +18 -0
- package/dist/builder/ChatInput.d.ts +11 -0
- package/dist/builder/ChatInput.d.ts.map +1 -0
- package/dist/builder/ChatInput.js +20 -0
- package/dist/builder/CodeChatPanel.d.ts +24 -0
- package/dist/builder/CodeChatPanel.d.ts.map +1 -0
- package/dist/builder/CodeChatPanel.js +299 -0
- package/dist/builder/EditableZone.d.ts +73 -0
- package/dist/builder/EditableZone.d.ts.map +1 -0
- package/dist/builder/EditableZone.js +83 -0
- package/dist/builder/MessageList.d.ts +14 -0
- package/dist/builder/MessageList.d.ts.map +1 -0
- package/dist/builder/MessageList.js +23 -0
- package/dist/builder/index.d.ts +18 -0
- package/dist/builder/index.d.ts.map +1 -0
- package/dist/builder/index.js +15 -0
- package/dist/builder/useChatbot.d.ts +24 -0
- package/dist/builder/useChatbot.d.ts.map +1 -0
- package/dist/builder/useChatbot.js +85 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/inference/DynimProvider.d.ts +23 -0
- package/dist/inference/DynimProvider.d.ts.map +1 -0
- package/dist/inference/DynimProvider.js +231 -0
- package/dist/inference/InferenceProvider.d.ts +23 -0
- package/dist/inference/InferenceProvider.d.ts.map +1 -0
- package/dist/inference/InferenceProvider.js +270 -0
- package/dist/inference/createDynimSDK.d.ts +39 -0
- package/dist/inference/createDynimSDK.d.ts.map +1 -0
- package/dist/inference/createDynimSDK.js +61 -0
- package/dist/inference/index.d.ts +7 -0
- package/dist/inference/index.d.ts.map +1 -0
- package/dist/inference/index.js +7 -0
- package/dist/inference/sharedContext.d.ts +39 -0
- package/dist/inference/sharedContext.d.ts.map +1 -0
- package/dist/inference/sharedContext.js +61 -0
- package/dist/inference/types.d.ts +67 -0
- package/dist/inference/types.d.ts.map +1 -0
- package/dist/inference/types.js +1 -0
- package/package.json +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,cAAc,YAAY,CAAC;AAG3B,OAAO,EAEL,eAAe,EACf,UAAU,EAEV,YAAY,EACZ,cAAc,EAEd,WAAW,EACX,SAAS,EACT,aAAa,EAEb,UAAU,EAEV,UAAU,EACV,YAAY,EACZ,eAAe,EACf,YAAY,GACb,MAAM,WAAW,CAAC;AAEnB,YAAY,EACV,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,SAAS,GACV,MAAM,WAAW,CAAC;AAGnB,OAAO,EACL,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,cAAc,EACd,WAAW,GACZ,MAAM,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dynim-react - React wrapper for dynim-core
|
|
3
|
+
*
|
|
4
|
+
* This package provides:
|
|
5
|
+
* - Re-exports everything from dynim-core (for convenience)
|
|
6
|
+
* - React components and hooks for the builder
|
|
7
|
+
* - Inference SDK for tenant customization loading
|
|
8
|
+
*/
|
|
9
|
+
// Re-export EVERYTHING from dynim-core
|
|
10
|
+
export * from 'dynim-core';
|
|
11
|
+
// Builder React exports
|
|
12
|
+
export {
|
|
13
|
+
// Provider and hook
|
|
14
|
+
BuilderProvider, useBuilder,
|
|
15
|
+
// Chat context
|
|
16
|
+
ChatProvider, useChatContext,
|
|
17
|
+
// Components
|
|
18
|
+
MessageList, ChatInput, CodeChatPanel,
|
|
19
|
+
// Headless hook
|
|
20
|
+
useChatbot,
|
|
21
|
+
// Zone wrappers for customization control
|
|
22
|
+
LockedZone, EditableZone, NonCustomizable, Customizable, } from './builder';
|
|
23
|
+
// Inference exports
|
|
24
|
+
export { InferenceProvider, InferenceProviderDefault, createSharedContext, getSharedContext, isSharedContextReady, } from './inference';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { DynimProviderProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* DynimProvider - A React component that dynamically loads tenant-specific bundles.
|
|
4
|
+
*
|
|
5
|
+
* This provider handles the lifecycle of loading, rendering, and cleaning up
|
|
6
|
+
* tenant customization bundles. It supports optional pre-checks to determine
|
|
7
|
+
* if a tenant has customizations before attempting to load them.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <DynimProvider
|
|
12
|
+
* tenantId={user?.tenantId}
|
|
13
|
+
* bundleUrl={(id) => `https://cdn.example.com/bundles/${id}.js`}
|
|
14
|
+
* loadingComponent={<Spinner />}
|
|
15
|
+
* errorBanner={true}
|
|
16
|
+
* >
|
|
17
|
+
* <YourBaseApp />
|
|
18
|
+
* </DynimProvider>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function DynimProvider({ tenantId, bundleUrl, checkCustomizations, loadingComponent, errorBanner, onError, onLoad, children, }: DynimProviderProps): JSX.Element;
|
|
22
|
+
export default DynimProvider;
|
|
23
|
+
//# sourceMappingURL=DynimProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DynimProvider.d.ts","sourceRoot":"","sources":["../../src/inference/DynimProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAc,MAAM,SAAS,CAAC;AA8E9D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,SAAS,EACT,mBAAmB,EACnB,gBAAgB,EAChB,WAAmB,EACnB,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,kBAAkB,GAAG,GAAG,CAAC,OAAO,CAuLlC;AAED,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Default loading component shown while bundle is loading
|
|
5
|
+
*/
|
|
6
|
+
function DefaultLoadingComponent() {
|
|
7
|
+
return (_jsx("div", { style: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
justifyContent: 'center',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
padding: '20px',
|
|
12
|
+
}, children: _jsx("span", { children: "Loading customizations..." }) }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Default error banner component
|
|
16
|
+
*/
|
|
17
|
+
function DefaultErrorBanner({ error }) {
|
|
18
|
+
return (_jsxs("div", { style: {
|
|
19
|
+
background: '#fee2e2',
|
|
20
|
+
border: '1px solid #ef4444',
|
|
21
|
+
borderRadius: '4px',
|
|
22
|
+
padding: '12px 16px',
|
|
23
|
+
marginBottom: '16px',
|
|
24
|
+
color: '#991b1b',
|
|
25
|
+
}, children: [_jsx("strong", { children: "Customization Error:" }), " ", error.message] }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Container for rendering tenant bundle content
|
|
29
|
+
*/
|
|
30
|
+
function BundleContainer() {
|
|
31
|
+
return _jsx("div", { id: "dynim-root" });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Loads a script from a URL and returns a promise that resolves when loaded
|
|
35
|
+
*/
|
|
36
|
+
function loadScript(url) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
// Check if script already exists
|
|
39
|
+
const existingScript = document.querySelector(`script[src="${url}"]`);
|
|
40
|
+
if (existingScript) {
|
|
41
|
+
resolve();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const script = document.createElement('script');
|
|
45
|
+
script.src = url;
|
|
46
|
+
script.async = true;
|
|
47
|
+
script.onload = () => resolve();
|
|
48
|
+
script.onerror = () => reject(new Error(`Failed to load bundle from ${url}`));
|
|
49
|
+
document.head.appendChild(script);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Removes a previously loaded script
|
|
54
|
+
*/
|
|
55
|
+
function removeScript(url) {
|
|
56
|
+
const script = document.querySelector(`script[src="${url}"]`);
|
|
57
|
+
if (script) {
|
|
58
|
+
script.remove();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* DynimProvider - A React component that dynamically loads tenant-specific bundles.
|
|
63
|
+
*
|
|
64
|
+
* This provider handles the lifecycle of loading, rendering, and cleaning up
|
|
65
|
+
* tenant customization bundles. It supports optional pre-checks to determine
|
|
66
|
+
* if a tenant has customizations before attempting to load them.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```tsx
|
|
70
|
+
* <DynimProvider
|
|
71
|
+
* tenantId={user?.tenantId}
|
|
72
|
+
* bundleUrl={(id) => `https://cdn.example.com/bundles/${id}.js`}
|
|
73
|
+
* loadingComponent={<Spinner />}
|
|
74
|
+
* errorBanner={true}
|
|
75
|
+
* >
|
|
76
|
+
* <YourBaseApp />
|
|
77
|
+
* </DynimProvider>
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function DynimProvider({ tenantId, bundleUrl, checkCustomizations, loadingComponent, errorBanner = false, onError, onLoad, children, }) {
|
|
81
|
+
const [state, setState] = useState({
|
|
82
|
+
status: 'idle',
|
|
83
|
+
error: null,
|
|
84
|
+
currentTenantId: null,
|
|
85
|
+
});
|
|
86
|
+
const loadedUrlRef = useRef(null);
|
|
87
|
+
/**
|
|
88
|
+
* Resolves the bundle URL for a given tenant ID
|
|
89
|
+
*/
|
|
90
|
+
const resolveBundleUrl = useCallback((id) => {
|
|
91
|
+
if (typeof bundleUrl === 'function') {
|
|
92
|
+
return bundleUrl(id);
|
|
93
|
+
}
|
|
94
|
+
// If bundleUrl is a string template, replace {tenantId} placeholder
|
|
95
|
+
return bundleUrl.replace('{tenantId}', id);
|
|
96
|
+
}, [bundleUrl]);
|
|
97
|
+
/**
|
|
98
|
+
* Cleans up previously loaded bundle
|
|
99
|
+
*/
|
|
100
|
+
const cleanup = useCallback(() => {
|
|
101
|
+
if (loadedUrlRef.current) {
|
|
102
|
+
removeScript(loadedUrlRef.current);
|
|
103
|
+
loadedUrlRef.current = null;
|
|
104
|
+
}
|
|
105
|
+
// Clear the bundle render target
|
|
106
|
+
const root = document.getElementById('dynim-root');
|
|
107
|
+
if (root) {
|
|
108
|
+
root.innerHTML = '';
|
|
109
|
+
}
|
|
110
|
+
}, []);
|
|
111
|
+
/**
|
|
112
|
+
* Main effect to handle tenant bundle loading
|
|
113
|
+
*/
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
// No tenant - reset to idle and show children
|
|
116
|
+
if (!tenantId) {
|
|
117
|
+
cleanup();
|
|
118
|
+
setState({
|
|
119
|
+
status: 'idle',
|
|
120
|
+
error: null,
|
|
121
|
+
currentTenantId: null,
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
// Same tenant already loaded - do nothing
|
|
126
|
+
if (state.currentTenantId === tenantId && state.status === 'loaded') {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let cancelled = false;
|
|
130
|
+
async function loadBundle() {
|
|
131
|
+
try {
|
|
132
|
+
// Step 1: Check if tenant has customizations (if check function provided)
|
|
133
|
+
if (checkCustomizations) {
|
|
134
|
+
setState((prev) => ({ ...prev, status: 'checking' }));
|
|
135
|
+
const hasCustomizations = await checkCustomizations(tenantId);
|
|
136
|
+
if (cancelled)
|
|
137
|
+
return;
|
|
138
|
+
if (!hasCustomizations) {
|
|
139
|
+
cleanup();
|
|
140
|
+
setState({
|
|
141
|
+
status: 'no-customization',
|
|
142
|
+
error: null,
|
|
143
|
+
currentTenantId: tenantId,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Step 2: Load the bundle
|
|
149
|
+
setState((prev) => ({ ...prev, status: 'loading' }));
|
|
150
|
+
// Clean up previous bundle
|
|
151
|
+
cleanup();
|
|
152
|
+
const url = resolveBundleUrl(tenantId);
|
|
153
|
+
await loadScript(url);
|
|
154
|
+
if (cancelled) {
|
|
155
|
+
removeScript(url);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
loadedUrlRef.current = url;
|
|
159
|
+
setState({
|
|
160
|
+
status: 'loaded',
|
|
161
|
+
error: null,
|
|
162
|
+
currentTenantId: tenantId,
|
|
163
|
+
});
|
|
164
|
+
onLoad?.(tenantId);
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
if (cancelled)
|
|
168
|
+
return;
|
|
169
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
170
|
+
setState({
|
|
171
|
+
status: 'error',
|
|
172
|
+
error,
|
|
173
|
+
currentTenantId: tenantId,
|
|
174
|
+
});
|
|
175
|
+
onError?.(error, tenantId);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
loadBundle();
|
|
179
|
+
return () => {
|
|
180
|
+
cancelled = true;
|
|
181
|
+
};
|
|
182
|
+
}, [
|
|
183
|
+
tenantId,
|
|
184
|
+
checkCustomizations,
|
|
185
|
+
resolveBundleUrl,
|
|
186
|
+
cleanup,
|
|
187
|
+
onError,
|
|
188
|
+
onLoad,
|
|
189
|
+
state.currentTenantId,
|
|
190
|
+
state.status,
|
|
191
|
+
]);
|
|
192
|
+
/**
|
|
193
|
+
* Cleanup on unmount
|
|
194
|
+
*/
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
return () => {
|
|
197
|
+
cleanup();
|
|
198
|
+
};
|
|
199
|
+
}, [cleanup]);
|
|
200
|
+
// Render error banner if enabled and there's an error
|
|
201
|
+
const renderErrorBanner = () => {
|
|
202
|
+
if (!errorBanner || state.status !== 'error' || !state.error) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
if (errorBanner === true) {
|
|
206
|
+
return _jsx(DefaultErrorBanner, { error: state.error });
|
|
207
|
+
}
|
|
208
|
+
return errorBanner;
|
|
209
|
+
};
|
|
210
|
+
// Determine what to render based on state
|
|
211
|
+
const renderContent = () => {
|
|
212
|
+
switch (state.status) {
|
|
213
|
+
case 'checking':
|
|
214
|
+
case 'loading':
|
|
215
|
+
return loadingComponent ?? _jsx(DefaultLoadingComponent, {});
|
|
216
|
+
case 'loaded':
|
|
217
|
+
// Bundle is loaded and will render itself into #dynim-root
|
|
218
|
+
return _jsx(BundleContainer, {});
|
|
219
|
+
case 'error':
|
|
220
|
+
// Show children as fallback with optional error banner
|
|
221
|
+
return (_jsxs(_Fragment, { children: [renderErrorBanner(), children] }));
|
|
222
|
+
case 'no-customization':
|
|
223
|
+
case 'idle':
|
|
224
|
+
default:
|
|
225
|
+
// No customizations or no tenant - render base app
|
|
226
|
+
return children;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
return _jsx(_Fragment, { children: renderContent() });
|
|
230
|
+
}
|
|
231
|
+
export default DynimProvider;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { InferenceProviderProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* InferenceProvider - A React component that dynamically loads tenant-specific bundles.
|
|
4
|
+
*
|
|
5
|
+
* This provider handles the lifecycle of loading, rendering, and cleaning up
|
|
6
|
+
* tenant customization bundles. It supports optional pre-checks to determine
|
|
7
|
+
* if a tenant has customizations before attempting to load them.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <InferenceProvider
|
|
12
|
+
* tenantId={user?.tenantId}
|
|
13
|
+
* bundleUrl={(id) => `https://cdn.example.com/bundles/${id}.js`}
|
|
14
|
+
* loadingComponent={<Spinner />}
|
|
15
|
+
* errorBanner={true}
|
|
16
|
+
* >
|
|
17
|
+
* <YourBaseApp />
|
|
18
|
+
* </InferenceProvider>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function InferenceProvider({ tenantId, bundleUrl, bundleToken, checkCustomizations, loadingComponent, errorBanner, onError, onLoad, children, }: InferenceProviderProps): JSX.Element;
|
|
22
|
+
export default InferenceProvider;
|
|
23
|
+
//# sourceMappingURL=InferenceProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InferenceProvider.d.ts","sourceRoot":"","sources":["../../src/inference/InferenceProvider.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,SAAS,CAAC;AAkHtE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,SAAS,EACT,WAAW,EACX,mBAAmB,EACnB,gBAAgB,EAChB,WAAmB,EACnB,OAAO,EACP,MAAM,EACN,QAAQ,GACT,EAAE,sBAAsB,GAAG,GAAG,CAAC,OAAO,CA8LtC;AAED,eAAe,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* Default loading component shown while bundle is loading
|
|
5
|
+
*/
|
|
6
|
+
function DefaultLoadingComponent() {
|
|
7
|
+
return (_jsx("div", { style: {
|
|
8
|
+
display: 'flex',
|
|
9
|
+
justifyContent: 'center',
|
|
10
|
+
alignItems: 'center',
|
|
11
|
+
padding: '20px',
|
|
12
|
+
}, children: _jsx("span", { children: "Loading customizations..." }) }));
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Default error banner component
|
|
16
|
+
*/
|
|
17
|
+
function DefaultErrorBanner({ error }) {
|
|
18
|
+
return (_jsxs("div", { style: {
|
|
19
|
+
background: '#fee2e2',
|
|
20
|
+
border: '1px solid #ef4444',
|
|
21
|
+
borderRadius: '4px',
|
|
22
|
+
padding: '12px 16px',
|
|
23
|
+
marginBottom: '16px',
|
|
24
|
+
color: '#991b1b',
|
|
25
|
+
}, children: [_jsx("strong", { children: "Customization Error:" }), " ", error.message] }));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Container for rendering tenant bundle content
|
|
29
|
+
*/
|
|
30
|
+
function BundleContainer() {
|
|
31
|
+
return _jsx("div", { id: "dynim-root" });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Loads a script from a URL and returns a promise that resolves when loaded
|
|
35
|
+
*/
|
|
36
|
+
function loadScript(url) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
// Check if script already exists
|
|
39
|
+
const existingScript = document.querySelector(`script[src="${url}"]`);
|
|
40
|
+
if (existingScript) {
|
|
41
|
+
resolve();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const script = document.createElement('script');
|
|
45
|
+
script.src = url;
|
|
46
|
+
script.async = true;
|
|
47
|
+
script.onload = () => resolve();
|
|
48
|
+
script.onerror = () => reject(new Error(`Failed to load bundle from ${url}`));
|
|
49
|
+
document.head.appendChild(script);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Loads a script from a URL with authentication and returns a promise
|
|
54
|
+
*/
|
|
55
|
+
async function loadScriptWithAuth(url, token) {
|
|
56
|
+
const response = await fetch(url, {
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
if (response.status === 401) {
|
|
63
|
+
throw new Error('Bundle token is invalid or expired');
|
|
64
|
+
}
|
|
65
|
+
if (response.status === 404) {
|
|
66
|
+
throw new Error('Bundle not found');
|
|
67
|
+
}
|
|
68
|
+
throw new Error(`Failed to load bundle: ${response.status} ${response.statusText}`);
|
|
69
|
+
}
|
|
70
|
+
const scriptContent = await response.text();
|
|
71
|
+
// Execute the script content
|
|
72
|
+
const script = document.createElement('script');
|
|
73
|
+
script.textContent = scriptContent;
|
|
74
|
+
script.setAttribute('data-dynim-bundle', url);
|
|
75
|
+
document.head.appendChild(script);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Removes a previously loaded script (handles both src and data-dynim-bundle attributes)
|
|
79
|
+
*/
|
|
80
|
+
function removeScript(url) {
|
|
81
|
+
// Try src attribute first (script tag injection)
|
|
82
|
+
let script = document.querySelector(`script[src="${url}"]`);
|
|
83
|
+
if (script) {
|
|
84
|
+
script.remove();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Try data-dynim-bundle attribute (authenticated fetch)
|
|
88
|
+
script = document.querySelector(`script[data-dynim-bundle="${url}"]`);
|
|
89
|
+
if (script) {
|
|
90
|
+
script.remove();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* InferenceProvider - A React component that dynamically loads tenant-specific bundles.
|
|
95
|
+
*
|
|
96
|
+
* This provider handles the lifecycle of loading, rendering, and cleaning up
|
|
97
|
+
* tenant customization bundles. It supports optional pre-checks to determine
|
|
98
|
+
* if a tenant has customizations before attempting to load them.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* <InferenceProvider
|
|
103
|
+
* tenantId={user?.tenantId}
|
|
104
|
+
* bundleUrl={(id) => `https://cdn.example.com/bundles/${id}.js`}
|
|
105
|
+
* loadingComponent={<Spinner />}
|
|
106
|
+
* errorBanner={true}
|
|
107
|
+
* >
|
|
108
|
+
* <YourBaseApp />
|
|
109
|
+
* </InferenceProvider>
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export function InferenceProvider({ tenantId, bundleUrl, bundleToken, checkCustomizations, loadingComponent, errorBanner = false, onError, onLoad, children, }) {
|
|
113
|
+
const [state, setState] = useState({
|
|
114
|
+
status: 'idle',
|
|
115
|
+
error: null,
|
|
116
|
+
currentTenantId: null,
|
|
117
|
+
});
|
|
118
|
+
const loadedUrlRef = useRef(null);
|
|
119
|
+
/**
|
|
120
|
+
* Resolves the bundle URL for a given tenant ID
|
|
121
|
+
*/
|
|
122
|
+
const resolveBundleUrl = useCallback((id) => {
|
|
123
|
+
if (typeof bundleUrl === 'function') {
|
|
124
|
+
return bundleUrl(id);
|
|
125
|
+
}
|
|
126
|
+
// If bundleUrl is a string template, replace {tenantId} placeholder
|
|
127
|
+
return bundleUrl.replace('{tenantId}', id);
|
|
128
|
+
}, [bundleUrl]);
|
|
129
|
+
/**
|
|
130
|
+
* Cleans up previously loaded bundle
|
|
131
|
+
*/
|
|
132
|
+
const cleanup = useCallback(() => {
|
|
133
|
+
if (loadedUrlRef.current) {
|
|
134
|
+
removeScript(loadedUrlRef.current);
|
|
135
|
+
loadedUrlRef.current = null;
|
|
136
|
+
}
|
|
137
|
+
// Clear the bundle render target
|
|
138
|
+
const root = document.getElementById('dynim-root');
|
|
139
|
+
if (root) {
|
|
140
|
+
root.innerHTML = '';
|
|
141
|
+
}
|
|
142
|
+
}, []);
|
|
143
|
+
/**
|
|
144
|
+
* Main effect to handle tenant bundle loading
|
|
145
|
+
*/
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
// No tenant - reset to idle and show children
|
|
148
|
+
if (!tenantId) {
|
|
149
|
+
cleanup();
|
|
150
|
+
setState({
|
|
151
|
+
status: 'idle',
|
|
152
|
+
error: null,
|
|
153
|
+
currentTenantId: null,
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Same tenant already loaded - do nothing
|
|
158
|
+
if (state.currentTenantId === tenantId && state.status === 'loaded') {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
let cancelled = false;
|
|
162
|
+
async function loadBundle() {
|
|
163
|
+
try {
|
|
164
|
+
// Step 1: Check if tenant has customizations (if check function provided)
|
|
165
|
+
if (checkCustomizations) {
|
|
166
|
+
setState((prev) => ({ ...prev, status: 'checking' }));
|
|
167
|
+
const hasCustomizations = await checkCustomizations(tenantId);
|
|
168
|
+
if (cancelled)
|
|
169
|
+
return;
|
|
170
|
+
if (!hasCustomizations) {
|
|
171
|
+
cleanup();
|
|
172
|
+
setState({
|
|
173
|
+
status: 'no-customization',
|
|
174
|
+
error: null,
|
|
175
|
+
currentTenantId: tenantId,
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Step 2: Load the bundle
|
|
181
|
+
setState((prev) => ({ ...prev, status: 'loading' }));
|
|
182
|
+
// Clean up previous bundle
|
|
183
|
+
cleanup();
|
|
184
|
+
const url = resolveBundleUrl(tenantId);
|
|
185
|
+
// Use authenticated fetch if bundleToken is provided, otherwise use script tag
|
|
186
|
+
if (bundleToken) {
|
|
187
|
+
await loadScriptWithAuth(url, bundleToken);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
await loadScript(url);
|
|
191
|
+
}
|
|
192
|
+
if (cancelled) {
|
|
193
|
+
removeScript(url);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
loadedUrlRef.current = url;
|
|
197
|
+
setState({
|
|
198
|
+
status: 'loaded',
|
|
199
|
+
error: null,
|
|
200
|
+
currentTenantId: tenantId,
|
|
201
|
+
});
|
|
202
|
+
onLoad?.(tenantId);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (cancelled)
|
|
206
|
+
return;
|
|
207
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
208
|
+
setState({
|
|
209
|
+
status: 'error',
|
|
210
|
+
error,
|
|
211
|
+
currentTenantId: tenantId,
|
|
212
|
+
});
|
|
213
|
+
onError?.(error, tenantId);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
loadBundle();
|
|
217
|
+
return () => {
|
|
218
|
+
cancelled = true;
|
|
219
|
+
};
|
|
220
|
+
}, [
|
|
221
|
+
tenantId,
|
|
222
|
+
bundleToken,
|
|
223
|
+
checkCustomizations,
|
|
224
|
+
resolveBundleUrl,
|
|
225
|
+
cleanup,
|
|
226
|
+
onError,
|
|
227
|
+
onLoad,
|
|
228
|
+
state.currentTenantId,
|
|
229
|
+
state.status,
|
|
230
|
+
]);
|
|
231
|
+
/**
|
|
232
|
+
* Cleanup on unmount
|
|
233
|
+
*/
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
return () => {
|
|
236
|
+
cleanup();
|
|
237
|
+
};
|
|
238
|
+
}, [cleanup]);
|
|
239
|
+
// Render error banner if enabled and there's an error
|
|
240
|
+
const renderErrorBanner = () => {
|
|
241
|
+
if (!errorBanner || state.status !== 'error' || !state.error) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
if (errorBanner === true) {
|
|
245
|
+
return _jsx(DefaultErrorBanner, { error: state.error });
|
|
246
|
+
}
|
|
247
|
+
return errorBanner;
|
|
248
|
+
};
|
|
249
|
+
// Determine what to render based on state
|
|
250
|
+
const renderContent = () => {
|
|
251
|
+
switch (state.status) {
|
|
252
|
+
case 'checking':
|
|
253
|
+
case 'loading':
|
|
254
|
+
return loadingComponent ?? _jsx(DefaultLoadingComponent, {});
|
|
255
|
+
case 'loaded':
|
|
256
|
+
// Bundle is loaded and will render itself into #dynim-root
|
|
257
|
+
return _jsx(BundleContainer, {});
|
|
258
|
+
case 'error':
|
|
259
|
+
// Show children as fallback with optional error banner
|
|
260
|
+
return (_jsxs(_Fragment, { children: [renderErrorBanner(), children] }));
|
|
261
|
+
case 'no-customization':
|
|
262
|
+
case 'idle':
|
|
263
|
+
default:
|
|
264
|
+
// No customizations or no tenant - render base app
|
|
265
|
+
return children;
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
return _jsx(_Fragment, { children: renderContent() });
|
|
269
|
+
}
|
|
270
|
+
export default InferenceProvider;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { DynimSDKConfig, DynimGlobal } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates and registers the Dynim SDK on the window object.
|
|
4
|
+
* This makes React, ReactDOM, hooks, and contexts available to tenant bundles.
|
|
5
|
+
*
|
|
6
|
+
* @param config - Configuration object containing React, ReactDOM, hooks, and contexts
|
|
7
|
+
* @returns The created SDK object
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* import { createDynimSDK } from 'dynim-react';
|
|
12
|
+
* import React from 'react';
|
|
13
|
+
* import ReactDOM from 'react-dom';
|
|
14
|
+
* import { useAuth, useNotifications } from './hooks';
|
|
15
|
+
* import { AuthContext, NotificationContext } from './contexts';
|
|
16
|
+
*
|
|
17
|
+
* createDynimSDK({
|
|
18
|
+
* React,
|
|
19
|
+
* ReactDOM,
|
|
20
|
+
* hooks: { useAuth, useNotifications },
|
|
21
|
+
* contexts: { AuthContext, NotificationContext }
|
|
22
|
+
* });
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createDynimSDK(config: DynimSDKConfig): DynimGlobal;
|
|
26
|
+
/**
|
|
27
|
+
* Gets the current Dynim SDK instance from the window object.
|
|
28
|
+
* Useful for tenant bundles to access the SDK.
|
|
29
|
+
*
|
|
30
|
+
* @returns The SDK instance or undefined if not initialized
|
|
31
|
+
*/
|
|
32
|
+
export declare function getDynimSDK(): DynimGlobal | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Checks if the Dynim SDK has been initialized.
|
|
35
|
+
*
|
|
36
|
+
* @returns true if the SDK is available
|
|
37
|
+
*/
|
|
38
|
+
export declare function isDynimSDKReady(): boolean;
|
|
39
|
+
//# sourceMappingURL=createDynimSDK.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createDynimSDK.d.ts","sourceRoot":"","sources":["../../src/inference/createDynimSDK.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,cAAc,GAAG,WAAW,CAqBlE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,WAAW,GAAG,SAAS,CAKrD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates and registers the Dynim SDK on the window object.
|
|
3
|
+
* This makes React, ReactDOM, hooks, and contexts available to tenant bundles.
|
|
4
|
+
*
|
|
5
|
+
* @param config - Configuration object containing React, ReactDOM, hooks, and contexts
|
|
6
|
+
* @returns The created SDK object
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { createDynimSDK } from 'dynim-react';
|
|
11
|
+
* import React from 'react';
|
|
12
|
+
* import ReactDOM from 'react-dom';
|
|
13
|
+
* import { useAuth, useNotifications } from './hooks';
|
|
14
|
+
* import { AuthContext, NotificationContext } from './contexts';
|
|
15
|
+
*
|
|
16
|
+
* createDynimSDK({
|
|
17
|
+
* React,
|
|
18
|
+
* ReactDOM,
|
|
19
|
+
* hooks: { useAuth, useNotifications },
|
|
20
|
+
* contexts: { AuthContext, NotificationContext }
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function createDynimSDK(config) {
|
|
25
|
+
const { React, ReactDOM, hooks = {}, contexts = {}, globals = {} } = config;
|
|
26
|
+
const sdk = {
|
|
27
|
+
React,
|
|
28
|
+
ReactDOM,
|
|
29
|
+
hooks,
|
|
30
|
+
contexts,
|
|
31
|
+
...globals,
|
|
32
|
+
};
|
|
33
|
+
// Register on window for tenant bundles to access
|
|
34
|
+
if (typeof window !== 'undefined') {
|
|
35
|
+
window.__DYNIM__ = sdk;
|
|
36
|
+
// Also expose React and ReactDOM directly on window for bundles using standard imports
|
|
37
|
+
window.React = React;
|
|
38
|
+
window.ReactDOM = ReactDOM;
|
|
39
|
+
}
|
|
40
|
+
return sdk;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Gets the current Dynim SDK instance from the window object.
|
|
44
|
+
* Useful for tenant bundles to access the SDK.
|
|
45
|
+
*
|
|
46
|
+
* @returns The SDK instance or undefined if not initialized
|
|
47
|
+
*/
|
|
48
|
+
export function getDynimSDK() {
|
|
49
|
+
if (typeof window !== 'undefined') {
|
|
50
|
+
return window.__DYNIM__;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Checks if the Dynim SDK has been initialized.
|
|
56
|
+
*
|
|
57
|
+
* @returns true if the SDK is available
|
|
58
|
+
*/
|
|
59
|
+
export function isDynimSDKReady() {
|
|
60
|
+
return typeof window !== 'undefined' && window.__DYNIM__ !== undefined;
|
|
61
|
+
}
|