@myop/react-native 0.0.3 → 0.0.5

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 CHANGED
@@ -10,7 +10,9 @@ Embed [Myop](https://myop.dev) components in your React Native applications.
10
10
  - Load Myop components by ID or custom configuration
11
11
  - Two-way communication between React Native and embedded components
12
12
  - Customizable loading and fallback states
13
- - Support for both V1 and V2 component formats
13
+ - Component preloading for instant rendering
14
+ - Full component proxy API for DOM manipulation
15
+ - Configurable scroll, zoom, and text selection
14
16
  - TypeScript support
15
17
 
16
18
  ## Installation
@@ -90,23 +92,36 @@ Listen for events from your component's `myop_cta_handler`:
90
92
  />
91
93
  ```
92
94
 
93
- ### Executing Commands
95
+ ### Component Proxy API
94
96
 
95
- Access the component instance for direct command execution:
97
+ Access the component instance for direct manipulation:
96
98
 
97
99
  ```tsx
98
- const [component, setComponent] = useState(null);
100
+ import { IMyopComponentProxy } from '@myop/react-native';
101
+
102
+ const [component, setComponent] = useState<IMyopComponentProxy | null>(null);
99
103
 
100
104
  <MyopComponent
101
105
  componentId="abc123"
102
- onLoad={(instance) => setComponent(() => instance)}
106
+ onLoad={(proxy) => setComponent(proxy)}
103
107
  style={{ flex: 1 }}
104
108
  />
105
109
 
106
- // Execute commands on the component
107
- const handleReset = () => {
108
- component?.('.reset()');
109
- };
110
+ // Send data to component
111
+ component?.props.myop_init_interface({ theme: 'dark' });
112
+
113
+ // DOM manipulation
114
+ component?.element.style.set('opacity', '0.5');
115
+ component?.element.set('style.background', 'red');
116
+
117
+ // Get values (async)
118
+ const opacity = await component?.element.style.get('opacity');
119
+ const scrollTop = await component?.element.get('scrollTop');
120
+
121
+ // Lifecycle methods
122
+ component?.hide();
123
+ component?.show();
124
+ component?.dispose();
110
125
  ```
111
126
 
112
127
  ### Custom Loading State
@@ -182,6 +197,62 @@ Target different deployment environments:
182
197
  />
183
198
  ```
184
199
 
200
+ ### WebView Behavior
201
+
202
+ Control scroll, zoom, and text selection:
203
+
204
+ ```tsx
205
+ <MyopComponent
206
+ componentId="abc123"
207
+ scrollEnabled={true} // Enable scrolling (default: false)
208
+ zoomEnabled={true} // Enable pinch-to-zoom (default: false)
209
+ selectionEnabled={true} // Enable text selection (default: false)
210
+ style={{ flex: 1 }}
211
+ />
212
+ ```
213
+
214
+ ### Preloading Components
215
+
216
+ Preload components for instant rendering when they're displayed:
217
+
218
+ ```tsx
219
+ import { preloadComponents, isPreloaded } from '@myop/react-native';
220
+
221
+ // Preload on app startup or before navigating
222
+ await preloadComponents(['component-1', 'component-2']);
223
+
224
+ // Check if component is preloaded
225
+ if (isPreloaded('component-1')) {
226
+ // Component will render instantly without loader
227
+ }
228
+ ```
229
+
230
+ ### Configuration APIs
231
+
232
+ Configure the CloudRepository for custom endpoints or local development:
233
+
234
+ ```tsx
235
+ import {
236
+ enableLocalDev,
237
+ setCloudRepositoryUrl,
238
+ setCloudRepository,
239
+ setEnvironment,
240
+ getCloudRepository
241
+ } from '@myop/react-native';
242
+
243
+ // Enable local development mode (connects to localhost:9292)
244
+ enableLocalDev();
245
+
246
+ // Use a custom endpoint
247
+ setCloudRepositoryUrl('https://custom.myop.dev');
248
+
249
+ // Set default environment for all components
250
+ setEnvironment('staging');
251
+
252
+ // Get the current CloudRepository instance
253
+ const repo = getCloudRepository();
254
+ ```
255
+
185
256
  ## API Reference
186
257
 
187
258
  ### MyopComponent Props
@@ -192,13 +263,16 @@ Target different deployment environments:
192
263
  | `componentConfig` | `IComponentInstanceConfig` | - | Full component configuration object |
193
264
  | `data` | `any` | - | Data passed to `myop_init_interface` |
194
265
  | `on` | `(action: string, payload?: any) => void` | - | Handler for `myop_cta_handler` events |
195
- | `onLoad` | `(instance: (cmd: string) => void) => void` | - | Callback when component loads |
266
+ | `onLoad` | `(proxy: IMyopComponentProxy) => void` | - | Callback with component proxy when loaded |
196
267
  | `onError` | `(error: string) => void` | - | Callback when component fails to load |
197
268
  | `loader` | `ReactNode` | `<MyopLoader />` | Custom loading component |
198
269
  | `fallback` | `ReactNode` | `<MyopFallback />` | Custom fallback component |
199
270
  | `fadeDuration` | `number` | `200` | Loader fade-out duration in ms |
200
271
  | `environment` | `string` | `"production"` | Target environment |
201
- | `v1Mode` | `boolean` | `false` | Use V1 component format |
272
+ | `preview` | `boolean` | `false` | Load preview version of component |
273
+ | `scrollEnabled` | `boolean` | `false` | Enable WebView scrolling |
274
+ | `zoomEnabled` | `boolean` | `false` | Enable pinch-to-zoom |
275
+ | `selectionEnabled` | `boolean` | `false` | Enable text selection |
202
276
  | `style` | `StyleProp<ViewStyle>` | - | Container styles |
203
277
 
204
278
  Either `componentId` or `componentConfig` must be provided.
@@ -211,9 +285,43 @@ Either `componentId` or `componentConfig` must be provided.
211
285
  | `MyopLoader` | Default animated loading state |
212
286
  | `MyopFallback` | Default error/fallback state |
213
287
 
288
+ ### Exported Functions
289
+
290
+ | Function | Description |
291
+ |----------|-------------|
292
+ | `preloadComponents(ids, env?, preview?)` | Preload components for instant rendering |
293
+ | `isPreloaded(componentId, env?, preview?)` | Check if a component is cached |
294
+ | `enableLocalDev()` | Enable local development mode (localhost:9292) |
295
+ | `setCloudRepositoryUrl(url)` | Set a custom CloudRepository URL |
296
+ | `setCloudRepository(repository)` | Set a custom CloudRepository instance |
297
+ | `setEnvironment(env)` | Set the default environment |
298
+ | `getCloudRepository()` | Get the current CloudRepository instance |
299
+
214
300
  ### Types
215
301
 
216
302
  ```typescript
303
+ interface IMyopComponentProxy {
304
+ id: string;
305
+ props: {
306
+ myop_init_interface: (data: any) => void;
307
+ myop_cta_handler: ((action: string, payload?: any) => void) | null;
308
+ };
309
+ element: IElementProxy;
310
+ dispose: () => void;
311
+ hide: () => void;
312
+ show: () => void;
313
+ inspect: () => void;
314
+ }
315
+
316
+ interface IElementProxy {
317
+ set: (path: string, value: any) => void;
318
+ get: (path: string) => Promise<any>;
319
+ style: {
320
+ set: (property: string, value: string) => void;
321
+ get: (property: string) => Promise<string>;
322
+ };
323
+ }
324
+
217
325
  interface IComponentInstanceConfig {
218
326
  id: string;
219
327
  componentId: string;
@@ -1,19 +1,59 @@
1
1
  import React, { ReactNode } from 'react';
2
2
  import { StyleProp, ViewStyle } from 'react-native';
3
3
  import type { IComponentInstanceConfig } from './types';
4
+ /**
5
+ * Element proxy for accessing/modifying the component's DOM element
6
+ */
7
+ export interface IElementProxy {
8
+ /** Set a property on the element (e.g., element.style.background = 'red') */
9
+ set: (path: string, value: any) => void;
10
+ /** Get a property from the element (e.g., element.style.background) - returns Promise */
11
+ get: (path: string) => Promise<any>;
12
+ /** Style proxy for convenient access */
13
+ style: {
14
+ set: (property: string, value: string) => void;
15
+ get: (property: string) => Promise<string>;
16
+ };
17
+ }
18
+ /**
19
+ * Proxy interface for window.myopComponent in WebView
20
+ * Mirrors the IMyopComponent interface from @myop/sdk
21
+ */
22
+ export interface IMyopComponentProxy {
23
+ /** Component ID */
24
+ id: string;
25
+ /** Props object with myop_init_interface and myop_cta_handler */
26
+ props: {
27
+ myop_init_interface: (data: any) => void;
28
+ myop_cta_handler: ((action: string, payload?: any) => void) | null;
29
+ };
30
+ /** Element proxy for DOM manipulation */
31
+ element: IElementProxy;
32
+ /** Dispose the component */
33
+ dispose: () => void;
34
+ /** Hide the component */
35
+ hide: () => void;
36
+ /** Show the component */
37
+ show: () => void;
38
+ /** Inspect the component (debug) */
39
+ inspect: () => void;
40
+ }
4
41
  interface IPropTypes {
5
42
  style?: StyleProp<ViewStyle> | undefined;
6
43
  componentId?: string;
7
44
  componentConfig?: IComponentInstanceConfig;
8
- onLoad?: (myopComponent: (command: string) => void) => void;
45
+ onLoad?: (myopComponent: IMyopComponentProxy) => void;
9
46
  onError?: (error: string) => void;
10
47
  on?: (action: string, payload?: any) => void;
11
48
  data?: any;
12
49
  loader?: ReactNode;
13
50
  fallback?: ReactNode;
14
51
  fadeDuration?: number;
15
- v1Mode?: boolean;
16
52
  environment?: string;
53
+ preview?: boolean;
54
+ scrollEnabled?: boolean;
55
+ zoomEnabled?: boolean;
56
+ selectionEnabled?: boolean;
17
57
  }
18
58
  export declare const MyopComponent: (props: IPropTypes) => React.JSX.Element;
19
59
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"MyopComponent.d.ts","sourceRoot":"","sources":["../src/MyopComponent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAmB,SAAS,EAAuD,MAAM,OAAO,CAAC;AAG/G,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,SAAS,CAAC;AAMtD,UAAU,UAAU;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,KAAK,IAAI,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7C,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,UAAU,sBA8N9C,CAAA"}
1
+ {"version":3,"file":"MyopComponent.d.ts","sourceRoot":"","sources":["../src/MyopComponent.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAmB,SAAS,EAAuD,MAAM,OAAO,CAAC;AAG/G,OAAO,EAAC,SAAS,EAAE,SAAS,EAAC,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,SAAS,CAAC;AAuEtD;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,6EAA6E;IAC7E,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACxC,yFAAyF;IACzF,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,wCAAwC;IACxC,KAAK,EAAE;QACH,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;QAC/C,GAAG,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;KAC9C,CAAC;CACL;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAChC,mBAAmB;IACnB,EAAE,EAAE,MAAM,CAAC;IAEX,iEAAiE;IACjE,KAAK,EAAE;QACH,mBAAmB,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;QACzC,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;KACtE,CAAC;IAEF,yCAAyC;IACzC,OAAO,EAAE,aAAa,CAAC;IAEvB,4BAA4B;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IAEpB,yBAAyB;IACzB,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,yBAAyB;IACzB,IAAI,EAAE,MAAM,IAAI,CAAC;IAEjB,oCAAoC;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,UAAU,UAAU;IAChB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,SAAS,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,wBAAwB,CAAC;IAC3C,MAAM,CAAC,EAAE,CAAC,aAAa,EAAE,mBAAmB,KAAK,IAAI,CAAC;IACtD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7C,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,GAAI,OAAO,UAAU,sBAkU9C,CAAA"}
@@ -38,13 +38,73 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_1 = require("react-native");
39
39
  const react_native_webview_1 = require("react-native-webview");
40
40
  const componentHost_html_js_1 = require("./componentHost.html.js");
41
+ const getHtml = (zoomEnabled, selectionEnabled) => {
42
+ let html = componentHost_html_js_1.HTML;
43
+ // Disable zoom
44
+ if (!zoomEnabled) {
45
+ html = html.replace('width=device-width, initial-scale=1.0', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no');
46
+ }
47
+ // Disable text selection for native feel
48
+ if (!selectionEnabled) {
49
+ const noSelectCSS = `
50
+ *, *::before, *::after {
51
+ -webkit-user-select: none !important;
52
+ -webkit-touch-callout: none !important;
53
+ -webkit-tap-highlight-color: transparent !important;
54
+ user-select: none !important;
55
+ }
56
+ input, textarea, [contenteditable="true"] {
57
+ -webkit-user-select: auto !important;
58
+ user-select: auto !important;
59
+ }
60
+ `;
61
+ html = html.replace('</style>', noSelectCSS + '</style>');
62
+ // Also inject into iframes via script
63
+ html = html.replace('</body>', `<script>
64
+ (function() {
65
+ const css = \`${noSelectCSS}\`;
66
+ function injectStyle(doc) {
67
+ try {
68
+ const style = doc.createElement('style');
69
+ style.textContent = css;
70
+ doc.head.appendChild(style);
71
+ } catch(e) {}
72
+ }
73
+ // Inject into any iframe that loads
74
+ const observer = new MutationObserver(function(mutations) {
75
+ document.querySelectorAll('iframe').forEach(function(iframe) {
76
+ try {
77
+ if (iframe.contentDocument && !iframe.contentDocument.__noSelectInjected) {
78
+ iframe.contentDocument.__noSelectInjected = true;
79
+ injectStyle(iframe.contentDocument);
80
+ // Also observe iframe for nested iframes
81
+ iframe.contentWindow.addEventListener('load', function() {
82
+ injectStyle(iframe.contentDocument);
83
+ });
84
+ }
85
+ } catch(e) {}
86
+ });
87
+ });
88
+ observer.observe(document.body, { childList: true, subtree: true });
89
+ })();
90
+ </script></body>`);
91
+ }
92
+ return html;
93
+ };
41
94
  const MyopLoader_1 = require("./MyopLoader");
