@myop/react-native 0.0.4 → 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 +119 -11
- package/dist/MyopComponent.d.ts +41 -2
- package/dist/MyopComponent.d.ts.map +1 -1
- package/dist/MyopComponent.js +227 -92
- package/dist/MyopComponent.js.map +1 -1
- package/dist/componentHost.html.d.ts +1 -1
- package/dist/componentHost.html.d.ts.map +1 -1
- package/dist/componentHost.html.js +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +51 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
-
-
|
|
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
|
-
###
|
|
95
|
+
### Component Proxy API
|
|
94
96
|
|
|
95
|
-
Access the component instance for direct
|
|
97
|
+
Access the component instance for direct manipulation:
|
|
96
98
|
|
|
97
99
|
```tsx
|
|
98
|
-
|
|
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={(
|
|
106
|
+
onLoad={(proxy) => setComponent(proxy)}
|
|
103
107
|
style={{ flex: 1 }}
|
|
104
108
|
/>
|
|
105
109
|
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
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` | `(
|
|
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
|
-
| `
|
|
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;
|
package/dist/MyopComponent.d.ts
CHANGED
|
@@ -1,20 +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:
|
|
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;
|
|
17
53
|
preview?: boolean;
|
|
54
|
+
scrollEnabled?: boolean;
|
|
55
|
+
zoomEnabled?: boolean;
|
|
56
|
+
selectionEnabled?: boolean;
|
|
18
57
|
}
|
|
19
58
|
export declare const MyopComponent: (props: IPropTypes) => React.JSX.Element;
|
|
20
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;
|
|
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"}
|
package/dist/MyopComponent.js
CHANGED
|
@@ -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
|
|
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
|
|
58
|
-
window.myopComponent.
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,101 +268,42 @@ const MyopComponent = (props) => {
|
|
|
93
268
|
}
|
|
94
269
|
}, [hideLoader, props.onError]);
|
|
95
270
|
const loadComponent = (0, react_1.useCallback)(async () => {
|
|
96
|
-
var _a
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
let consumeUrl = `https://cloud.myop.dev/consume?id=${props.componentId}&env=${env}`;
|
|
117
|
-
if (props.preview) {
|
|
118
|
-
consumeUrl += '&preview=true';
|
|
119
|
-
}
|
|
120
|
-
const res = await fetch(consumeUrl);
|
|
121
|
-
const config = await res.json();
|
|
122
|
-
const variant = config.item;
|
|
123
|
-
if (!variant) {
|
|
124
|
-
handleError(`Component "${props.componentId}" not found`);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
if (!variant.consume_variant || !variant.consume_variant.length) {
|
|
128
|
-
handleError(`Component "${props.componentId}" has no implementation for environment "${env}"`);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
// Build V2 component config
|
|
132
|
-
componentConfig = {
|
|
133
|
-
instance: {
|
|
134
|
-
id: 'auto',
|
|
135
|
-
componentId: variant.componentId,
|
|
136
|
-
componentName: variant.name,
|
|
137
|
-
skinSelector: {
|
|
138
|
-
type: 'Dedicated',
|
|
139
|
-
skin: { id: 'skin_auto_v2_converted' }
|
|
140
|
-
}
|
|
141
|
-
},
|
|
142
|
-
type: {
|
|
143
|
-
id: variant.id,
|
|
144
|
-
name: variant.name,
|
|
145
|
-
description: variant.description,
|
|
146
|
-
props: [
|
|
147
|
-
{
|
|
148
|
-
id: 'in_auto_v2_converted',
|
|
149
|
-
name: 'myop_init_interface',
|
|
150
|
-
type: 'any',
|
|
151
|
-
behavior: { type: 'code' }
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
id: 'out_auto_v2_converted',
|
|
155
|
-
name: 'myop_cta_handler',
|
|
156
|
-
type: 'any',
|
|
157
|
-
behavior: { type: 'code' }
|
|
158
|
-
}
|
|
159
|
-
],
|
|
160
|
-
refs: [],
|
|
161
|
-
skins: [{
|
|
162
|
-
id: 'skin_auto_v2_converted',
|
|
163
|
-
name: 'auto_v2_converted',
|
|
164
|
-
description: '',
|
|
165
|
-
loader: variant.consume_variant[0].loader
|
|
166
|
-
}],
|
|
167
|
-
defaultSkin: 0
|
|
168
|
-
},
|
|
169
|
-
name: variant.name
|
|
170
|
-
};
|
|
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;
|
|
171
284
|
}
|
|
172
285
|
}
|
|
173
286
|
if (!componentConfig) {
|
|
174
287
|
handleError('No component configuration provided');
|
|
175
288
|
return;
|
|
176
289
|
}
|
|
290
|
+
if (isCancelled.current)
|
|
291
|
+
return;
|
|
177
292
|
const encoded = encodeURIComponent(JSON.stringify(componentConfig));
|
|
178
|
-
(
|
|
293
|
+
(_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`
|
|
179
294
|
loadMyopComponent(\`${encoded}\`);
|
|
180
|
-
true;
|
|
181
295
|
`);
|
|
182
296
|
}
|
|
183
297
|
catch (err) {
|
|
184
|
-
|
|
298
|
+
if (!isCancelled.current) {
|
|
299
|
+
handleError((err === null || err === void 0 ? void 0 : err.message) || 'Unknown error');
|
|
300
|
+
}
|
|
185
301
|
}
|
|
186
|
-
}, [props.componentConfig, props.componentId, props.
|
|
302
|
+
}, [props.componentConfig, props.componentId, props.environment, props.preview, handleError]);
|
|
187
303
|
const onMessage = (0, react_1.useCallback)((event) => {
|
|
188
|
-
var _a, _b, _c, _d;
|
|
304
|
+
var _a, _b, _c, _d, _e;
|
|
189
305
|
const message = event.nativeEvent.data;
|
|
190
|
-
console.log('[MyopComponent] Message from WebView:', message);
|
|
306
|
+
//console.log('[MyopComponent] Message from WebView:', message);
|
|
191
307
|
if (message === 'WEBVIEW_READY') {
|
|
192
308
|
// WebView is ready, now inject the component loading code
|
|
193
309
|
loadComponent();
|
|
@@ -199,14 +315,13 @@ const MyopComponent = (props) => {
|
|
|
199
315
|
if (props.data !== undefined) {
|
|
200
316
|
const encoded = encodeURIComponent(JSON.stringify(props.data));
|
|
201
317
|
(_a = webviewRef.current) === null || _a === void 0 ? void 0 : _a.injectJavaScript(`
|
|
202
|
-
if (window.myopComponent
|
|
203
|
-
window.myopComponent.
|
|
318
|
+
if (window.myopComponent) {
|
|
319
|
+
window.myopComponent.props.myop_init_interface(JSON.parse(decodeURIComponent('${encoded}')));
|
|
204
320
|
}
|
|
205
|
-
true;
|
|
206
321
|
`);
|
|
207
322
|
}
|
|
208
323
|
if (props.onLoad) {
|
|
209
|
-
props.onLoad(
|
|
324
|
+
props.onLoad(createMyopComponentProxy(props.componentId || 'unknown'));
|
|
210
325
|
}
|
|
211
326
|
}
|
|
212
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:'))) {
|
|
@@ -230,15 +345,35 @@ const MyopComponent = (props) => {
|
|
|
230
345
|
console.error('[MyopComponent] Failed to parse CTA message:', e);
|
|
231
346
|
}
|
|
232
347
|
}
|
|
233
|
-
|
|
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
|
+
}, []);
|
|
234
369
|
return (react_1.default.createElement(react_native_1.View, { style: props.style ? props.style : styles.root },
|
|
235
|
-
react_1.default.createElement(react_native_webview_1.WebView, { ref: webviewRef, originWhitelist: ['*'], source: { html:
|
|
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 }),
|
|
236
371
|
showLoader && (react_1.default.createElement(react_native_1.View, { style: styles.loaderContainer }, props.loader !== undefined
|
|
237
372
|
? ((0, react_1.isValidElement)(props.loader)
|
|
238
373
|
? (0, react_1.cloneElement)(props.loader, { ref: loaderRef })
|
|
239
374
|
: props.loader)
|
|
240
375
|
: react_1.default.createElement(MyopLoader_1.MyopLoader, { ref: loaderRef }))),
|
|
241
|
-
showFallback && (react_1.default.createElement(react_native_1.View, { style: styles.loaderContainer }, (
|
|
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)))));
|
|
242
377
|
};
|
|
243
378
|
exports.MyopComponent = MyopComponent;
|
|
244
379
|
const styles = react_native_1.StyleSheet.create({
|