Skip to content
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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.
*
* <p>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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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<JsObject>("window")!!
jsWindow.putProperty("webSocketCallback", WebSocketCallback())
frame.executeJavaScript<Any>(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()
78 changes: 78 additions & 0 deletions examples/src/main/resources/echo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<!--
~ 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.
-->

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8"/>
<title></title>
</head>
<body>
<script>
var exampleSocket;

const showElement = (e, ok) => e.style.display = 'block';
const hideElement = (e, ok) => e.style.display = 'none';
function connectSocket() {
var wsurl = document.getElementById("wsurl").value;
exampleSocket = new WebSocket(
wsurl
);
exampleSocket.onmessage = (event) => {
document.getElementById("consoleLog").innerText = event.data;
};
hideElement(document.getElementById("not-connected"));
showElement(document.getElementById("connected"));
}

function sendMessage() {
exampleSocket.send(document.getElementById("message").value);
}

function disconnectSocket() {
exampleSocket.close();
showElement(document.getElementById("not-connected"));
hideElement(document.getElementById("connected"));
}

</script>
<div class="px-4 py-5 sm:p-6">
<h3>Enter WebSocket URL and click "Connect"</h3>
<form>
<div id="not-connected">
<label for="wsurl"></label><input name="wsurl" id="wsurl" placeholder="wss://" value="wss://ws.postman-echo.com/raw">
<button type="button" id="connect" onclick="connectSocket()">Connect</button>
</div>
<div id="connected">
<label for="message"></label><input name="message" id="message" value="Hello world!">
<button type="button" id="send" onclick="sendMessage()">Send</button>
<button type="button" id="disconnect" onclick="disconnectSocket()">Disconnect</button>
</div>
</form>
<div>
<pre id="consoleLog" style="height: 350px; overflow-y: scroll;">- Connection log will appear here</pre>
</div>
</div>
<script>
hideElement(document.getElementById("connected"));
</script>
</body>
</html>