42
95
  const MyopFallback_1 = require("./MyopFallback");
96
+ const index_1 = require("./index");
43
97
  const MyopComponent = (props) => {
44
- var _a, _b;
98
+ var _a, _b, _c, _d, _e;
45
99
  const webviewRef = (0, react_1.useRef)(null);
46
100
  const loaderRef = (0, react_1.useRef)(null);
47
- const [showLoader, setShowLoader] = (0, react_1.useState)(true);
101
+ const isCancelled = (0, react_1.useRef)(false);
102
+ const pendingGetRequests = (0, react_1.useRef)(new Map());
103
+ // Don't show loader if component is already preloaded/cached
104
+ const componentIsPreloaded = props.componentId
105
+ ? (0, index_1.isPreloaded)(props.componentId, props.environment, props.preview)
106
+ : false;
107
+ const [showLoader, setShowLoader] = (0, react_1.useState)(!componentIsPreloaded);
48
108
  const [showFallback, setShowFallback] = (0, react_1.useState)(false);
49
109
  const [isComponentLoaded, setIsComponentLoaded] = (0, react_1.useState)(false);
50
110
  const fadeDuration = (_a = props.fadeDuration) !== null && _a !== void 0 ? _a : 200;
@@ -54,8 +114,8 @@ const MyopComponent = (props) => {
54
114
  return;
55
115
  const encoded = encodeURIComponent(JSON.stringify(data));
56
116
  webviewRef.current.injectJavaScript(`
57
- if (window.myopComponent && window.myopComponent.element && window.myopComponent.element.contentWindow && window.myopComponent.element.contentWindow.myop_init_interface) {
58
- window.myopComponent.element.contentWindow.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
117
+ if (window.myopComponent) {
118
+ window.myopComponent.props.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
59
119
  }
60
120
  true;
61
121
  `);
@@ -78,12 +138,127 @@ const MyopComponent = (props) => {
78
138
  setShowLoader(false);
79
139
  }
80
140
  }, [fadeDuration]);
