@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 +59 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +74 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +74 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +6 -0
- package/src/useAspectlyIframe.test.tsx +6 -0
- package/src/useAspectlyIframe.tsx +14 -2
- package/src/useAspectlyWindow.tsx +152 -0
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
|
-
|
|
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
|
-
|
|
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
|
|
22
|
+
const wrappedListener = core.BridgeCore.wrapListener(
|
|
23
23
|
bridge.handleCoreEvent
|
|
24
24
|
);
|
|
25
|
-
|
|
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
|
|
21
|
+
const wrappedListener = BridgeCore.wrapListener(
|
|
22
22
|
bridge.handleCoreEvent
|
|
23
23
|
);
|
|
24
|
-
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":";;;;;;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,kBAAkB,UAAA,CAAW,YAAA;AAAA,MACjC,MAAA,CAAO;AAAA,KACT;AAEA,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAA8B;AAC7C,MAAA,IACE,SAAA,CAAU,OAAA,EAAS,aAAA,IACnB,KAAA,CAAM,MAAA,KAAW,SAAA,CAAU,OAAA,CAAQ,aAAA,IACnC,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,EACtB;AACA,QAAA,eAAA,CAAgB,MAAM,IAAI,CAAA;AAAA,MAC5B;AAAA,IACF,CAAA;AAEA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,OAAO,CAAA;AAC1C,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,OAAO,CAAA;AAAA,EAC5D,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;AClEO,IAAM,oBAAoB,CAAC;AAAA,EAChC,GAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA,GAAS,QAAA;AAAA,EACT;AACF,CAAA,KAAyD;AACvD,EAAA,MAAM,SAAA,GAAYA,OAAsB,IAAI,CAAA;AAC5C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIC,SAAkB,KAAK,CAAA;AACnD,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAIA,SAAkB,KAAK,CAAA;AAEnD,EAAA,MAAM,MAAA,GAASC,QAAQ,MAAM;AAC3B,IAAA,OAAO,IAAIC,cAAAA,CAAe,CAAC,KAAA,KAAwB;AACjD,MAAA,MAAM,WAAA,GAAcC,UAAAA,CAAW,eAAA,CAAgB,KAAK,CAAA;AACpD,MAAA,IAAI,SAAA,CAAU,OAAA,IAAW,CAAC,SAAA,CAAU,QAAQ,MAAA,EAAQ;AAClD,QAAA,SAAA,CAAU,OAAA,CAAQ,WAAA,CAAY,WAAA,EAAa,GAAG,CAAA;AAAA,MAChD;AAAA,IACF,CAAA,EAAG,EAAE,OAAA,EAAS,CAAA;AAAA,EAChB,CAAA,EAAG,CAAC,OAAO,CAAC,CAAA;AAEZ,EAAA,MAAM,YAAA,GAAeF,QAAQ,MAAM,IAAIG,WAAW,MAAM,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEnE,EAAAC,UAAU,MAAM;AACd,IAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAA8B;AAC7C,MAAA,IACE,SAAA,CAAU,WACV,KAAA,CAAM,MAAA,KAAW,UAAU,OAAA,IAC3B,OAAO,KAAA,CAAM,IAAA,KAAS,QAAA,EACtB;AACA,QAAA,MAAM,kBAAkBF,UAAAA,CAAW,YAAA;AAAA,UACjC,MAAA,CAAO;AAAA,SACT;AACA,QAAA,eAAA,CAAgB,MAAM,IAAI,CAAA;AAAA,MAC5B;AAAA,IACF,CAAA;AAEA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,OAAO,CAAA;AAC1C,IAAA,OAAO,MAAM,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,OAAO,CAAA;AAAA,EAC5D,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,kBAAA,GAAqBG,YAAY,MAAM;AAC3C,IAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,IAAA,MAAA,CAAO,KAAA,EAAM;AACb,IAAA,SAAA,CAAU,KAAK,CAAA;AACf,IAAA,SAAA,CAAU,KAAK,CAAA;AAAA,EACjB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,MAAM,IAAA,GAAOA,YAAY,MAAM;AAC7B,IAAA,IAAI,SAAA,CAAU,OAAA,IAAW,CAAC,SAAA,CAAU,QAAQ,MAAA,EAAQ;AAClD,MAAA,SAAA,CAAU,QAAQ,KAAA,EAAM;AACxB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,GAAA,EAAK,QAAQ,QAAQ,CAAA;AACnD,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,SAAA,CAAU,OAAA,GAAU,SAAA;AACpB,MAAA,SAAA,CAAU,IAAI,CAAA;AACd,MAAA,SAAA,CAAU,KAAK,CAAA;AAEf,MAAA,IAAI;AACF,QAAA,SAAA,CAAU,gBAAA,CAAiB,MAAA,EAAQ,MAAM,SAAA,CAAU,IAAI,CAAC,CAAA;AACxD,QAAA,SAAA,CAAU,gBAAA,CAAiB,gBAAgB,kBAAkB,CAAA;AAAA,MAC/D,CAAA,CAAA,MAAQ;AAEN,QAAA,SAAA,CAAU,IAAI,CAAA;AAAA,MAChB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,GAAA,EAAK,MAAA,EAAQ,QAAA,EAAU,kBAAkB,CAAC,CAAA;AAE9C,EAAA,MAAM,KAAA,GAAQA,YAAY,MAAM;AAC9B,IAAA,IAAI,SAAA,CAAU,OAAA,IAAW,CAAC,SAAA,CAAU,QAAQ,MAAA,EAAQ;AAClD,MAAA,SAAA,CAAU,QAAQ,KAAA,EAAM;AAAA,IAC1B;AACA,IAAA,SAAA,CAAU,OAAA,GAAU,IAAA;AACpB,IAAA,MAAA,CAAO,KAAA,EAAM;AACb,IAAA,SAAA,CAAU,KAAK,CAAA;AACf,IAAA,SAAA,CAAU,KAAK,CAAA;AAAA,EACjB,CAAA,EAAG,CAAC,MAAM,CAAC,CAAA;AAEX,EAAA,OAAO,CAAC,YAAA,EAAc,MAAA,EAAQ,IAAA,EAAM,OAAO,MAAM,CAAA;AACnD","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.
|
|
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.
|
|
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
|
|
100
|
+
const wrappedListener = BridgeCore.wrapListener(
|
|
101
101
|
bridge.handleCoreEvent as (event: unknown) => void
|
|
102
102
|
);
|
|
103
|
-
|
|
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
|
+
};
|