@aspectly/web 0.1.0 → 2.0.8

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.8",
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",
@@ -22,6 +22,14 @@
22
22
  "src"
23
23
  ],
24
24
  "sideEffects": false,
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src --ext .ts,.tsx",
30
+ "clean": "rimraf dist",
31
+ "test": "vitest run"
32
+ },
25
33
  "keywords": [
26
34
  "iframe",
27
35
  "bridge",
@@ -49,7 +57,7 @@
49
57
  "react": ">=17.0.0"
50
58
  },
51
59
  "dependencies": {
52
- "@aspectly/core": "0.1.0"
60
+ "@aspectly/core": "workspace:*"
53
61
  },
54
62
  "devDependencies": {
55
63
  "@types/react": "^18.2.0",
@@ -57,13 +65,5 @@
57
65
  "tsup": "^8.0.1",
58
66
  "typescript": "^5.3.2",
59
67
  "rimraf": "^5.0.5"
60
- },
61
- "scripts": {
62
- "build": "tsup",
63
- "dev": "tsup --watch",
64
- "typecheck": "tsc --noEmit",
65
- "lint": "eslint src --ext .ts,.tsx",
66
- "clean": "rimraf dist",
67
- "test": "vitest run"
68
68
  }
69
- }
69
+ }
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
+ };
package/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2022 Zhan Isaakian
4
- Permission is hereby granted, free of charge, to any person obtaining a copy
5
- of this software and associated documentation files (the "Software"), to deal
6
- in the Software without restriction, including without limitation the rights
7
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- copies of the Software, and to permit persons to whom the Software is
9
- furnished to do so, subject to the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be included in all
12
- copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
- SOFTWARE.