diff --git a/examples/src/main/java/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.java b/examples/src/main/java/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.java new file mode 100644 index 00000000..83c2bd7e --- /dev/null +++ b/examples/src/main/java/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.java @@ -0,0 +1,150 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.teamdev.jxbrowser.examples; + +import static com.teamdev.jxbrowser.engine.RenderingMode.HARDWARE_ACCELERATED; + +import com.teamdev.jxbrowser.browser.callback.InjectJsCallback; +import com.teamdev.jxbrowser.engine.Engine; +import com.teamdev.jxbrowser.js.JsAccessible; +import com.teamdev.jxbrowser.js.JsObject; +import com.teamdev.jxbrowser.view.swing.BrowserView; +import java.awt.BorderLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.net.URL; +import javax.swing.JFrame; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; + +/** + * This example demonstrates how to intercept web socket data by using JS-Java + * bridget capabilities. + */ +public final class WebSocketDataInterceptor { + + private static final String JAVA_SCRIPT = """ + var oldSocket = window.WebSocket; + window.WebSocket = function (url) { + var socket = new oldSocket(url); + socket.onopen = () => { + window.webSocketCallback.socketOpened(socket); + this.onopen(); + }; + socket.onmessage = (message) => { + window.webSocketCallback.messageReceived(socket,message.data); + this.onmessage(message); + }; + var onclose = socket.onclose; + socket.onclose = (closeEvent) => { + this.onclose(); + window.webSocketCallback.socketClosed(closeEvent); + this.close(closeEvent); + }; + this.close = (event) => { + window.webSocketCallback.socketClosed(event); + socket.close(); + }; + this.send = (data) => { + window.webSocketCallback.beforeSendData(socket,data); + socket.send(data); + }; + };"""; + + private static String getUrl() { + ClassLoader classLoader = WebSocketDataInterceptor.class.getClassLoader(); + if (classLoader != null) { + URL resource = classLoader.getResource("echo.html"); + if (resource != null) { + return resource.toString(); + } + } + throw new IllegalArgumentException("Resource not found: echo.html"); + } + + public static void main(String[] args) { + var engine = Engine.newInstance(HARDWARE_ACCELERATED); + var browser = engine.newBrowser(); + browser.set(InjectJsCallback.class, params -> { + var frame = params.frame(); + JsObject jsObject = frame.executeJavaScript("window"); + if (jsObject != null) { + jsObject.putProperty("webSocketCallback", + new WebSocketCallback()); + } + frame.executeJavaScript(JAVA_SCRIPT); + return InjectJsCallback.Response.proceed(); + }); + + SwingUtilities.invokeLater(() -> { + var view = BrowserView.newInstance(browser); + + var frame = new JFrame("Web socket data interceptor"); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + engine.close(); + } + }); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + frame.add(view, BorderLayout.CENTER); + frame.setSize(700, 500); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + + browser.navigation().loadUrlAndWait(getUrl()); + } + + /** + * A JS-accessible class used as a web socket event listener in JS. + * + *

The class is marked with the {@code @JsAccessible} annotation to tell + * JavaScript that all public methods of the injected object can be invoked + * from the JavaScript side. + * + *

Please note, only public classes and static nested classes can be + * injected into JavaScript. + */ + @JsAccessible + public final static class WebSocketCallback { + + @SuppressWarnings("unused") // To be called from JavaScript. + public void socketClosed(JsObject closeEvent) { + System.out.println("WebSocketCallback.socketClosed"); + } + + @SuppressWarnings("unused") // To be called from JavaScript. + public void messageReceived(JsObject socket, Object data) { + System.out.println("WebSocketCallback.messageReceived: " + data); + } + + @SuppressWarnings("unused") // To be called from JavaScript. + public void socketOpened(JsObject socket) { + System.out.println("WebSocketCallback.socketOpened"); + } + + @SuppressWarnings("unused") // To be called from JavaScript. + public void beforeSendData(JsObject socket, Object data) { + System.out.println("WebSocketCallback.beforeSendData: " + data); + } + } +} diff --git a/examples/src/main/kotlin/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.kt b/examples/src/main/kotlin/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.kt new file mode 100644 index 00000000..bcb7d519 --- /dev/null +++ b/examples/src/main/kotlin/com/teamdev/jxbrowser/examples/WebSocketDataInterceptor.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2025, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.teamdev.jxbrowser.examples + +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.window.singleWindowApplication +import com.teamdev.jxbrowser.browser.callback.InjectJsCallback +import com.teamdev.jxbrowser.dsl.Engine +import com.teamdev.jxbrowser.dsl.browser.navigation +import com.teamdev.jxbrowser.dsl.register +import com.teamdev.jxbrowser.engine.RenderingMode +import com.teamdev.jxbrowser.js.JsAccessible +import com.teamdev.jxbrowser.js.JsObject +import com.teamdev.jxbrowser.view.compose.BrowserView + +/** + * This example demonstrates how to intercept web socket data by using JS-Kotlin + * bridget capabilities. + */ +fun main() { + val engine = Engine(RenderingMode.OFF_SCREEN) + val browser = engine.newBrowser() + browser.register(InjectJsCallback { params -> + val frame = params.frame() + val jsWindow = frame.executeJavaScript("window")!! + jsWindow.putProperty("webSocketCallback", WebSocketCallback()) + frame.executeJavaScript(JAVA_SCRIPT) + InjectJsCallback.Response.proceed() + }) + singleWindowApplication(title = "Web socket data interceptor") { + BrowserView(browser) + LaunchedEffect(Unit) { + browser.navigation.loadUrlAndWait(getUrl()) + } + } +} + +private fun getUrl(): String { + val classLoader = WebSocketDataInterceptor::class.java.classLoader + val resource = classLoader.getResource("echo.html") + if (resource != null) { + return resource.toString() + } + throw IllegalArgumentException("Resource not found: echo.html") +} + +/** + * A JS-accessible class used as web socket events listener in JS. + * + * The class is marked with [JsAccessible] annotation to tell JavaScript + * that all public methods of the injected object can be invoked from + * the JavaScript side. + * + * Please note, only public classes and static nested classes can be injected + * into JavaScript. + */ +@JsAccessible +class WebSocketCallback { + + @Suppress("unused") // To be called from JavaScript. + fun socketClosed(closeEvent: JsObject?) { + println("WebSocketCallback.socketClosed: $closeEvent") + } + + @Suppress("unused") // To be called from JavaScript. + fun messageReceived(socket: JsObject?, data: Any) { + println("WebSocketCallback.messageReceived: $socket $data") + } + + @Suppress("unused") // To be called from JavaScript. + fun socketOpened(socket: JsObject?) { + println("WebSocketCallback.socketOpened : $socket") + } + + @Suppress("unused") // To be called from JavaScript. + fun beforeSendData(socket: JsObject?, data: Any) { + println("WebSocketCallback.beforeSendData: $socket $data") + } +} + +private val JAVA_SCRIPT = """ + var oldSocket = window.WebSocket; + window.WebSocket = function (url) { + var socket = new oldSocket(url); + socket.onopen = () => { + window.webSocketCallback.socketOpened(socket); + this.onopen(); + }; + socket.onmessage = (message) => { + window.webSocketCallback.messageReceived(socket,message.data); + this.onmessage(message); + }; + var onclose = socket.onclose; + socket.onclose = (closeEvent) => { + this.onclose(); + window.webSocketCallback.socketClosed(closeEvent); + this.close(closeEvent); + }; + this.close = (event) => { + window.webSocketCallback.socketClosed(event); + socket.close(); + }; + this.send = (data) => { + window.webSocketCallback.beforeSendData(socket,data); + socket.send(data); + }; + }; +""".trimIndent() diff --git a/examples/src/main/resources/echo.html b/examples/src/main/resources/echo.html new file mode 100644 index 00000000..539c9a22 --- /dev/null +++ b/examples/src/main/resources/echo.html @@ -0,0 +1,78 @@ + + + + + + + + + + + +

+

Enter WebSocket URL and click "Connect"

+
+
+ + +
+
+ + + +
+
+
+
- Connection log will appear here
+
+
+ + +