@oceanum/eidos 0.9.1 → 0.9.2

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/src/lib/react.tsx DELETED
@@ -1,70 +0,0 @@
1
- import { createContext, useContext, useEffect, useRef, useState, ReactNode } from 'react';
2
- import { useSnapshot } from 'valtio';
3
- import { Proxy } from 'valtio/vanilla';
4
- import { EidosSpec } from '../schema/interfaces';
5
- import { render, RenderOptions } from './render';
6
-
7
- // Context to hold the EIDOS spec proxy
8
- const EidosSpecContext = createContext<Proxy<EidosSpec> | null>(null);
9
-
10
- // Hook to get the spec proxy from context
11
- // Can be used for both reading and mutations - valtio tracks reads automatically
12
- export const useEidosSpec = () => {
13
- const spec = useContext(EidosSpecContext);
14
- if (!spec) {
15
- throw new Error('useEidosSpec must be used within an EidosProvider');
16
- }
17
- return spec;
18
- };
19
-
20
- // Props for the EidosProvider component
21
- interface EidosProviderProps {
22
- children: ReactNode;
23
- initialSpec: EidosSpec;
24
- options?: Omit<RenderOptions, 'id'>;
25
- containerStyle?: React.CSSProperties;
26
- onInitialized?: (spec: Proxy<EidosSpec>) => void;
27
- }
28
-
29
- // Provider component that renders EIDOS and provides spec to children
30
- export const EidosProvider = ({
31
- children,
32
- initialSpec,
33
- options = {},
34
- containerStyle = { width: '100%', height: '100%', position: 'absolute' },
35
- onInitialized,
36
- }: EidosProviderProps) => {
37
- const containerRef = useRef<HTMLDivElement>(null);
38
- const [spec, setSpec] = useState<Proxy<EidosSpec> | null>(null);
39
- const initializedRef = useRef(false);
40
-
41
- useEffect(() => {
42
- if (!containerRef.current || initializedRef.current) return;
43
-
44
- initializedRef.current = true;
45
-
46
- const initEidos = async () => {
47
- try {
48
- const result = await render(containerRef.current!, initialSpec, options);
49
- setSpec(result.spec);
50
- onInitialized?.(result.spec);
51
- } catch (error) {
52
- console.error('Failed to initialize EIDOS:', error);
53
- initializedRef.current = false;
54
- }
55
- };
56
-
57
- initEidos();
58
- }, [initialSpec, options, onInitialized]);
59
-
60
- return (
61
- <div style={{ position: 'relative', width: '100%', height: '100%' }}>
62
- <div ref={containerRef} style={containerStyle} />
63
- {spec && (
64
- <EidosSpecContext.Provider value={spec}>
65
- {children}
66
- </EidosSpecContext.Provider>
67
- )}
68
- </div>
69
- );
70
- };
package/src/lib/render.ts DELETED
@@ -1,176 +0,0 @@
1
- import { proxy, subscribe, snapshot, Proxy } from 'valtio/vanilla';
2
- import { validateSchema } from './eidosmodel';
3
- import { EidosSpec } from '../schema/interfaces';
4
-
5
- const DEFAULT_RENDERER = 'https://render.eidos.oceanum.io';
6
-
7
- /**
8
- * Options for rendering an EIDOS spec
9
- */
10
- export interface RenderOptions {
11
- /** The unique identifier for the EIDOS spec (optional, defaults to spec.id) */
12
- id?: string;
13
- /** Optional callback for handling events from the renderer */
14
- eventListener?: (payload: unknown) => void;
15
- /** URL of the EIDOS renderer */
16
- renderer?: string;
17
- /** Authentication token to pass to the renderer for data fetching */
18
- authToken?: string | (() => string | Promise<string>);
19
- }
20
-
21
- /**
22
- * Result from the render function
23
- */
24
- export interface RenderResult {
25
- /** The reactive EIDOS spec proxy */
26
- spec: Proxy<EidosSpec>;
27
- /** Update the auth token sent to the renderer */
28
- updateAuth: (
29
- token: string | (() => string | Promise<string>),
30
- ) => Promise<void>;
31
- /** The iframe element */
32
- iframe: HTMLIFrameElement;
33
- /** Destroy the renderer and clean up resources */
34
- destroy: () => void;
35
- }
36
-
37
- /**
38
- * Embed the EIDOS iframe and set up message passing
39
- * @param element HTML element to render the EIDOS spec into
40
- * @param spec The EIDOS specification object
41
- * @param options Render options including id, eventListener, renderer URL, and authToken
42
- * @returns A RenderResult object containing the spec proxy and control methods
43
- */
44
- const render = async (
45
- element: HTMLElement,
46
- spec: EidosSpec,
47
- options: RenderOptions = {},
48
- ): Promise<RenderResult> => {
49
- const { id, eventListener, renderer = DEFAULT_RENDERER, authToken } = options;
50
-
51
- // Check if this container is already initialized
52
- // We check both for an existing iframe and a marker on the container itself
53
- // This prevents double-mounting even if destroy() was called but the container is being reused
54
- const existingIframe = element.querySelector('iframe[data-eidos-renderer]');
55
- const isInitialized = element.getAttribute('data-eidos-initialized') === 'true';
56
-
57
- if (existingIframe || isInitialized) {
58
- throw new Error('EIDOS renderer already mounted in this container. Call destroy() before re-rendering.');
59
- }
60
-
61
- // Mark container as initialized
62
- element.setAttribute('data-eidos-initialized', 'true');
63
-
64
- // Validate the spec before creating proxy
65
- try {
66
- await validateSchema(spec);
67
- } catch (e: any) {
68
- throw new Error(`Invalid Eidos Spec: ${e.message}`);
69
- }
70
-
71
- // Create Valtio proxy - naturally mutable, no unprotect needed
72
- const eidos = proxy(structuredClone(spec));
73
- const _id = id || spec.id;
74
-
75
- return new Promise((resolve, reject) => {
76
- const iframe = document.createElement('iframe');
77
- iframe.src = `${renderer}?id=${_id}`;
78
- iframe.width = '100%';
79
- iframe.height = '100%';
80
- iframe.frameBorder = '0';
81
- // Mark this as an EIDOS iframe for duplicate detection
82
- iframe.setAttribute('data-eidos-renderer', 'true');
83
- element.appendChild(iframe);
84
-
85
- let messageHandler: ((event: MessageEvent) => void) | null = null;
86
- let unsubscribe: (() => void) | null = null;
87
-
88
- iframe.onload = () => {
89
- const win = iframe.contentWindow;
90
-
91
- // Helper to send auth token to iframe
92
- const sendAuth = async (
93
- token: string | (() => string | Promise<string>),
94
- ) => {
95
- const resolvedToken =
96
- typeof token === 'function' ? await token() : token;
97
- win?.postMessage(
98
- {
99
- id: _id,
100
- type: 'auth',
101
- payload: resolvedToken,
102
- },
103
- '*',
104
- );
105
- };
106
-
107
- messageHandler = (event: MessageEvent) => {
108
- if (event.source !== win) return;
109
-
110
- // Check ID only if both message and expected ID exist
111
- // Some EIDOS renderers may not include ID in event messages
112
- if (event.data.id && event.data.id !== _id) return;
113
-
114
- if (event.data.type === 'init') {
115
- // Send initial spec
116
- win?.postMessage(
117
- {
118
- id: _id,
119
- type: 'spec',
120
- payload: structuredClone(eidos),
121
- },
122
- '*',
123
- );
124
- // Send auth token on init if provided
125
- if (authToken) {
126
- sendAuth(authToken);
127
- }
128
- } else if (event.data.type || event.data.action || event.data.control) {
129
- // Process as event - EIDOS events may not have 'type' but have 'action' and 'control'
130
- eventListener?.(event.data);
131
- }
132
- };
133
- window.addEventListener('message', messageHandler);
134
-
135
- // Subscribe to changes and send patches to renderer
136
- unsubscribe = subscribe(eidos, () => {
137
- win?.postMessage(
138
- {
139
- id: _id,
140
- type: 'spec',
141
- payload: snapshot(eidos),
142
- },
143
- '*',
144
- );
145
- });
146
-
147
- // Send initial auth token if provided (in case init already happened)
148
- if (authToken) {
149
- sendAuth(authToken);
150
- }
151
-
152
- resolve({
153
- spec: eidos,
154
- updateAuth: sendAuth,
155
- iframe,
156
- destroy: () => {
157
- if (messageHandler) {
158
- window.removeEventListener('message', messageHandler);
159
- }
160
- if (unsubscribe) {
161
- unsubscribe();
162
- }
163
- iframe.remove();
164
- // Clear the initialization marker when explicitly destroyed
165
- element.removeAttribute('data-eidos-initialized');
166
- },
167
- });
168
- };
169
-
170
- iframe.onerror = () => {
171
- reject(new Error('Failed to load EIDOS renderer'));
172
- };
173
- });
174
- };
175
-
176
- export { render };