@persona/layout-editor-remote-module 0.0.0-bootstrap.0 → 0.5.1

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 ADDED
@@ -0,0 +1,164 @@
1
+ # @persona/layout-editor-remote-module
2
+
3
+ Shared manifest format and Vite plugin for remotely-defined dashboard layout modules — used by
4
+ Persona dashboard apps to declare a layout module directly in a typical app's `vite.config.ts`,
5
+ without editing the host.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ yarn add @persona/layout-editor-remote-module
11
+ ```
12
+
13
+ `vite`, `esbuild`, and `zod` are peer dependencies. The plugin's dev-mode bundling uses `esbuild`
14
+ directly, and declaring it as a peer means it resolves to the same copy `vite` already pulls in
15
+ rather than a second version. With Yarn's `node-modules` linker the hoisted transitive copy usually
16
+ satisfies it; add `esbuild` to your own `devDependencies` if your package manager warns about an
17
+ unmet peer.
18
+
19
+ The optional `react` and `react-dom` peer dependencies are required only when using the React bridge
20
+ components.
21
+
22
+ The package is published to the public npm registry.
23
+
24
+ ## Usage
25
+
26
+ ```ts
27
+ // vite.config.ts
28
+ import {
29
+ defineLayoutModule,
30
+ personaLayoutModules,
31
+ } from '@persona/layout-editor-remote-module/vite-plugin';
32
+
33
+ export default defineConfig({
34
+ plugins: [
35
+ react(),
36
+ personaLayoutModules({
37
+ modules: [
38
+ defineLayoutModule({
39
+ key: 'risk_summary',
40
+ displayName: 'Risk Summary',
41
+ descriptionShort: 'Show the account risk score',
42
+ allowAddingToLayout: true,
43
+ layouts: ['case'],
44
+ defaultSize: { case: { width: 4, height: 20 } },
45
+ // `.tsx`/`.js` — a `code` delivery, imported and rendered directly
46
+ // in the host's own React tree.
47
+ render: { entry: './src/layout-modules/risk-summary/render.tsx' },
48
+ }),
49
+ ],
50
+ }),
51
+ ],
52
+ });
53
+ ```
54
+
55
+ An empty or omitted `layouts` makes the module available in every layout. When `layouts` lists
56
+ specific layouts, `defaultSize` must provide an entry for each one.
57
+
58
+ An `.html` entry (rather than `.tsx`/`.js`) is a `document` delivery instead — the host iframes it
59
+ rather than importing anything. See `LayoutModuleEntry`'s doc comment in `src/define.ts` for the
60
+ full contract, including the optional `path` field for a client-routed SPA.
61
+
62
+ ## Communicating with the host
63
+
64
+ Document-delivered modules can use the browser bridge to receive layout data, report their height,
65
+ and resolve data paths through the host. The host renders the document with `RemoteModuleFrame`:
66
+
67
+ ```tsx
68
+ import { RemoteModuleFrame } from '@persona/layout-editor-remote-module/bridge-host';
69
+
70
+ <RemoteModuleFrame
71
+ documentUrl={registration.document}
72
+ moduleOrigin={registration.origin}
73
+ title='Risk summary'
74
+ sandbox='allow-scripts allow-same-origin'
75
+ data={moduleData}
76
+ context={moduleContext}
77
+ dynamicHeight
78
+ resolveDataPath={resolveDataPath}
79
+ />;
80
+ ```
81
+
82
+ The required `sandbox` value is chosen by the host.
83
+
84
+ By default, the bridge accepts host origins matching `*.withpersona.com`,
85
+ `*.withpersona-staging.com`, `localhost`, and `127.0.0.1`. Add development or preview origins with
86
+ `allowedHostOrigins`, optionally composing the exported `personaDashboardHostOrigins` helper:
87
+
88
+ ```tsx
89
+ import {
90
+ LayoutModuleBridge,
91
+ personaDashboardHostOrigins,
92
+ } from '@persona/layout-editor-remote-module/bridge-frame';
93
+
94
+ <LayoutModuleBridge
95
+ allowedHostOrigins={(origin) => personaDashboardHostOrigins(origin) || myDevHost(origin)}
96
+ >
97
+ {({ data }) => <RiskSummary data={data} />}
98
+ </LayoutModuleBridge>;
99
+ ```
100
+
101
+ The document entry uses `LayoutModuleBridge` as a render-prop component:
102
+
103
+ ```tsx
104
+ import { LayoutModuleBridge } from '@persona/layout-editor-remote-module/bridge-frame';
105
+
106
+ <LayoutModuleBridge>
107
+ {({ data, context, resolveDataPath }) => (
108
+ <RiskSummary data={data} context={context} resolveDataPath={resolveDataPath} />
109
+ )}
110
+ </LayoutModuleBridge>;
111
+ ```
112
+
113
+ Alongside `data` (the module's persisted config), the host sends a typed `context` payload
114
+ (`RemoteModuleContext`) describing where the module is rendered — facts the host owns and the module
115
+ cannot derive itself:
116
+
117
+ - `layoutType` — which layout kind the module is placed on (`account`, `case`, or `transaction`)
118
+ - `layoutObjectId` — the ID of the object the layout is rendered for (e.g. the viewed case's ID on a
119
+ `case` layout), or `null` when there is none
120
+ - `isReadOnly` — the host object is locked for edits
121
+ - `isInEditor` — the module is rendered inside the layout editor rather than on a live record
122
+ - `isUsingStubData` — the host is feeding placeholder/stub data instead of a real record
123
+
124
+ `context` is sent with `init` and pushed again whenever it changes, just like `data`; inside the
125
+ iframe it arrives through `ready()`, `getContext()`, and the `context` render prop (all
126
+ `RemoteModuleContext | null` until init).
127
+
128
+ The host still appends `access_token`, `environment_id`, and `locale` as query parameters. The app
129
+ hosting the module consumes and strips those parameters itself, so by the time `LayoutModuleBridge`
130
+ runs they have already been handled; the bridge neither reads nor strips them. Keep the host
131
+ iframe's `referrerpolicy` at its default `no-referrer` (or use `strict-origin`) so the URL
132
+ parameters cannot leak through subsequent requests.
133
+
134
+ The bridge's security model is intentionally narrow:
135
+
136
+ - The host sends only to the exact module origin and accepts messages only from that origin and
137
+ iframe window.
138
+ - The iframe accepts only messages from its parent window and an allowed host origin, then pins the
139
+ first trusted origin for the lifetime of the bridge.
140
+ - The iframe derives the ready target from `ancestorOrigins` or the referrer, and the host retries
141
+ `init` until the frame answers.
142
+ - Messages use a strict, versioned protocol. Credentials remain in the iframe URL and are never
143
+ handled through bridge messages.
144
+ - `extraParams` rejects reserved names and credential-like keys.
145
+
146
+ ### Consuming a manifest
147
+
148
+ The plugin emits `{ modules: RemoteModuleRegistrationManifestV1[] }` at `manifestPath`. A host
149
+ fetching that document should run it through `parseRemoteModuleManifest` — it validates the
150
+ envelope, every module, and key uniqueness, and returns the typed document:
151
+
152
+ ```ts
153
+ import {
154
+ parseRemoteModuleManifest,
155
+ RemoteModuleManifestParseError,
156
+ } from '@persona/layout-editor-remote-module';
157
+
158
+ const response = await fetch(`${appOrigin}/_layout_modules_manifest.json`);
159
+ const { modules } = parseRemoteModuleManifest(await response.json());
160
+ ```
161
+
162
+ It throws `RemoteModuleManifestParseError` (with `.issues` and a prettified `.message`) on a
163
+ malformed document; `safeParseRemoteModuleManifest` returns `{ success, data | error }` instead of
164
+ throwing.
@@ -0,0 +1,48 @@
1
+ import { n as Json, t as RemoteModuleContext } from "./protocol-edoRdXpF.mjs";
2
+ import { ReactNode } from "react";
3
+
4
+ //#region src/bridge/origin.d.ts
5
+ declare function personaDashboardHostOrigins(origin: string): boolean;
6
+ //#endregion
7
+ //#region src/bridge/iframe/createModuleBridge.d.ts
8
+ type ModuleBridgeInitPayload = {
9
+ data: Json;
10
+ context: RemoteModuleContext | null;
11
+ };
12
+ type CreateModuleBridgeOptions = {
13
+ allowedHostOrigins?: (string | RegExp)[] | ((origin: string) => boolean);
14
+ resolveTimeoutMs?: number;
15
+ };
16
+ type ModuleBridge = {
17
+ ready: () => Promise<ModuleBridgeInitPayload>;
18
+ getData: () => Json;
19
+ getContext: () => RemoteModuleContext | null;
20
+ subscribe: (listener: () => void) => () => void;
21
+ reportRenderError: (message: string) => void;
22
+ resolveDataPath: (dataPath: string, variableMap?: Record<string, string | number>) => Promise<Json>;
23
+ observeHeight: (element: Element) => () => void;
24
+ destroy: () => void;
25
+ };
26
+ //#endregion
27
+ //#region src/bridge/iframe/LayoutModuleBridge.d.ts
28
+ type LayoutModuleBridgeRenderProps = {
29
+ data: ReturnType<ModuleBridge['getData']>;
30
+ context: RemoteModuleContext | null;
31
+ resolveDataPath: ModuleBridge['resolveDataPath'];
32
+ };
33
+ type LayoutModuleBridgeProps = {
34
+ allowedHostOrigins?: CreateModuleBridgeOptions['allowedHostOrigins'];
35
+ resolveTimeoutMs?: number;
36
+ observeHeight?: boolean;
37
+ fallback?: ReactNode;
38
+ children: (props: LayoutModuleBridgeRenderProps) => ReactNode;
39
+ };
40
+ declare function LayoutModuleBridge({
41
+ allowedHostOrigins,
42
+ resolveTimeoutMs,
43
+ observeHeight,
44
+ fallback,
45
+ children
46
+ }: LayoutModuleBridgeProps): ReactNode;
47
+ //#endregion
48
+ export { type Json, LayoutModuleBridge, type LayoutModuleBridgeProps, type LayoutModuleBridgeRenderProps, type RemoteModuleContext, personaDashboardHostOrigins };
@@ -0,0 +1,350 @@
1
+ import { c as resolveAllowedHostOrigins, n as SOURCE, o as candidateParentOrigin, r as parseHostToModuleMessage, s as personaDashboardHostOrigins, t as PROTOCOL_VERSION } from "./protocol-Dz3ZX59L.mjs";
2
+ import { Component, useEffect, useRef, useState } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/bridge/rpc.ts
6
+ var BridgeDestroyedError = class extends Error {
7
+ constructor(message = "Bridge has been destroyed") {
8
+ super(message);
9
+ this.name = "BridgeDestroyedError";
10
+ }
11
+ };
12
+ var BridgeTimeoutError = class extends Error {
13
+ constructor(message = "Bridge request timed out") {
14
+ super(message);
15
+ this.name = "BridgeTimeoutError";
16
+ }
17
+ };
18
+ function createRequestId() {
19
+ const cryptoApi = globalThis.crypto;
20
+ if (cryptoApi?.randomUUID) return cryptoApi.randomUUID();
21
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
22
+ }
23
+ function createPendingRequests() {
24
+ const pending = /* @__PURE__ */ new Map();
25
+ return {
26
+ add(timeoutMs) {
27
+ const requestId = createRequestId();
28
+ return {
29
+ requestId,
30
+ promise: new Promise((resolve, reject) => {
31
+ const timer = setTimeout(() => {
32
+ pending.delete(requestId);
33
+ reject(new BridgeTimeoutError());
34
+ }, timeoutMs);
35
+ pending.set(requestId, {
36
+ resolve,
37
+ reject,
38
+ timer
39
+ });
40
+ })
41
+ };
42
+ },
43
+ settle(requestId, settlement) {
44
+ const request = pending.get(requestId);
45
+ if (!request) return false;
46
+ pending.delete(requestId);
47
+ clearTimeout(request.timer);
48
+ if ("error" in settlement) request.reject(settlement.error);
49
+ else request.resolve(settlement.result);
50
+ return true;
51
+ },
52
+ rejectAll(error) {
53
+ for (const [requestId, request] of pending) {
54
+ pending.delete(requestId);
55
+ clearTimeout(request.timer);
56
+ request.reject(error);
57
+ }
58
+ }
59
+ };
60
+ }
61
+
62
+ //#endregion
63
+ //#region src/bridge/iframe/createModuleBridge.ts
64
+ function createModuleBridge({ allowedHostOrigins = personaDashboardHostOrigins, resolveTimeoutMs = 3e4 } = {}) {
65
+ const isEmbedded = typeof window !== "undefined" && window.parent !== window;
66
+ const allowedOrigin = resolveAllowedHostOrigins(allowedHostOrigins);
67
+ if (!isEmbedded) {
68
+ const notEmbeddedError = new BridgeDestroyedError("Bridge is not embedded");
69
+ return {
70
+ ready: async () => ({
71
+ data: null,
72
+ context: null
73
+ }),
74
+ getData: () => null,
75
+ getContext: () => null,
76
+ subscribe: () => () => {},
77
+ reportRenderError: () => {},
78
+ resolveDataPath: async () => {
79
+ throw notEmbeddedError;
80
+ },
81
+ observeHeight: () => () => {},
82
+ destroy: () => {}
83
+ };
84
+ }
85
+ let destroyed = false;
86
+ let hostOrigin;
87
+ let data = null;
88
+ let context = null;
89
+ let readyResolved = false;
90
+ const listeners = /* @__PURE__ */ new Set();
91
+ const observers = /* @__PURE__ */ new Set();
92
+ const pendingRequests = createPendingRequests();
93
+ const queuedRequests = /* @__PURE__ */ new Map();
94
+ let resolveReady;
95
+ let rejectReady;
96
+ const readyPromise = new Promise((resolve, reject) => {
97
+ resolveReady = resolve;
98
+ rejectReady = reject;
99
+ });
100
+ const post = (message) => {
101
+ if (hostOrigin) window.parent.postMessage(message, hostOrigin);
102
+ };
103
+ const flushQueuedRequests = () => {
104
+ if (!hostOrigin) return;
105
+ for (const [requestId, request] of queuedRequests) {
106
+ post({
107
+ source: SOURCE,
108
+ protocolVersion: PROTOCOL_VERSION,
109
+ type: "resolve-data-path",
110
+ requestId,
111
+ dataPath: request.dataPath,
112
+ ...request.variableMap === void 0 ? {} : { variableMap: request.variableMap }
113
+ });
114
+ queuedRequests.delete(requestId);
115
+ }
116
+ };
117
+ const pinHostOrigin = (origin) => {
118
+ if (!allowedOrigin(origin)) return false;
119
+ if (hostOrigin !== void 0 && hostOrigin !== origin) return false;
120
+ hostOrigin = origin;
121
+ flushQueuedRequests();
122
+ return true;
123
+ };
124
+ const notify = () => {
125
+ for (const listener of listeners) listener();
126
+ };
127
+ const onMessage = (event) => {
128
+ if (destroyed || event.source !== window.parent) return;
129
+ const message = parseHostToModuleMessage(event.data);
130
+ if (!message || !pinHostOrigin(event.origin)) return;
131
+ switch (message.type) {
132
+ case "init":
133
+ data = message.data;
134
+ context = message.context;
135
+ notify();
136
+ if (!readyResolved) {
137
+ readyResolved = true;
138
+ resolveReady({
139
+ data,
140
+ context
141
+ });
142
+ }
143
+ break;
144
+ case "data":
145
+ data = message.data;
146
+ notify();
147
+ break;
148
+ case "context":
149
+ context = message.context;
150
+ notify();
151
+ break;
152
+ case "data-path-result":
153
+ if (message.error !== void 0) pendingRequests.settle(message.requestId, { error: new Error(message.error) });
154
+ else pendingRequests.settle(message.requestId, { result: message.result ?? null });
155
+ break;
156
+ }
157
+ };
158
+ window.addEventListener("message", onMessage);
159
+ const parentOrigin = candidateParentOrigin();
160
+ if (parentOrigin && allowedOrigin(parentOrigin)) window.parent.postMessage({
161
+ source: SOURCE,
162
+ protocolVersion: PROTOCOL_VERSION,
163
+ type: "ready"
164
+ }, parentOrigin);
165
+ return {
166
+ ready: () => readyPromise,
167
+ getData: () => data,
168
+ getContext: () => context,
169
+ subscribe(listener) {
170
+ listeners.add(listener);
171
+ return () => listeners.delete(listener);
172
+ },
173
+ reportRenderError(message) {
174
+ post({
175
+ source: SOURCE,
176
+ protocolVersion: PROTOCOL_VERSION,
177
+ type: "render-error",
178
+ message
179
+ });
180
+ },
181
+ resolveDataPath(dataPath, variableMap) {
182
+ if (destroyed) return Promise.reject(new BridgeDestroyedError());
183
+ const { requestId, promise } = pendingRequests.add(resolveTimeoutMs);
184
+ queuedRequests.set(requestId, {
185
+ dataPath,
186
+ variableMap
187
+ });
188
+ flushQueuedRequests();
189
+ return promise;
190
+ },
191
+ observeHeight(element) {
192
+ if (destroyed || typeof ResizeObserver === "undefined") return () => {};
193
+ let lastHeight;
194
+ let frame;
195
+ let disconnected = false;
196
+ const postHeight = (height) => {
197
+ if (disconnected || height === lastHeight) return;
198
+ lastHeight = height;
199
+ post({
200
+ source: SOURCE,
201
+ protocolVersion: PROTOCOL_VERSION,
202
+ type: "set-height",
203
+ height
204
+ });
205
+ };
206
+ const observer = new ResizeObserver((entries) => {
207
+ const entry = entries[0];
208
+ if (!entry) return;
209
+ const borderBoxSize = entry.borderBoxSize;
210
+ const height = (Array.isArray(borderBoxSize) ? borderBoxSize[0]?.blockSize : void 0) ?? entry.contentRect.height;
211
+ if (frame !== void 0) return;
212
+ const callback = () => {
213
+ frame = void 0;
214
+ postHeight(height);
215
+ };
216
+ frame = typeof window.requestAnimationFrame === "function" ? window.requestAnimationFrame(callback) : window.setTimeout(callback, 0);
217
+ });
218
+ observers.add(observer);
219
+ observer.observe(element);
220
+ return () => {
221
+ disconnected = true;
222
+ if (frame !== void 0) {
223
+ window.cancelAnimationFrame?.(frame);
224
+ window.clearTimeout(frame);
225
+ }
226
+ observer.disconnect();
227
+ observers.delete(observer);
228
+ };
229
+ },
230
+ destroy() {
231
+ if (destroyed) return;
232
+ destroyed = true;
233
+ window.removeEventListener("message", onMessage);
234
+ for (const observer of observers) observer.disconnect();
235
+ observers.clear();
236
+ queuedRequests.clear();
237
+ const error = new BridgeDestroyedError();
238
+ pendingRequests.rejectAll(error);
239
+ if (!readyResolved) rejectReady(error);
240
+ }
241
+ };
242
+ }
243
+
244
+ //#endregion
245
+ //#region src/bridge/iframe/useRemoteModuleBridge.ts
246
+ const rejectNotReady = async () => {
247
+ throw new BridgeDestroyedError("Bridge is not ready");
248
+ };
249
+ function useRemoteModuleBridge(options = {}) {
250
+ const optionsRef = useRef(options);
251
+ const [bridge, setBridge] = useState(null);
252
+ const [ready, setReady] = useState(false);
253
+ const [data, setData] = useState(null);
254
+ const [context, setContext] = useState(null);
255
+ useEffect(() => {
256
+ let active = true;
257
+ const currentBridge = createModuleBridge(optionsRef.current);
258
+ setBridge(currentBridge);
259
+ setData(currentBridge.getData());
260
+ setContext(currentBridge.getContext());
261
+ const unsubscribe = currentBridge.subscribe(() => {
262
+ if (active) {
263
+ setData(currentBridge.getData());
264
+ setContext(currentBridge.getContext());
265
+ }
266
+ });
267
+ currentBridge.ready().then(({ data: initialData, context: initialContext }) => {
268
+ if (active) {
269
+ setData(initialData);
270
+ setContext(initialContext);
271
+ setReady(true);
272
+ }
273
+ }, () => {
274
+ if (active) setReady(false);
275
+ });
276
+ return () => {
277
+ active = false;
278
+ unsubscribe();
279
+ currentBridge.destroy();
280
+ setBridge(null);
281
+ setData(null);
282
+ setContext(null);
283
+ setReady(false);
284
+ };
285
+ }, []);
286
+ return {
287
+ bridge,
288
+ context,
289
+ data,
290
+ ready,
291
+ resolveDataPath: bridge?.resolveDataPath ?? rejectNotReady
292
+ };
293
+ }
294
+
295
+ //#endregion
296
+ //#region src/bridge/iframe/LayoutModuleBridge.tsx
297
+ let mountedBridgeCount = 0;
298
+ function BridgeErrorContent({ render }) {
299
+ return render();
300
+ }
301
+ var BridgeErrorBoundary = class extends Component {
302
+ state = { error: null };
303
+ static getDerivedStateFromError(error) {
304
+ return { error };
305
+ }
306
+ componentDidCatch(error, _errorInfo) {
307
+ this.props.bridge.reportRenderError(error.message);
308
+ }
309
+ render() {
310
+ return this.state.error ? this.props.fallback : /* @__PURE__ */ jsx(BridgeErrorContent, { render: this.props.render });
311
+ }
312
+ };
313
+ function LayoutModuleBridge({ allowedHostOrigins, resolveTimeoutMs, observeHeight = true, fallback = null, children }) {
314
+ const { bridge, context, data, ready, resolveDataPath } = useRemoteModuleBridge({
315
+ allowedHostOrigins,
316
+ resolveTimeoutMs
317
+ });
318
+ const rootRef = useRef(null);
319
+ useEffect(() => {
320
+ mountedBridgeCount += 1;
321
+ if (typeof process !== "undefined" && mountedBridgeCount > 1) console.warn("Multiple LayoutModuleBridge instances are mounted in the same document; this is unsupported.");
322
+ return () => {
323
+ mountedBridgeCount -= 1;
324
+ };
325
+ }, []);
326
+ useEffect(() => {
327
+ if (!ready || !observeHeight || !rootRef.current || !bridge) return;
328
+ return bridge.observeHeight(rootRef.current);
329
+ }, [
330
+ bridge,
331
+ observeHeight,
332
+ ready
333
+ ]);
334
+ if (!ready || !bridge) return fallback;
335
+ return /* @__PURE__ */ jsx(BridgeErrorBoundary, {
336
+ bridge,
337
+ fallback,
338
+ render: () => /* @__PURE__ */ jsx("div", {
339
+ ref: rootRef,
340
+ children: children({
341
+ context,
342
+ data,
343
+ resolveDataPath
344
+ })
345
+ })
346
+ });
347
+ }
348
+
349
+ //#endregion
350
+ export { LayoutModuleBridge, personaDashboardHostOrigins };
@@ -0,0 +1,61 @@
1
+ import { n as Json, t as RemoteModuleContext } from "./protocol-edoRdXpF.mjs";
2
+ import { CSSProperties, ReactNode } from "react";
3
+
4
+ //#region src/bridge/host/buildModuleSrc.d.ts
5
+ type BuildModuleSrcOptions = {
6
+ accessToken?: string;
7
+ environmentId?: string;
8
+ locale?: string;
9
+ extraParams?: Record<string, string>;
10
+ };
11
+ //#endregion
12
+ //#region src/bridge/host/createHostBridge.d.ts
13
+ type ResolveDataPath = (dataPath: string, variableMap?: Record<string, string | number>) => Promise<Json | undefined> | Json | undefined;
14
+ //#endregion
15
+ //#region src/bridge/host/RemoteModuleFrame.d.ts
16
+ type RemoteModuleFrameProps = {
17
+ documentUrl: string;
18
+ moduleOrigin: string;
19
+ accessToken?: string;
20
+ environmentId?: string;
21
+ locale?: string;
22
+ extraParams?: BuildModuleSrcOptions['extraParams'];
23
+ title: string;
24
+ sandbox: string;
25
+ referrerPolicy?: ReferrerPolicy;
26
+ initRetryMs?: number;
27
+ initRetryTimeoutMs?: number;
28
+ dynamicHeight?: boolean;
29
+ heightOffset?: number;
30
+ minHeight?: number;
31
+ className?: string;
32
+ style?: CSSProperties;
33
+ data: Json;
34
+ context: RemoteModuleContext;
35
+ resolveDataPath: ResolveDataPath;
36
+ onRenderError?: (message: string) => void;
37
+ };
38
+ declare function RemoteModuleFrame({
39
+ documentUrl,
40
+ moduleOrigin,
41
+ accessToken,
42
+ environmentId,
43
+ locale,
44
+ extraParams,
45
+ title,
46
+ sandbox,
47
+ referrerPolicy,
48
+ initRetryMs,
49
+ initRetryTimeoutMs,
50
+ dynamicHeight,
51
+ heightOffset,
52
+ minHeight,
53
+ className,
54
+ style,
55
+ data,
56
+ context,
57
+ resolveDataPath,
58
+ onRenderError
59
+ }: RemoteModuleFrameProps): ReactNode;
60
+ //#endregion
61
+ export { type Json, type RemoteModuleContext, RemoteModuleFrame, type RemoteModuleFrameProps, type ResolveDataPath };