@aspectly/web 0.1.0 → 2.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/dist/index.d.mts CHANGED
@@ -65,4 +65,62 @@ type UseAspectlyIframeReturn = [
65
65
  */
66
66
  declare const useAspectlyIframe: ({ url, timeout, }: UseAspectlyIframeOptions) => UseAspectlyIframeReturn;
67
67
 
68
- export { type AspectlyIframeProps, type UseAspectlyIframeOptions, type UseAspectlyIframeReturn, useAspectlyIframe };
68
+ /**
69
+ * Options for the useAspectlyWindow hook
70
+ */
71
+ interface UseAspectlyWindowOptions extends BridgeOptions {
72
+ /** URL to open in the popup window */
73
+ url: string;
74
+ /** Window features string (e.g., 'width=800,height=600') */
75
+ features?: string;
76
+ /** Window target name (default: '_blank') */
77
+ target?: string;
78
+ }
79
+ /**
80
+ * Return type for useAspectlyWindow hook
81
+ */
82
+ type UseAspectlyWindowReturn = [
83
+ /** Bridge instance for communication */
84
+ bridge: BridgeBase,
85
+ /** Whether the window has loaded */
86
+ loaded: boolean,
87
+ /** Open the popup window */
88
+ open: () => void,
89
+ /** Close the popup window */
90
+ close: () => void,
91
+ /** Whether the window is currently open */
92
+ isOpen: boolean
93
+ ];
94
+ /**
95
+ * React hook for opening a popup window and communicating with it via Aspectly bridge.
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * import { useAspectlyWindow } from '@aspectly/web';
100
+ *
101
+ * function App() {
102
+ * const [bridge, loaded, openWindow, closeWindow, isOpen] = useAspectlyWindow({
103
+ * url: 'https://example.com/popup',
104
+ * features: 'width=800,height=600',
105
+ * });
106
+ *
107
+ * useEffect(() => {
108
+ * if (loaded) {
109
+ * bridge.init({
110
+ * getData: async () => ({ user: 'John' })
111
+ * });
112
+ * }
113
+ * }, [loaded, bridge]);
114
+ *
115
+ * return (
116
+ * <div>
117
+ * <button onClick={openWindow}>Open Window</button>
118
+ * {isOpen && <button onClick={closeWindow}>Close Window</button>}
119
+ * </div>
120
+ * );
121
+ * }
122
+ * ```
123
+ */
124
+ declare const useAspectlyWindow: ({ url, features, target, timeout, }: UseAspectlyWindowOptions) => UseAspectlyWindowReturn;
125
+
126
+ export { type AspectlyIframeProps, type UseAspectlyIframeOptions, type UseAspectlyIframeReturn, type UseAspectlyWindowOptions, type UseAspectlyWindowReturn, useAspectlyIframe, useAspectlyWindow };
package/dist/index.d.ts CHANGED
@@ -65,4 +65,62 @@ type UseAspectlyIframeReturn = [
65
65
  */
66
66
  declare const useAspectlyIframe: ({ url, timeout, }: UseAspectlyIframeOptions) => UseAspectlyIframeReturn;
67
67
 
68
- export { type AspectlyIframeProps, type UseAspectlyIframeOptions, type UseAspectlyIframeReturn, useAspectlyIframe };
68
+ /**
69
+ * Options for the useAspectlyWindow hook
70
+ */
71
+ interface UseAspectlyWindowOptions extends BridgeOptions {
72
+ /** URL to open in the popup window */
73
+ url: string;
74
+ /** Window features string (e.g., 'width=800,height=600') */
75
+ features?: string;
76
+ /** Window target name (default: '_blank') */
77
+ target?: string;
78
+ }
79
+ /**
80
+ * Return type for useAspectlyWindow hook
81
+ */
82
+ type UseAspectlyWindowReturn = [
83
+ /** Bridge instance for communication */
84
+ bridge: BridgeBase,
85
+ /** Whether the window has loaded */
86
+ loaded: boolean,
87
+ /** Open the popup window */
88
+ open: () => void,
89
+ /** Close the popup window */
90
+ close: () => void,
91
+ /** Whether the window is currently open */
92
+ isOpen: boolean
93
+ ];
94
+ /**
95
+ * React hook for opening a popup window and communicating with it via Aspectly bridge.
96
+ *
97
+ * @example
98
+ * ```tsx
99
+ * import { useAspectlyWindow } from '@aspectly/web';
100
+ *
101
+ * function App() {
102
+ * const [bridge, loaded, openWindow, closeWindow, isOpen] = useAspectlyWindow({
103
+ * url: 'https://example.com/popup',
104
+ * features: 'width=800,height=600',
105
+ * });
106
+ *
107
+ * useEffect(() => {
108
+ * if (loaded) {
109
+ * bridge.init({
110
+ * getData: async () => ({ user: 'John' })
111
+ * });
112
+ * }
113
+ * }, [loaded, bridge]);
114
+ *
115
+ * return (
116
+ * <div>
117
+ * <button onClick={openWindow}>Open Window</button>
118
+ * {isOpen && <button onClick={closeWindow}>Close Window</button>}
119
+ * </div>
120
+ * );
121
+ * }
122
+ * ```
123
+ */
124
+ declare const useAspectlyWindow: ({ url, features, target, timeout, }: UseAspectlyWindowOptions) => UseAspectlyWindowReturn;
125
+
126
+ export { type AspectlyIframeProps, type UseAspectlyIframeOptions, type UseAspectlyIframeReturn, type UseAspectlyWindowOptions, type UseAspectlyWindowReturn, useAspectlyIframe, useAspectlyWindow };
package/dist/index.js CHANGED
@@ -19,10 +19,16 @@ var useAspectlyIframe = ({
19
19
  }, [timeout]);
20
20
  const publicBridge = react.useMemo(() => new core.BridgeBase(bridge), [bridge]);
21
21
  react.useEffect(() => {
22
- const unsubscribe = core.BridgeCore.subscribe(
22
+ const wrappedListener = core.BridgeCore.wrapListener(
23
23
  bridge.handleCoreEvent
24
24
  );
25
- return () => unsubscribe();
25
+ const handler = (event) => {
26
+ if (iframeRef.current?.contentWindow && event.source === iframeRef.current.contentWindow && typeof event.data === "string") {
27
+ wrappedListener(event.data);
28
+ }
29
+ };
30
+ window.addEventListener("message", handler);
31
+ return () => window.removeEventListener("message", handler);
26
32
  }, [bridge]);
27
33
  const onLoad = react.useCallback(() => setLoaded(true), []);
28
34
  const IframeComponent = react.useCallback(
@@ -45,6 +51,71 @@ var useAspectlyIframe = ({
45
51
  );
46
52
  return [publicBridge, loaded, IframeComponent];
47
53
  };
54
+ var useAspectlyWindow = ({
55
+ url,
56
+ features,
57
+ target = "_blank",
58
+ timeout
59
+ }) => {
60
+ const windowRef = react.useRef(null);
61
+ const [loaded, setLoaded] = react.useState(false);
62
+ const [isOpen, setIsOpen] = react.useState(false);
63
+ const bridge = react.useMemo(() => {
64
+ return new core.BridgeInternal((event) => {
65
+ const bridgeEvent = core.BridgeCore.wrapBridgeEvent(event);
66
+ if (windowRef.current && !windowRef.current.closed) {
67
+ windowRef.current.postMessage(bridgeEvent, "*");
68
+ }
69
+ }, { timeout });
70
+ }, [timeout]);
71
+ const publicBridge = react.useMemo(() => new core.BridgeBase(bridge), [bridge]);
72
+ react.useEffect(() => {
73
+ const handler = (event) => {
74
+ if (windowRef.current && event.source === windowRef.current && typeof event.data === "string") {
75
+ const wrappedListener = core.BridgeCore.wrapListener(
76
+ bridge.handleCoreEvent
77
+ );
78
+ wrappedListener(event.data);
79
+ }
80
+ };
81
+ window.addEventListener("message", handler);
82
+ return () => window.removeEventListener("message", handler);
83
+ }, [bridge]);
84
+ const handleWindowClosed = react.useCallback(() => {
85
+ windowRef.current = null;
86
+ bridge.reset();
87
+ setIsOpen(false);
88
+ setLoaded(false);
89
+ }, [bridge]);
90
+ const open = react.useCallback(() => {
91
+ if (windowRef.current && !windowRef.current.closed) {
92
+ windowRef.current.focus();
93
+ return;
94
+ }
95
+ const newWindow = window.open(url, target, features);
96
+ if (newWindow) {
97
+ windowRef.current = newWindow;
98
+ setIsOpen(true);
99
+ setLoaded(false);
100
+ try {
101
+ newWindow.addEventListener("load", () => setLoaded(true));
102
+ newWindow.addEventListener("beforeunload", handleWindowClosed);
103
+ } catch {
104
+ setLoaded(true);
105
+ }
106
+ }
107
+ }, [url, target, features, handleWindowClosed]);
108
+ const close = react.useCallback(() => {
109
+ if (windowRef.current && !windowRef.current.closed) {
110
+ windowRef.current.close();
111
+ }
112
+ windowRef.current = null;
113
+ bridge.reset();
114
+ setIsOpen(false);
115
+ setLoaded(false);
116
+ }, [bridge]);
117
+ return [publicBridge, loaded, open, close, isOpen];
118
+ };
48
119
 
49
120
  Object.defineProperty(exports, "AspectlyBridge", {
50
121
  enumerable: true,
@@ -67,5 +138,6 @@ Object.defineProperty(exports, "BridgeResultType", {
67
138
  get: function () { return core.BridgeResultType; }
68
139
  });
69
140
  exports.useAspectlyIframe = useAspectlyIframe;
141
+ exports.useAspectlyWindow = useAspectlyWindow;
70
142
  //# sourceMappingURL=index.js.map
71
143
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useAspectlyIframe.tsx"],"names":["useRef","useState","useMemo","BridgeInternal","BridgeCore","BridgeBase","useEffect","useCallback","jsx"],"mappings":";;;;;;;AAkFO,IAAM,oBAAoB,CAAC;AAAA,EAChC,GAAA;AAAA,EACA;AACF,CAAA,KAAyD;AACvD,EAAA,MAAM,SAAA,GAAYA,aAA0B,IAAI,CAAA;AAChD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIC,eAAkB,KAAK,CAAA;AAEnD,EAAA,MAAM,MAAA,GAASC,cAAQ,MAAM;AAC3B,IAAA,OAAO,IAAIC,mBAAA,CAAe,CAAC,KAAA,KAAwB;AACjD,MAAA,MAAM,WAAA,GAAcC,eAAA,CAAW,eAAA,CAAgB,KAAK,CAAA;AACpD,MAAA,SAAA,CAAU,OAAA,EAAS,aAAA,EAAe,WAAA,CAAY,WAAA,EAAa,GAAG,CAAA;AAAA,IAChE,CAAA,EAAG,EAAE,OAAA,EAAS,CAAA;AAAA,EAChB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,YAAA,GAAeF,cAAQ,MAAM,IAAIG,gBAAW,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEnE,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,cAAcF,eAAA,CAAW,SAAA;AAAA,MAC7B,MAAA,CAAO;AAAA,KACT;AACA,IAAA,OAAO,MAAM,WAAA,EAAY;AAAA,EAC3B,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,SAASG,iBAAA,CAAY,MAAM,UAAU,IAAI,CAAA,EAAG,EAAE,CAAA;AAEpD,EAAA,MAAM,eAAA,GAA0DA,iBAAA;AAAA,IAC9D,CAAC,EAAE,KAAA,EAAO,GAAG,OAAM,KAA2B;AAC5C,MAAA,uBACEC,cAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACE,GAAG,KAAA;AAAA,UACJ,MAAA;AAAA,UACA,GAAA,EAAK,SAAA;AAAA,UACL,KAAA,EAAO;AAAA,YACL,MAAA,EAAQ,CAAA;AAAA,YACR,GAAG;AAAA,WACL;AAAA,UACA,GAAA,EAAK;AAAA;AAAA,OACP;AAAA,IAEJ,CAAA;AAAA,IACA,CAAC,KAAK,MAAM;AAAA,GACd;AAEA,EAAA,OAAO,CAAC,YAAA,EAAc,MAAA,EAAQ,eAAe,CAAA;AAC/C","file":"index.js","sourcesContent":["import {\n FunctionComponent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n CSSProperties,\n IframeHTMLAttributes,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyIframe hook\n */\nexport interface UseAspectlyIframeOptions extends BridgeOptions {\n /** URL to load in the iframe */\n url: string;\n}\n\n/**\n * Props for the iframe component\n */\nexport interface AspectlyIframeProps\n extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'src' | 'onLoad'> {\n /** Optional error handler */\n onError?: (error: unknown) => void;\n /** Custom styles */\n style?: CSSProperties;\n}\n\n/**\n * Return type for useAspectlyIframe hook\n */\nexport type UseAspectlyIframeReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the iframe has loaded */\n loaded: boolean,\n /** React component to render the iframe */\n IframeComponent: FunctionComponent<AspectlyIframeProps>\n];\n\n/**\n * React hook for embedding an iframe and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyIframe } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, Iframe] = useAspectlyIframe({\n * url: 'https://example.com/widget'\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * const handleClick = async () => {\n * const result = await bridge.send('greet', { name: 'World' });\n * console.log(result);\n * };\n *\n * return (\n * <div>\n * <Iframe style={{ width: '100%', height: 400 }} />\n * <button onClick={handleClick}>Send Message</button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyIframe = ({\n url,\n timeout,\n}: UseAspectlyIframeOptions): UseAspectlyIframeReturn => {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n iframeRef.current?.contentWindow?.postMessage(bridgeEvent, '*');\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const unsubscribe = BridgeCore.subscribe(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n return () => unsubscribe();\n }, [bridge]);\n\n const onLoad = useCallback(() => setLoaded(true), []);\n\n const IframeComponent: FunctionComponent<AspectlyIframeProps> = useCallback(\n ({ style, ...props }: AspectlyIframeProps) => {\n return (\n <iframe\n {...props}\n onLoad={onLoad}\n ref={iframeRef}\n style={{\n border: 0,\n ...style,\n }}\n src={url}\n />\n );\n },\n [url, onLoad]\n );\n\n return [publicBridge, loaded, IframeComponent];\n};\n"]}
1
+ {"version":3,"sources":["../src/useAspectlyIframe.tsx","../src/useAspectlyWindow.tsx"],"names":["useRef","useState","useMemo","BridgeInternal","BridgeCore","BridgeBase","useEffect","useCallback","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import {\n FunctionComponent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n CSSProperties,\n IframeHTMLAttributes,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyIframe hook\n */\nexport interface UseAspectlyIframeOptions extends BridgeOptions {\n /** URL to load in the iframe */\n url: string;\n}\n\n/**\n * Props for the iframe component\n */\nexport interface AspectlyIframeProps\n extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'src' | 'onLoad'> {\n /** Optional error handler */\n onError?: (error: unknown) => void;\n /** Custom styles */\n style?: CSSProperties;\n}\n\n/**\n * Return type for useAspectlyIframe hook\n */\nexport type UseAspectlyIframeReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the iframe has loaded */\n loaded: boolean,\n /** React component to render the iframe */\n IframeComponent: FunctionComponent<AspectlyIframeProps>\n];\n\n/**\n * React hook for embedding an iframe and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyIframe } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, Iframe] = useAspectlyIframe({\n * url: 'https://example.com/widget'\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * const handleClick = async () => {\n * const result = await bridge.send('greet', { name: 'World' });\n * console.log(result);\n * };\n *\n * return (\n * <div>\n * <Iframe style={{ width: '100%', height: 400 }} />\n * <button onClick={handleClick}>Send Message</button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyIframe = ({\n url,\n timeout,\n}: UseAspectlyIframeOptions): UseAspectlyIframeReturn => {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n iframeRef.current?.contentWindow?.postMessage(bridgeEvent, '*');\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const wrappedListener = BridgeCore.wrapListener(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n\n const handler = (event: MessageEvent): void => {\n if (\n iframeRef.current?.contentWindow &&\n event.source === iframeRef.current.contentWindow &&\n typeof event.data === 'string'\n ) {\n wrappedListener(event.data);\n }\n };\n\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n }, [bridge]);\n\n const onLoad = useCallback(() => setLoaded(true), []);\n\n const IframeComponent: FunctionComponent<AspectlyIframeProps> = useCallback(\n ({ style, ...props }: AspectlyIframeProps) => {\n return (\n <iframe\n {...props}\n onLoad={onLoad}\n ref={iframeRef}\n style={{\n border: 0,\n ...style,\n }}\n src={url}\n />\n );\n },\n [url, onLoad]\n );\n\n return [publicBridge, loaded, IframeComponent];\n};\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyWindow hook\n */\nexport interface UseAspectlyWindowOptions extends BridgeOptions {\n /** URL to open in the popup window */\n url: string;\n /** Window features string (e.g., 'width=800,height=600') */\n features?: string;\n /** Window target name (default: '_blank') */\n target?: string;\n}\n\n/**\n * Return type for useAspectlyWindow hook\n */\nexport type UseAspectlyWindowReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the window has loaded */\n loaded: boolean,\n /** Open the popup window */\n open: () => void,\n /** Close the popup window */\n close: () => void,\n /** Whether the window is currently open */\n isOpen: boolean,\n];\n\n/**\n * React hook for opening a popup window and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyWindow } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, openWindow, closeWindow, isOpen] = useAspectlyWindow({\n * url: 'https://example.com/popup',\n * features: 'width=800,height=600',\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * return (\n * <div>\n * <button onClick={openWindow}>Open Window</button>\n * {isOpen && <button onClick={closeWindow}>Close Window</button>}\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyWindow = ({\n url,\n features,\n target = '_blank',\n timeout,\n}: UseAspectlyWindowOptions): UseAspectlyWindowReturn => {\n const windowRef = useRef<Window | null>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.postMessage(bridgeEvent, '*');\n }\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const handler = (event: MessageEvent): void => {\n if (\n windowRef.current &&\n event.source === windowRef.current &&\n typeof event.data === 'string'\n ) {\n const wrappedListener = BridgeCore.wrapListener(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n wrappedListener(event.data);\n }\n };\n\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n }, [bridge]);\n\n const handleWindowClosed = useCallback(() => {\n windowRef.current = null;\n bridge.reset();\n setIsOpen(false);\n setLoaded(false);\n }, [bridge]);\n\n const open = useCallback(() => {\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.focus();\n return;\n }\n\n const newWindow = window.open(url, target, features);\n if (newWindow) {\n windowRef.current = newWindow;\n setIsOpen(true);\n setLoaded(false);\n\n try {\n newWindow.addEventListener('load', () => setLoaded(true));\n newWindow.addEventListener('beforeunload', handleWindowClosed);\n } catch {\n // Cross-origin: can't add listeners\n setLoaded(true);\n }\n }\n }, [url, target, features, handleWindowClosed]);\n\n const close = useCallback(() => {\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.close();\n }\n windowRef.current = null;\n bridge.reset();\n setIsOpen(false);\n setLoaded(false);\n }, [bridge]);\n\n return [publicBridge, loaded, open, close, isOpen];\n};\n"]}
package/dist/index.mjs CHANGED
@@ -18,10 +18,16 @@ var useAspectlyIframe = ({
18
18
  }, [timeout]);
19
19
  const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);
20
20
  useEffect(() => {
21
- const unsubscribe = BridgeCore.subscribe(
21
+ const wrappedListener = BridgeCore.wrapListener(
22
22
  bridge.handleCoreEvent
23
23
  );
24
- return () => unsubscribe();
24
+ const handler = (event) => {
25
+ if (iframeRef.current?.contentWindow && event.source === iframeRef.current.contentWindow && typeof event.data === "string") {
26
+ wrappedListener(event.data);
27
+ }
28
+ };
29
+ window.addEventListener("message", handler);
30
+ return () => window.removeEventListener("message", handler);
25
31
  }, [bridge]);
26
32
  const onLoad = useCallback(() => setLoaded(true), []);
27
33
  const IframeComponent = useCallback(
@@ -44,7 +50,72 @@ var useAspectlyIframe = ({
44
50
  );
45
51
  return [publicBridge, loaded, IframeComponent];
46
52
  };
53
+ var useAspectlyWindow = ({
54
+ url,
55
+ features,
56
+ target = "_blank",
57
+ timeout
58
+ }) => {
59
+ const windowRef = useRef(null);
60
+ const [loaded, setLoaded] = useState(false);
61
+ const [isOpen, setIsOpen] = useState(false);
62
+ const bridge = useMemo(() => {
63
+ return new BridgeInternal((event) => {
64
+ const bridgeEvent = BridgeCore.wrapBridgeEvent(event);
65
+ if (windowRef.current && !windowRef.current.closed) {
66
+ windowRef.current.postMessage(bridgeEvent, "*");
67
+ }
68
+ }, { timeout });
69
+ }, [timeout]);
70
+ const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);
71
+ useEffect(() => {
72
+ const handler = (event) => {
73
+ if (windowRef.current && event.source === windowRef.current && typeof event.data === "string") {
74
+ const wrappedListener = BridgeCore.wrapListener(
75
+ bridge.handleCoreEvent
76
+ );
77
+ wrappedListener(event.data);
78
+ }
79
+ };
80
+ window.addEventListener("message", handler);
81
+ return () => window.removeEventListener("message", handler);
82
+ }, [bridge]);
83
+ const handleWindowClosed = useCallback(() => {
84
+ windowRef.current = null;
85
+ bridge.reset();
86
+ setIsOpen(false);
87
+ setLoaded(false);
88
+ }, [bridge]);
89
+ const open = useCallback(() => {
90
+ if (windowRef.current && !windowRef.current.closed) {
91
+ windowRef.current.focus();
92
+ return;
93
+ }
94
+ const newWindow = window.open(url, target, features);
95
+ if (newWindow) {
96
+ windowRef.current = newWindow;
97
+ setIsOpen(true);
98
+ setLoaded(false);
99
+ try {
100
+ newWindow.addEventListener("load", () => setLoaded(true));
101
+ newWindow.addEventListener("beforeunload", handleWindowClosed);
102
+ } catch {
103
+ setLoaded(true);
104
+ }
105
+ }
106
+ }, [url, target, features, handleWindowClosed]);
107
+ const close = useCallback(() => {
108
+ if (windowRef.current && !windowRef.current.closed) {
109
+ windowRef.current.close();
110
+ }
111
+ windowRef.current = null;
112
+ bridge.reset();
113
+ setIsOpen(false);
114
+ setLoaded(false);
115
+ }, [bridge]);
116
+ return [publicBridge, loaded, open, close, isOpen];
117
+ };
47
118
 
48
- export { useAspectlyIframe };
119
+ export { useAspectlyIframe, useAspectlyWindow };
49
120
  //# sourceMappingURL=index.mjs.map
50
121
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/useAspectlyIframe.tsx"],"names":[],"mappings":";;;;;;AAkFO,IAAM,oBAAoB,CAAC;AAAA,EAChC,GAAA;AAAA,EACA;AACF,CAAA,KAAyD;AACvD,EAAA,MAAM,SAAA,GAAY,OAA0B,IAAI,CAAA;AAChD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAkB,KAAK,CAAA;AAEnD,EAAA,MAAM,MAAA,GAAS,QAAQ,MAAM;AAC3B,IAAA,OAAO,IAAI,cAAA,CAAe,CAAC,KAAA,KAAwB;AACjD,MAAA,MAAM,WAAA,GAAc,UAAA,CAAW,eAAA,CAAgB,KAAK,CAAA;AACpD,MAAA,SAAA,CAAU,OAAA,EAAS,aAAA,EAAe,WAAA,CAAY,WAAA,EAAa,GAAG,CAAA;AAAA,IAChE,CAAA,EAAG,EAAE,OAAA,EAAS,CAAA;AAAA,EAChB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,YAAA,GAAe,QAAQ,MAAM,IAAI,WAAW,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEnE,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,cAAc,UAAA,CAAW,SAAA;AAAA,MAC7B,MAAA,CAAO;AAAA,KACT;AACA,IAAA,OAAO,MAAM,WAAA,EAAY;AAAA,EAC3B,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,SAAS,WAAA,CAAY,MAAM,UAAU,IAAI,CAAA,EAAG,EAAE,CAAA;AAEpD,EAAA,MAAM,eAAA,GAA0D,WAAA;AAAA,IAC9D,CAAC,EAAE,KAAA,EAAO,GAAG,OAAM,KAA2B;AAC5C,MAAA,uBACE,GAAA;AAAA,QAAC,QAAA;AAAA,QAAA;AAAA,UACE,GAAG,KAAA;AAAA,UACJ,MAAA;AAAA,UACA,GAAA,EAAK,SAAA;AAAA,UACL,KAAA,EAAO;AAAA,YACL,MAAA,EAAQ,CAAA;AAAA,YACR,GAAG;AAAA,WACL;AAAA,UACA,GAAA,EAAK;AAAA;AAAA,OACP;AAAA,IAEJ,CAAA;AAAA,IACA,CAAC,KAAK,MAAM;AAAA,GACd;AAEA,EAAA,OAAO,CAAC,YAAA,EAAc,MAAA,EAAQ,eAAe,CAAA;AAC/C","file":"index.mjs","sourcesContent":["import {\n FunctionComponent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n CSSProperties,\n IframeHTMLAttributes,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyIframe hook\n */\nexport interface UseAspectlyIframeOptions extends BridgeOptions {\n /** URL to load in the iframe */\n url: string;\n}\n\n/**\n * Props for the iframe component\n */\nexport interface AspectlyIframeProps\n extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'src' | 'onLoad'> {\n /** Optional error handler */\n onError?: (error: unknown) => void;\n /** Custom styles */\n style?: CSSProperties;\n}\n\n/**\n * Return type for useAspectlyIframe hook\n */\nexport type UseAspectlyIframeReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the iframe has loaded */\n loaded: boolean,\n /** React component to render the iframe */\n IframeComponent: FunctionComponent<AspectlyIframeProps>\n];\n\n/**\n * React hook for embedding an iframe and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyIframe } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, Iframe] = useAspectlyIframe({\n * url: 'https://example.com/widget'\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * const handleClick = async () => {\n * const result = await bridge.send('greet', { name: 'World' });\n * console.log(result);\n * };\n *\n * return (\n * <div>\n * <Iframe style={{ width: '100%', height: 400 }} />\n * <button onClick={handleClick}>Send Message</button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyIframe = ({\n url,\n timeout,\n}: UseAspectlyIframeOptions): UseAspectlyIframeReturn => {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n iframeRef.current?.contentWindow?.postMessage(bridgeEvent, '*');\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const unsubscribe = BridgeCore.subscribe(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n return () => unsubscribe();\n }, [bridge]);\n\n const onLoad = useCallback(() => setLoaded(true), []);\n\n const IframeComponent: FunctionComponent<AspectlyIframeProps> = useCallback(\n ({ style, ...props }: AspectlyIframeProps) => {\n return (\n <iframe\n {...props}\n onLoad={onLoad}\n ref={iframeRef}\n style={{\n border: 0,\n ...style,\n }}\n src={url}\n />\n );\n },\n [url, onLoad]\n );\n\n return [publicBridge, loaded, IframeComponent];\n};\n"]}
1
+ {"version":3,"sources":["../src/useAspectlyIframe.tsx","../src/useAspectlyWindow.tsx"],"names":["useRef","useState","useMemo","BridgeInternal","BridgeCore","BridgeBase","useEffect","useCallback"],"mappings":"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","file":"index.mjs","sourcesContent":["import {\n FunctionComponent,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n CSSProperties,\n IframeHTMLAttributes,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyIframe hook\n */\nexport interface UseAspectlyIframeOptions extends BridgeOptions {\n /** URL to load in the iframe */\n url: string;\n}\n\n/**\n * Props for the iframe component\n */\nexport interface AspectlyIframeProps\n extends Omit<IframeHTMLAttributes<HTMLIFrameElement>, 'src' | 'onLoad'> {\n /** Optional error handler */\n onError?: (error: unknown) => void;\n /** Custom styles */\n style?: CSSProperties;\n}\n\n/**\n * Return type for useAspectlyIframe hook\n */\nexport type UseAspectlyIframeReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the iframe has loaded */\n loaded: boolean,\n /** React component to render the iframe */\n IframeComponent: FunctionComponent<AspectlyIframeProps>\n];\n\n/**\n * React hook for embedding an iframe and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyIframe } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, Iframe] = useAspectlyIframe({\n * url: 'https://example.com/widget'\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * const handleClick = async () => {\n * const result = await bridge.send('greet', { name: 'World' });\n * console.log(result);\n * };\n *\n * return (\n * <div>\n * <Iframe style={{ width: '100%', height: 400 }} />\n * <button onClick={handleClick}>Send Message</button>\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyIframe = ({\n url,\n timeout,\n}: UseAspectlyIframeOptions): UseAspectlyIframeReturn => {\n const iframeRef = useRef<HTMLIFrameElement>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n iframeRef.current?.contentWindow?.postMessage(bridgeEvent, '*');\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const wrappedListener = BridgeCore.wrapListener(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n\n const handler = (event: MessageEvent): void => {\n if (\n iframeRef.current?.contentWindow &&\n event.source === iframeRef.current.contentWindow &&\n typeof event.data === 'string'\n ) {\n wrappedListener(event.data);\n }\n };\n\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n }, [bridge]);\n\n const onLoad = useCallback(() => setLoaded(true), []);\n\n const IframeComponent: FunctionComponent<AspectlyIframeProps> = useCallback(\n ({ style, ...props }: AspectlyIframeProps) => {\n return (\n <iframe\n {...props}\n onLoad={onLoad}\n ref={iframeRef}\n style={{\n border: 0,\n ...style,\n }}\n src={url}\n />\n );\n },\n [url, onLoad]\n );\n\n return [publicBridge, loaded, IframeComponent];\n};\n","import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n BridgeCore,\n BridgeInternal,\n BridgeBase,\n BridgeOptions,\n} from '@aspectly/core';\n\n/**\n * Options for the useAspectlyWindow hook\n */\nexport interface UseAspectlyWindowOptions extends BridgeOptions {\n /** URL to open in the popup window */\n url: string;\n /** Window features string (e.g., 'width=800,height=600') */\n features?: string;\n /** Window target name (default: '_blank') */\n target?: string;\n}\n\n/**\n * Return type for useAspectlyWindow hook\n */\nexport type UseAspectlyWindowReturn = [\n /** Bridge instance for communication */\n bridge: BridgeBase,\n /** Whether the window has loaded */\n loaded: boolean,\n /** Open the popup window */\n open: () => void,\n /** Close the popup window */\n close: () => void,\n /** Whether the window is currently open */\n isOpen: boolean,\n];\n\n/**\n * React hook for opening a popup window and communicating with it via Aspectly bridge.\n *\n * @example\n * ```tsx\n * import { useAspectlyWindow } from '@aspectly/web';\n *\n * function App() {\n * const [bridge, loaded, openWindow, closeWindow, isOpen] = useAspectlyWindow({\n * url: 'https://example.com/popup',\n * features: 'width=800,height=600',\n * });\n *\n * useEffect(() => {\n * if (loaded) {\n * bridge.init({\n * getData: async () => ({ user: 'John' })\n * });\n * }\n * }, [loaded, bridge]);\n *\n * return (\n * <div>\n * <button onClick={openWindow}>Open Window</button>\n * {isOpen && <button onClick={closeWindow}>Close Window</button>}\n * </div>\n * );\n * }\n * ```\n */\nexport const useAspectlyWindow = ({\n url,\n features,\n target = '_blank',\n timeout,\n}: UseAspectlyWindowOptions): UseAspectlyWindowReturn => {\n const windowRef = useRef<Window | null>(null);\n const [loaded, setLoaded] = useState<boolean>(false);\n const [isOpen, setIsOpen] = useState<boolean>(false);\n\n const bridge = useMemo(() => {\n return new BridgeInternal((event: object): void => {\n const bridgeEvent = BridgeCore.wrapBridgeEvent(event);\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.postMessage(bridgeEvent, '*');\n }\n }, { timeout });\n }, [timeout]);\n\n const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);\n\n useEffect(() => {\n const handler = (event: MessageEvent): void => {\n if (\n windowRef.current &&\n event.source === windowRef.current &&\n typeof event.data === 'string'\n ) {\n const wrappedListener = BridgeCore.wrapListener(\n bridge.handleCoreEvent as (event: unknown) => void\n );\n wrappedListener(event.data);\n }\n };\n\n window.addEventListener('message', handler);\n return () => window.removeEventListener('message', handler);\n }, [bridge]);\n\n const handleWindowClosed = useCallback(() => {\n windowRef.current = null;\n bridge.reset();\n setIsOpen(false);\n setLoaded(false);\n }, [bridge]);\n\n const open = useCallback(() => {\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.focus();\n return;\n }\n\n const newWindow = window.open(url, target, features);\n if (newWindow) {\n windowRef.current = newWindow;\n setIsOpen(true);\n setLoaded(false);\n\n try {\n newWindow.addEventListener('load', () => setLoaded(true));\n newWindow.addEventListener('beforeunload', handleWindowClosed);\n } catch {\n // Cross-origin: can't add listeners\n setLoaded(true);\n }\n }\n }, [url, target, features, handleWindowClosed]);\n\n const close = useCallback(() => {\n if (windowRef.current && !windowRef.current.closed) {\n windowRef.current.close();\n }\n windowRef.current = null;\n bridge.reset();\n setIsOpen(false);\n setLoaded(false);\n }, [bridge]);\n\n return [publicBridge, loaded, open, close, isOpen];\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspectly/web",
3
- "version": "0.1.0",
3
+ "version": "2.0.0",
4
4
  "description": "Web/iframe integration for Aspectly bridge - React hooks for embedding and communicating with iframes",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -49,7 +49,7 @@
49
49
  "react": ">=17.0.0"
50
50
  },
51
51
  "dependencies": {
52
- "@aspectly/core": "0.1.0"
52
+ "@aspectly/core": "2.0.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/react": "^18.2.0",
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // Main exports
2
2
  export { useAspectlyIframe } from './useAspectlyIframe';
3
+ export { useAspectlyWindow } from './useAspectlyWindow';
3
4
 
4
5
  // Type exports
5
6
  export type {
@@ -8,6 +9,11 @@ export type {
8
9
  AspectlyIframeProps,
9
10
  } from './useAspectlyIframe';
10
11
 
12
+ export type {
13
+ UseAspectlyWindowOptions,
14
+ UseAspectlyWindowReturn,
15
+ } from './useAspectlyWindow';
16
+
11
17
  // Re-export core types for convenience
12
18
  export {
13
19
  AspectlyBridge,
@@ -6,6 +6,12 @@ import { useAspectlyIframe } from './useAspectlyIframe';
6
6
  vi.mock('@aspectly/core', () => ({
7
7
  BridgeCore: {
8
8
  wrapBridgeEvent: vi.fn((event) => JSON.stringify({ type: 'BridgeEvent', event })),
9
+ wrapListener: vi.fn((listener) => (data: string) => {
10
+ try {
11
+ const parsed = JSON.parse(data);
12
+ if (parsed?.type === 'BridgeEvent') listener(parsed.event);
13
+ } catch { /* ignore parse errors */ }
14
+ }),
9
15
  subscribe: vi.fn().mockReturnValue(vi.fn()),
10
16
  },
11
17
  BridgeInternal: vi.fn().mockImplementation(() => ({
@@ -97,10 +97,22 @@ export const useAspectlyIframe = ({
97
97
  const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);
98
98
 
99
99
  useEffect(() => {
100
- const unsubscribe = BridgeCore.subscribe(
100
+ const wrappedListener = BridgeCore.wrapListener(
101
101
  bridge.handleCoreEvent as (event: unknown) => void
102
102
  );
103
- return () => unsubscribe();
103
+
104
+ const handler = (event: MessageEvent): void => {
105
+ if (
106
+ iframeRef.current?.contentWindow &&
107
+ event.source === iframeRef.current.contentWindow &&
108
+ typeof event.data === 'string'
109
+ ) {
110
+ wrappedListener(event.data);
111
+ }
112
+ };
113
+
114
+ window.addEventListener('message', handler);
115
+ return () => window.removeEventListener('message', handler);
104
116
  }, [bridge]);
105
117
 
106
118
  const onLoad = useCallback(() => setLoaded(true), []);
@@ -0,0 +1,152 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
8
+ import {
9
+ BridgeCore,
10
+ BridgeInternal,
11
+ BridgeBase,
12
+ BridgeOptions,
13
+ } from '@aspectly/core';
14
+
15
+ /**
16
+ * Options for the useAspectlyWindow hook
17
+ */
18
+ export interface UseAspectlyWindowOptions extends BridgeOptions {
19
+ /** URL to open in the popup window */
20
+ url: string;
21
+ /** Window features string (e.g., 'width=800,height=600') */
22
+ features?: string;
23
+ /** Window target name (default: '_blank') */
24
+ target?: string;
25
+ }
26
+
27
+ /**
28
+ * Return type for useAspectlyWindow hook
29
+ */
30
+ export type UseAspectlyWindowReturn = [
31
+ /** Bridge instance for communication */
32
+ bridge: BridgeBase,
33
+ /** Whether the window has loaded */
34
+ loaded: boolean,
35
+ /** Open the popup window */
36
+ open: () => void,
37
+ /** Close the popup window */
38
+ close: () => void,
39
+ /** Whether the window is currently open */
40
+ isOpen: boolean,
41
+ ];
42
+
43
+ /**
44
+ * React hook for opening a popup window and communicating with it via Aspectly bridge.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * import { useAspectlyWindow } from '@aspectly/web';
49
+ *
50
+ * function App() {
51
+ * const [bridge, loaded, openWindow, closeWindow, isOpen] = useAspectlyWindow({
52
+ * url: 'https://example.com/popup',
53
+ * features: 'width=800,height=600',
54
+ * });
55
+ *
56
+ * useEffect(() => {
57
+ * if (loaded) {
58
+ * bridge.init({
59
+ * getData: async () => ({ user: 'John' })
60
+ * });
61
+ * }
62
+ * }, [loaded, bridge]);
63
+ *
64
+ * return (
65
+ * <div>
66
+ * <button onClick={openWindow}>Open Window</button>
67
+ * {isOpen && <button onClick={closeWindow}>Close Window</button>}
68
+ * </div>
69
+ * );
70
+ * }
71
+ * ```
72
+ */
73
+ export const useAspectlyWindow = ({
74
+ url,
75
+ features,
76
+ target = '_blank',
77
+ timeout,
78
+ }: UseAspectlyWindowOptions): UseAspectlyWindowReturn => {
79
+ const windowRef = useRef<Window | null>(null);
80
+ const [loaded, setLoaded] = useState<boolean>(false);
81
+ const [isOpen, setIsOpen] = useState<boolean>(false);
82
+
83
+ const bridge = useMemo(() => {
84
+ return new BridgeInternal((event: object): void => {
85
+ const bridgeEvent = BridgeCore.wrapBridgeEvent(event);
86
+ if (windowRef.current && !windowRef.current.closed) {
87
+ windowRef.current.postMessage(bridgeEvent, '*');
88
+ }
89
+ }, { timeout });
90
+ }, [timeout]);
91
+
92
+ const publicBridge = useMemo(() => new BridgeBase(bridge), [bridge]);
93
+
94
+ useEffect(() => {
95
+ const handler = (event: MessageEvent): void => {
96
+ if (
97
+ windowRef.current &&
98
+ event.source === windowRef.current &&
99
+ typeof event.data === 'string'
100
+ ) {
101
+ const wrappedListener = BridgeCore.wrapListener(
102
+ bridge.handleCoreEvent as (event: unknown) => void
103
+ );
104
+ wrappedListener(event.data);
105
+ }
106
+ };
107
+
108
+ window.addEventListener('message', handler);
109
+ return () => window.removeEventListener('message', handler);
110
+ }, [bridge]);
111
+
112
+ const handleWindowClosed = useCallback(() => {
113
+ windowRef.current = null;
114
+ bridge.reset();
115
+ setIsOpen(false);
116
+ setLoaded(false);
117
+ }, [bridge]);
118
+
119
+ const open = useCallback(() => {
120
+ if (windowRef.current && !windowRef.current.closed) {
121
+ windowRef.current.focus();
122
+ return;
123
+ }
124
+
125
+ const newWindow = window.open(url, target, features);
126
+ if (newWindow) {
127
+ windowRef.current = newWindow;
128
+ setIsOpen(true);
129
+ setLoaded(false);
130
+
131
+ try {
132
+ newWindow.addEventListener('load', () => setLoaded(true));
133
+ newWindow.addEventListener('beforeunload', handleWindowClosed);
134
+ } catch {
135
+ // Cross-origin: can't add listeners
136
+ setLoaded(true);
137
+ }
138
+ }
139
+ }, [url, target, features, handleWindowClosed]);
140
+
141
+ const close = useCallback(() => {
142
+ if (windowRef.current && !windowRef.current.closed) {
143
+ windowRef.current.close();
144
+ }
145
+ windowRef.current = null;
146
+ bridge.reset();
147
+ setIsOpen(false);
148
+ setLoaded(false);
149
+ }, [bridge]);
150
+
151
+ return [publicBridge, loaded, open, close, isOpen];
152
+ };