81
- const myopComponent = (command) => {
82
- var _a;
83
- (_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`
84
- window.myopComponent${command}
85
- `);
86
- };
141
+ // Create proxy object for window.myopComponent
142
+ const createMyopComponentProxy = (0, react_1.useCallback)((componentId) => {
143
+ const injectJS = (js) => {
144
+ var _a;
145
+ (_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`${js}; true;`);
146
+ };
147
+ // Generate unique request ID for async get operations
148
+ const generateRequestId = () => `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
149
+ // Element proxy for DOM manipulation
150
+ const elementProxy = {
151
+ set: (path, value) => {
152
+ const encoded = encodeURIComponent(JSON.stringify(value));
153
+ injectJS(`
154
+ if (window.myopComponent && window.myopComponent.element) {
155
+ const parts = '${path}'.split('.');
156
+ let target = window.myopComponent.element;
157
+ for (let i = 0; i < parts.length - 1; i++) {
158
+ target = target[parts[i]];
159
+ }
160
+ target[parts[parts.length - 1]] = JSON.parse(decodeURIComponent('${encoded}'));
161
+ }
162
+ `);
163
+ },
164
+ get: (path) => {
165
+ return new Promise((resolve) => {
166
+ const requestId = generateRequestId();
167
+ pendingGetRequests.current.set(requestId, resolve);
168
+ injectJS(`
169
+ if (window.myopComponent && window.myopComponent.element) {
170
+ const parts = '${path}'.split('.');
171
+ let value = window.myopComponent.element;
172
+ for (const part of parts) {
173
+ value = value[part];
174
+ }
175
+ window.ReactNativeWebView.postMessage('GET_RESULT:' + JSON.stringify({
176
+ requestId: '${requestId}',
177
+ value: value
178
+ }));
179
+ } else {
180
+ window.ReactNativeWebView.postMessage('GET_RESULT:' + JSON.stringify({
181
+ requestId: '${requestId}',
182
+ value: undefined
183
+ }));
184
+ }
185
+ `);
186
+ });
187
+ },
188
+ style: {
189
+ set: (property, value) => {
190
+ injectJS(`
191
+ if (window.myopComponent && window.myopComponent.element) {
192
+ window.myopComponent.element.style['${property}'] = '${value}';
193
+ }
194
+ `);
195
+ },
196
+ get: (property) => {
197
+ return new Promise((resolve) => {
198
+ const requestId = generateRequestId();
199
+ pendingGetRequests.current.set(requestId, resolve);
200
+ injectJS(`
201
+ if (window.myopComponent && window.myopComponent.element) {
202
+ const value = window.myopComponent.element.style['${property}'];
203
+ window.ReactNativeWebView.postMessage('GET_RESULT:' + JSON.stringify({
204
+ requestId: '${requestId}',
205
+ value: value
206
+ }));
207
+ } else {
208
+ window.ReactNativeWebView.postMessage('GET_RESULT:' + JSON.stringify({
209
+ requestId: '${requestId}',
210
+ value: undefined
211
+ }));
212
+ }
213
+ `);
214
+ });
215
+ }
216
+ }
217
+ };
218
+ return {
219
+ id: componentId,
220
+ props: {
221
+ myop_init_interface: (data) => {
222
+ const encoded = encodeURIComponent(JSON.stringify(data));
223
+ injectJS(`
224
+ if (window.myopComponent && window.myopComponent.props.myop_init_interface) {
225
+ window.myopComponent.props.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
226
+ }
227
+ `);
228
+ },
229
+ myop_cta_handler: null // CTA is handled via message passing (props.on)
230
+ },
231
+ element: elementProxy,
232
+ dispose: () => {
233
+ injectJS(`
234
+ if (window.myopComponent && window.myopComponent.dispose) {
235
+ window.myopComponent.dispose();
236
+ }
237
+ `);
238
+ },
239
+ hide: () => {
240
+ injectJS(`
241
+ if (window.myopComponent && window.myopComponent.hide) {
242
+ window.myopComponent.hide();
243
+ }
244
+ `);
245
+ },
246
+ show: () => {
247
+ injectJS(`
248
+ if (window.myopComponent && window.myopComponent.show) {
249
+ window.myopComponent.show();
250
+ }
251
+ `);
252
+ },
253
+ inspect: () => {
254
+ injectJS(`
255
+ if (window.myopComponent && window.myopComponent.inspect) {
256
+ window.myopComponent.inspect();
257
+ }
258
+ `);
259
+ }
260
+ };
261
+ }, []);
87
262
  const handleError = (0, react_1.useCallback)((errorMsg) => {
88
263
  console.error('[MyopComponent] Error:', errorMsg);
89
264
  hideLoader();
@@ -93,97 +268,42 @@ const MyopComponent = (props) => {
93
268
  }
94
269
  }, [hideLoader, props.onError]);
95
270
  const loadComponent = (0, react_1.useCallback)(async () => {
96
- var _a, _b, _c;
271
+ var _a;
272
+ isCancelled.current = false;
97
273
  try {
98
274
  let componentConfig = null;
99
275
  if (props.componentConfig) {
100
276
  componentConfig = props.componentConfig;
101
277
  }
102
278
  else if (props.componentId) {
103
- if (props.v1Mode) {
104
- // V1 mode: use flow endpoint
105
- const res = await fetch(`https://cloud.myop.dev/flow?id=${props.componentId}&auto=true`);
106
- const json = await res.json();
107
- if (!((_b = (_a = json.item) === null || _a === void 0 ? void 0 : _a.components) === null || _b === void 0 ? void 0 : _b[0])) {
108
- handleError(`Component "${props.componentId}" not found`);
109
- return;
110
- }
111
- componentConfig = json.item.components[0];
112
- }
113
- else {
114
- // V2 mode (default): use consume endpoint
115
- const env = props.environment || 'production';
116
- const res = await fetch(`https://cloud.myop.dev/consume?id=${props.componentId}&env=${env}`);
117
- const config = await res.json();
118
- const variant = config.item;
119
- if (!variant) {
120
- handleError(`Component "${props.componentId}" not found`);
121
- return;
122
- }
123
- if (!variant.consume_variant || !variant.consume_variant.length) {
124
- handleError(`Component "${props.componentId}" has no implementation for environment "${env}"`);
125
- return;
126
- }
127
- // Build V2 component config
128
- componentConfig = {
129
- instance: {
130
- id: 'auto',
131
- componentId: variant.componentId,
132
- componentName: variant.name,
133
- skinSelector: {
134
- type: 'Dedicated',
135
- skin: { id: 'skin_auto_v2_converted' }
136
- }
137
- },
138
- type: {
139
- id: variant.id,
140
- name: variant.name,
141
- description: variant.description,
142
- props: [
143
- {
144
- id: 'in_auto_v2_converted',
145
- name: 'myop_init_interface',
146
- type: 'any',
147
- behavior: { type: 'code' }
148
- },
149
- {
150
- id: 'out_auto_v2_converted',
151
- name: 'myop_cta_handler',
152
- type: 'any',
153
- behavior: { type: 'code' }
154
- }
155
- ],
156
- refs: [],
157
- skins: [{
158
- id: 'skin_auto_v2_converted',
159
- name: 'auto_v2_converted',
160
- description: '',
161
- loader: variant.consume_variant[0].loader
162
- }],
163
- defaultSkin: 0
164
- },
165
- name: variant.name
166
- };
279
+ const env = props.environment || (0, index_1.getCloudRepository)().getDefaultEnvironment();
280
+ componentConfig = await (0, index_1.getCloudRepository)().fetchComponentV2(props.componentId, env, props.preview);
281
+ if (!componentConfig) {
282
+ handleError(`Component "${props.componentId}" not found`);
283
+ return;
167
284
  }
168
285
  }
169
286
  if (!componentConfig) {
170
287
  handleError('No component configuration provided');
171
288
  return;
172
289
  }
290
+ if (isCancelled.current)
291
+ return;
173
292
  const encoded = encodeURIComponent(JSON.stringify(componentConfig));
174
- (_c = webviewRef.current) === null || _c === void 0 ? void 0 : _c.injectJavaScript(`
293
+ (_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`
175
294
  loadMyopComponent(\`${encoded}\`);
176
- true;
177
295
  `);
178
296
  }
179
297
  catch (err) {
180
- handleError((err === null || err === void 0 ? void 0 : err.message) || 'Unknown error');
298
+ if (!isCancelled.current) {
299
+ handleError((err === null || err === void 0 ? void 0 : err.message) || 'Unknown error');
300
+ }
181
301
  }
182
- }, [props.componentConfig, props.componentId, props.v1Mode, props.environment, handleError]);
302
+ }, [props.componentConfig, props.componentId, props.environment, props.preview, handleError]);
183
303
  const onMessage = (0, react_1.useCallback)((event) => {
184
- var _a, _b, _c, _d;
304
+ var _a, _b, _c, _d, _e;
185
305
  const message = event.nativeEvent.data;
186
- console.log('[MyopComponent] Message from WebView:', message);
306
+ //console.log('[MyopComponent] Message from WebView:', message);
187
307
  if (message === 'WEBVIEW_READY') {
188
308
  // WebView is ready, now inject the component loading code
189
309
  loadComponent();
@@ -195,14 +315,13 @@ const MyopComponent = (props) => {
195
315
  if (props.data !== undefined) {
196
316
  const encoded = encodeURIComponent(JSON.stringify(props.data));
197
317
  (_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`
198
- if (window.myopComponent && window.myopComponent.element && window.myopComponent.element.contentWindow && window.myopComponent.element.contentWindow.myop_init_interface) {
199
- window.myopComponent.element.contentWindow.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
318
+ if (window.myopComponent) {
319
+ window.myopComponent.props.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
200
320
  }
201
- true;
202
321
  `);
203
322
  }
204
323
  if (props.onLoad) {
205
- props.onLoad(myopComponent);
324
+ props.onLoad(createMyopComponentProxy(props.componentId || 'unknown'));
206
325
  }
207
326
  }
208
327
  else if (((_b = message === null || message === void 0 ? void 0 : message.startsWith) === null || _b === void 0 ? void 0 : _b.call(message, 'COMPONENT_ERROR:')) || ((_c = message === null || message === void 0 ? void 0 : message.startsWith) === null || _c === void 0 ? void 0 : _c.call(message, 'SDK_INIT_ERROR:'))) {
@@ -226,15 +345,35 @@ const MyopComponent = (props) => {
226
345
  console.error('[MyopComponent] Failed to parse CTA message:', e);
227
346
  }
228
347
  }
229
- }, [loadComponent, props.onLoad, props.onError, props.on, hideLoader]);
348
+ else if ((_e = message === null || message === void 0 ? void 0 : message.startsWith) === null || _e === void 0 ? void 0 : _e.call(message, 'GET_RESULT:')) {
349
+ // Handle element.get() responses
350
+ try {
351
+ const result = JSON.parse(message.substring(11));
352
+ const resolver = pendingGetRequests.current.get(result.requestId);
353
+ if (resolver) {
354
+ resolver(result.value);
355
+ pendingGetRequests.current.delete(result.requestId);
356
+ }
357
+ }
358
+ catch (e) {
359
+ console.error('[MyopComponent] Failed to parse GET_RESULT message:', e);
360
+ }
361
+ }
362
+ }, [loadComponent, props.onLoad, props.onError, props.on, hideLoader, createMyopComponentProxy]);
363
+ // Cleanup on unmount
364
+ (0, react_1.useEffect)(() => {
365
+ return () => {
366
+ isCancelled.current = true;
367
+ };
368
+ }, []);
230
369
  return (react_1.default.createElement(react_native_1.View, { style: props.style ? props.style : styles.root },
231
- react_1.default.createElement(react_native_webview_1.WebView, { ref: webviewRef, originWhitelist: ['*'], source: { html: componentHost_html_js_1.HTML }, onMessage: onMessage, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, mixedContentMode: "always", allowFileAccess: true, allowUniversalAccessFromFileURLs: true }),
370
+ react_1.default.createElement(react_native_webview_1.WebView, { ref: webviewRef, originWhitelist: ['*'], source: { html: getHtml((_b = props.zoomEnabled) !== null && _b !== void 0 ? _b : false, (_c = props.selectionEnabled) !== null && _c !== void 0 ? _c : false) }, onMessage: onMessage, style: styles.webview, javaScriptEnabled: true, domStorageEnabled: true, mixedContentMode: "always", allowFileAccess: true, allowUniversalAccessFromFileURLs: true, overScrollMode: "never", scrollEnabled: (_d = props.scrollEnabled) !== null && _d !== void 0 ? _d : false }),
232
371
  showLoader && (react_1.default.createElement(react_native_1.View, { style: styles.loaderContainer }, props.loader !== undefined
233
372
  ? ((0, react_1.isValidElement)(props.loader)
234
373
  ? (0, react_1.cloneElement)(props.loader, { ref: loaderRef })
235
374
  : props.loader)
236
375
  : react_1.default.createElement(MyopLoader_1.MyopLoader, { ref: loaderRef }))),
237
- showFallback && (react_1.default.createElement(react_native_1.View, { style: styles.loaderContainer }, (_b = props.fallback) !== null && _b !== void 0 ? _b : react_1.default.createElement(MyopFallback_1.MyopFallback, null)))));
376
+ showFallback && (react_1.default.createElement(react_native_1.View, { style: styles.loaderContainer }, (_e = props.fallback) !== null && _e !== void 0 ? _e : react_1.default.createElement(MyopFallback_1.MyopFallback, null)))));
238
377
  };
239
378
  exports.MyopComponent = MyopComponent;
240
379
  const styles = react_native_1.StyleSheet.create({