diff --git a/History.md b/History.md
index 02f66f43..18e4f07b 100644
--- a/History.md
+++ b/History.md
@@ -1,4 +1,56 @@
+2.1.0 / 2022-07-10
+==================
+
+### Bug Fixes
+
+* ensure randomizationFactor is always between 0 and 1 ([0cbf01e](https://github.com/socketio/socket.io-client-java/commit/0cbf01eb2501b3098eacd22594966a719b20c31e))
+* prevent socket from reconnecting after middleware failure ([95ecf22](https://github.com/socketio/socket.io-client-java/commit/95ecf222d25de390d8c0f2ffade37b608cf448eb))
+* increase the readTimeout value of the default OkHttpClient ([fb531fa](https://github.com/socketio/engine.io-client-java/commit/fb531fab30968a4b65a402c81f37e92dd5671f33)) (from `engine.io-client`)
+
+### Features
+
+* emit with timeout ([fca3b95](https://github.com/socketio/socket.io-client-java/commit/fca3b9507d5bc79d3c41ab6e119efccd23669ca6))
+
+This feature allows to send a packet and expect an acknowledgement from the server within the given delay.
+
+Syntax:
+
+```java
+socket.emit("hello", "world", new AckWithTimeout(5000) {
+ @Override
+ public void onTimeout() {
+ // ...
+ }
+
+ @Override
+ public void onSuccess(Object... args) {
+ // ...
+ }
+});
+```
+
+* implement catch-all listeners ([c7d50b8](https://github.com/socketio/socket.io-client-java/commit/c7d50b8ae9787e9ebdff50aa5d36f88433fc50b9))
+
+Syntax:
+
+```java
+socket.onAnyIncoming(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+
+socket.onAnyOutgoing(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+```
+
+
2.0.1 / 2021-04-27
==================
diff --git a/pom.xml b/pom.xml
index 66b4d2f2..e6ff6051 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
io.socket
socket.io-client
- 2.0.1
+ 2.1.0
jar
socket.io-client
Socket.IO Client Library for Java
@@ -30,7 +30,7 @@
https://github.com/socketio/socket.io-client-java
scm:git:https://github.com/socketio/socket.io-client-java.git
scm:git:https://github.com/socketio/socket.io-client-java.git
- socket.io-client-2.0.1
+ socket.io-client-2.1.0
@@ -62,7 +62,7 @@
io.socket
engine.io-client
- 2.0.0
+ 2.1.0
org.json
diff --git a/src/main/java/io/socket/backo/Backoff.java b/src/main/java/io/socket/backo/Backoff.java
index f5199213..81e1dddd 100644
--- a/src/main/java/io/socket/backo/Backoff.java
+++ b/src/main/java/io/socket/backo/Backoff.java
@@ -3,6 +3,9 @@
import java.math.BigDecimal;
import java.math.BigInteger;
+/**
+ * Imported from https://github.com/mokesmokes/backo
+ */
public class Backoff {
private long ms = 100;
@@ -23,7 +26,10 @@ public long duration() {
.multiply(new BigDecimal(ms)).toBigInteger();
ms = (((int) Math.floor(rand * 10)) & 1) == 0 ? ms.subtract(deviation) : ms.add(deviation);
}
- return ms.min(BigInteger.valueOf(this.max)).longValue();
+ return ms
+ .min(BigInteger.valueOf(this.max))
+ .max(BigInteger.valueOf(this.ms))
+ .longValue();
}
public void reset() {
@@ -46,6 +52,10 @@ public Backoff setFactor(int factor) {
}
public Backoff setJitter(double jitter) {
+ boolean isValid = jitter >= 0 && jitter < 1;
+ if (!isValid) {
+ throw new IllegalArgumentException("jitter must be between 0 and 1");
+ }
this.jitter = jitter;
return this;
}
diff --git a/src/main/java/io/socket/client/AckWithTimeout.java b/src/main/java/io/socket/client/AckWithTimeout.java
new file mode 100644
index 00000000..88c43c73
--- /dev/null
+++ b/src/main/java/io/socket/client/AckWithTimeout.java
@@ -0,0 +1,35 @@
+package io.socket.client;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public abstract class AckWithTimeout implements Ack {
+ private final long timeout;
+ private final Timer timer = new Timer();
+
+ /**
+ *
+ * @param timeout delay in milliseconds
+ */
+ public AckWithTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+ @Override
+ public final void call(Object... args) {
+ this.timer.cancel();
+ this.onSuccess(args);
+ }
+
+ public final void schedule(TimerTask task) {
+ this.timer.schedule(task, this.timeout);
+ }
+
+ public final void cancelTimer() {
+ this.timer.cancel();
+ }
+
+ public abstract void onSuccess(Object... args);
+ public abstract void onTimeout();
+
+}
diff --git a/src/main/java/io/socket/client/Socket.java b/src/main/java/io/socket/client/Socket.java
index 05feff39..49bc3784 100644
--- a/src/main/java/io/socket/client/Socket.java
+++ b/src/main/java/io/socket/client/Socket.java
@@ -9,6 +9,7 @@
import org.json.JSONObject;
import java.util.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -63,6 +64,9 @@ public class Socket extends Emitter {
private final Queue> receiveBuffer = new LinkedList<>();
private final Queue> sendBuffer = new LinkedList<>();
+ private ConcurrentLinkedQueue onAnyIncomingListeners = new ConcurrentLinkedQueue<>();
+ private ConcurrentLinkedQueue onAnyOutgoingListeners = new ConcurrentLinkedQueue<>();
+
public Socket(Manager io, String nsp, Manager.Options opts) {
this.io = io;
this.nsp = nsp;
@@ -210,8 +214,32 @@ public void run() {
Packet packet = new Packet<>(Parser.EVENT, jsonArgs);
if (ack != null) {
- logger.fine(String.format("emitting packet with ack id %d", ids));
- Socket.this.acks.put(ids, ack);
+ final int ackId = Socket.this.ids;
+
+ logger.fine(String.format("emitting packet with ack id %d", ackId));
+
+ if (ack instanceof AckWithTimeout) {
+ final AckWithTimeout ackWithTimeout = (AckWithTimeout) ack;
+ ackWithTimeout.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ // remove the ack from the map (to prevent an actual acknowledgement)
+ acks.remove(ackId);
+
+ // remove the packet from the buffer (if applicable)
+ Iterator> iterator = sendBuffer.iterator();
+ while (iterator.hasNext()) {
+ if (iterator.next().id == ackId) {
+ iterator.remove();
+ }
+ }
+
+ ackWithTimeout.onTimeout();
+ }
+ });
+ }
+
+ Socket.this.acks.put(ackId, ack);
packet.id = ids++;
}
@@ -226,6 +254,14 @@ public void run() {
}
private void packet(Packet packet) {
+ if (packet.type == Parser.EVENT) {
+ if (!onAnyOutgoingListeners.isEmpty()) {
+ Object[] argsAsArray = toArray((JSONArray) packet.data);
+ for (Listener listener : onAnyOutgoingListeners) {
+ listener.call(argsAsArray);
+ }
+ }
+ }
packet.nsp = this.nsp;
this.io.packet(packet);
}
@@ -298,6 +334,7 @@ private void onpacket(Packet> packet) {
break;
case Parser.CONNECT_ERROR:
+ this.destroy();
super.emit(EVENT_CONNECT_ERROR, packet.data);
break;
}
@@ -316,6 +353,12 @@ private void onevent(Packet packet) {
if (this.connected) {
if (args.isEmpty()) return;
+ if (!this.onAnyIncomingListeners.isEmpty()) {
+ Object[] argsAsArray = args.toArray();
+ for (Listener listener : this.onAnyIncomingListeners) {
+ listener.call(argsAsArray);
+ }
+ }
String event = args.remove(0).toString();
super.emit(event, args.toArray());
} else {
@@ -405,6 +448,12 @@ private void destroy() {
this.subs = null;
}
+ for (Ack ack : acks.values()) {
+ if (ack instanceof AckWithTimeout) {
+ ((AckWithTimeout) ack).cancelTimer();
+ }
+ }
+
this.io.destroy();
}
@@ -477,5 +526,49 @@ private static Object[] toArray(JSONArray array) {
}
return data;
}
+
+ public Socket onAnyIncoming(Listener fn) {
+ this.onAnyIncomingListeners.add(fn);
+ return this;
+ }
+
+ public Socket offAnyIncoming() {
+ this.onAnyIncomingListeners.clear();
+ return this;
+ }
+
+ public Socket offAnyIncoming(Listener fn) {
+ Iterator it = this.onAnyIncomingListeners.iterator();
+ while (it.hasNext()) {
+ Listener listener = it.next();
+ if (listener == fn) {
+ it.remove();
+ break;
+ }
+ }
+ return this;
+ }
+
+ public Socket onAnyOutgoing(Listener fn) {
+ this.onAnyOutgoingListeners.add(fn);
+ return this;
+ }
+
+ public Socket offAnyOutgoing() {
+ this.onAnyOutgoingListeners.clear();
+ return this;
+ }
+
+ public Socket offAnyOutgoing(Listener fn) {
+ Iterator it = this.onAnyOutgoingListeners.iterator();
+ while (it.hasNext()) {
+ Listener listener = it.next();
+ if (listener == fn) {
+ it.remove();
+ break;
+ }
+ }
+ return this;
+ }
}
diff --git a/src/site/markdown/android.md b/src/site/markdown/android.md
new file mode 100644
index 00000000..a33b2f92
--- /dev/null
+++ b/src/site/markdown/android.md
@@ -0,0 +1,59 @@
+# Android
+
+
+
+## How to keep a Socket.IO client running in the background?
+
+Long story short, you probably shouldn't. The Socket.IO client is not meant to be used in a [background service](https://developer.android.com/guide/components/services?hl=en), as it will keep an open TCP connection to the server and quickly drain the battery of your users.
+
+It is totally usable in the foreground though.
+
+See also: https://developer.android.com/training/connectivity
+
+## How to reach an HTTP server?
+
+Starting with Android 9 (API level 28) you need to explicitly allow cleartext traffic to be able to reach an HTTP server (e.g. a local server at `http://192.168.0.10`):
+
+- either for all domains:
+
+`app/src/main/AndroidManifest.xml`
+
+```xml
+
+
+
+
+
+ ...
+
+
+```
+
+- or for a restricted list of domains:
+
+`app/src/main/AndroidManifest.xml`
+
+```xml
+
+
+
+
+
+ ...
+
+
+```
+
+`app/src/main/res/xml/network_security_config.xml`
+
+```xml
+
+
+
+ localhost
+ 192.168.0.10
+
+
+```
+
+Reference: https://developer.android.com/training/articles/security-config
diff --git a/src/site/markdown/changelog.md b/src/site/markdown/changelog.md
index 98ea025b..d197c228 100644
--- a/src/site/markdown/changelog.md
+++ b/src/site/markdown/changelog.md
@@ -1,4 +1,69 @@
+## [2.1.0](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-2.0.1...socket.io-client-2.1.0) (2022-07-10)
+
+
+### Bug Fixes
+
+* ensure randomizationFactor is always between 0 and 1 ([0cbf01e](https://github.com/socketio/socket.io-client-java/commit/0cbf01eb2501b3098eacd22594966a719b20c31e))
+* prevent socket from reconnecting after middleware failure ([95ecf22](https://github.com/socketio/socket.io-client-java/commit/95ecf222d25de390d8c0f2ffade37b608cf448eb))
+* increase the readTimeout value of the default OkHttpClient ([fb531fa](https://github.com/socketio/engine.io-client-java/commit/fb531fab30968a4b65a402c81f37e92dd5671f33)) (from `engine.io-client`)
+
+### Features
+
+* emit with timeout ([fca3b95](https://github.com/socketio/socket.io-client-java/commit/fca3b9507d5bc79d3c41ab6e119efccd23669ca6))
+
+This feature allows to send a packet and expect an acknowledgement from the server within the given delay.
+
+Syntax:
+
+```java
+socket.emit("hello", "world", new AckWithTimeout(5000) {
+ @Override
+ public void onTimeout() {
+ // ...
+ }
+
+ @Override
+ public void onSuccess(Object... args) {
+ // ...
+ }
+});
+```
+
+* implement catch-all listeners ([c7d50b8](https://github.com/socketio/socket.io-client-java/commit/c7d50b8ae9787e9ebdff50aa5d36f88433fc50b9))
+
+Syntax:
+
+```java
+socket.onAnyIncoming(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+
+socket.onAnyOutgoing(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+```
+
+
+
+## [2.0.1](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-2.0.0...socket.io-client-2.0.1) (2021-04-27)
+
+
+### Bug Fixes
+
+* fix usage with ws:// scheme ([67fd5f3](https://github.com/socketio/socket.io-client-java/commit/67fd5f34a31c63f7884f82ab39386ad343527590))
+* ensure buffered events are sent in order ([4885e7d](https://github.com/socketio/socket.io-client-java/commit/4885e7d59fad78285448694cb5681e8a9ce809ef))
+* ensure the payload format is valid ([e8ffe9d](https://github.com/socketio/socket.io-client-java/commit/e8ffe9d1383736f6a21090ab959a2f4fa5a41284))
+* emit a CONNECT_ERROR event upon connection failure ([d324e7f](https://github.com/socketio/socket.io-client-java/commit/d324e7f396a444ddd556c3d70a85a28eefb1e02b))
+
+
+
## [2.0.0](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-1.0.1...socket.io-client-2.0.0) (2020-12-14)
diff --git a/src/site/markdown/emitting_events.md b/src/site/markdown/emitting_events.md
index fce31092..4526a491 100644
--- a/src/site/markdown/emitting_events.md
+++ b/src/site/markdown/emitting_events.md
@@ -1,6 +1,6 @@
# Emitting events
-See also: https://socket.io/docs/v3/emitting-events/
+See also: https://socket.io/docs/v4/emitting-events/
**Table of content**
@@ -77,6 +77,27 @@ Events are great, but in some cases you may want a more classic request-response
You can add a callback as the last argument of the `emit()`, and this callback will be called once the other side acknowledges the event:
+### From client to server
+
+*Client*
+
+```java
+// Java 7
+socket.emit("update item", 1, new JSONObject(singletonMap("name", "updated")), new Ack() {
+ @Override
+ public void call(Object... args) {
+ JSONObject response = (JSONObject) args[0];
+ System.out.println(response.getString("status")); // "ok"
+ }
+});
+
+// Java 8 and above
+socket.emit("update item", 1, new JSONObject(singletonMap("name", "updated")), (Ack) args -> {
+ JSONObject response = (JSONObject) args[0];
+ System.out.println(response.getString("status")); // "ok"
+});
+```
+
*Server*
```js
@@ -91,15 +112,55 @@ io.on("connection", (socket) => {
});
```
+### From server to client
+
+*Server*
+
+```js
+io.on("connection", (socket) => {
+ socket.emit("hello", "please acknowledge", (response) => {
+ console.log(response); // prints "hi!"
+ });
+});
+```
+
*Client*
```java
-socket.emit("update item", 1, new JSONObject(singletonMap("name", "updated")), new Ack() {
+// Java 7
+socket.on("hello", new Emitter.Listener() {
@Override
public void call(Object... args) {
- JSONObject response = (JSONObject) args[0];
- System.out.println(response.getString("status")); // "ok"
+ System.out.println(args[0]); // "please acknowledge"
+ if (args.length > 1 && args[1] instanceof Ack) {
+ ((Ack) args[1]).call("hi!");
+ }
+ }
+});
+
+// Java 8 and above
+socket.on("hello", args -> {
+ System.out.println(args[0]); // "please acknowledge"
+ if (args.length > 1 && args[1] instanceof Ack) {
+ ((Ack) args[1]).call("hi!");
}
});
```
+## With timeout
+
+Starting with version `2.1.0`, you can now assign a timeout to each emit:
+
+```java
+socket.emit("hello", "world", new AckWithTimeout(5000) {
+ @Override
+ public void onTimeout() {
+ // ...
+ }
+
+ @Override
+ public void onSuccess(Object... args) {
+ // ...
+ }
+});
+```
diff --git a/src/site/markdown/faq.md b/src/site/markdown/faq.md
new file mode 100644
index 00000000..b1defeac
--- /dev/null
+++ b/src/site/markdown/faq.md
@@ -0,0 +1,401 @@
+# Frequently asked questions
+
+
+
+## How to deal with cookies
+
+In order to store the cookies sent by the server and include them in all subsequent requests, you need to create an OkHttpClient with a custom [cookie jar](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-cookie-jar/).
+
+You can either implement your own cookie jar:
+
+```java
+public class MyApp {
+
+ public static void main(String[] argz) throws Exception {
+ IO.Options options = new IO.Options();
+
+ OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .cookieJar(new MyCookieJar())
+ .build();
+
+ options.callFactory = okHttpClient;
+ options.webSocketFactory = okHttpClient;
+
+ Socket socket = IO.socket(URI.create("https://example.com"), options);
+
+ socket.connect();
+ }
+
+ private static class MyCookieJar implements CookieJar {
+ private Set cache = new HashSet<>();
+
+ @Override
+ public void saveFromResponse(HttpUrl url, List cookies) {
+ for (Cookie cookie : cookies) {
+ this.cache.add(new WrappedCookie(cookie));
+ }
+ }
+
+ @Override
+ public List loadForRequest(HttpUrl url) {
+ List cookies = new ArrayList<>();
+ Iterator iterator = this.cache.iterator();
+ while (iterator.hasNext()) {
+ Cookie cookie = iterator.next().cookie;
+ if (isCookieExpired(cookie)) {
+ iterator.remove();
+ } else if (cookie.matches(url)) {
+ cookies.add(cookie);
+ }
+ }
+ return cookies;
+ }
+
+ private static boolean isCookieExpired(Cookie cookie) {
+ return cookie.expiresAt() < System.currentTimeMillis();
+ }
+ }
+
+ private static class WrappedCookie {
+ private final Cookie cookie;
+
+ public WrappedCookie(Cookie cookie) {
+ this.cookie = cookie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof WrappedCookie)) return false;
+ WrappedCookie that = (WrappedCookie) o;
+ return that.cookie.name().equals(this.cookie.name())
+ && that.cookie.domain().equals(this.cookie.domain())
+ && that.cookie.path().equals(this.cookie.path())
+ && that.cookie.secure() == this.cookie.secure()
+ && that.cookie.hostOnly() == this.cookie.hostOnly();
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 17;
+ hash = 31 * hash + cookie.name().hashCode();
+ hash = 31 * hash + cookie.domain().hashCode();
+ hash = 31 * hash + cookie.path().hashCode();
+ hash = 31 * hash + (cookie.secure() ? 0 : 1);
+ hash = 31 * hash + (cookie.hostOnly() ? 0 : 1);
+ return hash;
+ }
+ }
+}
+```
+
+Or use a package like [PersistentCookieJar](https://github.com/franmontiel/PersistentCookieJar):
+
+```java
+public class MyApp {
+
+ public static void main(String[] argz) throws Exception {
+ IO.Options options = new IO.Options();
+
+ ClearableCookieJar cookieJar = new PersistentCookieJar(new SetCookieCache(), new SharedPrefsCookiePersistor(context));
+
+ OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .cookieJar(cookieJar)
+ .build();
+
+ options.callFactory = okHttpClient;
+ options.webSocketFactory = okHttpClient;
+
+ Socket socket = IO.socket(URI.create("https://example.com"), options);
+
+ socket.connect();
+ }
+}
+```
+
+## How to use with AWS Load Balancing
+
+When scaling to multiple Socket.IO servers, you must ensure that all the HTTP requests of a given session reach the same server (explanation [here](https://socket.io/docs/v4/using-multiple-nodes/#why-is-sticky-session-required)).
+
+Sticky sessions can be enabled on AWS Application Load Balancers, which works by sending a cookie (`AWSALB`) to the client.
+
+Please see [above](#how-to-deal-with-cookies) for how to deal with cookies.
+
+Reference: https://docs.aws.amazon.com/elasticloadbalancing/latest/application/sticky-sessions.html
+
+## How to force TLS v1.2 and above
+
+This library relies on the OkHttp library to create HTTP requests and WebSocket connections.
+
+Reference: https://square.github.io/okhttp/
+
+We currently depend on version `3.12.12`, which is the last version that supports Java 7+ and Android 2.3+ (API level 9+). With this version, the OkHttpClient allows `TLSv1` and `TLSv1.1` by default ([MODERN_TLS](https://square.github.io/okhttp/security/tls_configuration_history/#modern_tls-versions_1) configuration).
+
+You can overwrite it by providing your own OkHttp client:
+
+```java
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .connectionSpecs(Arrays.asList(
+ ConnectionSpec.RESTRICTED_TLS
+ ))
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+options.webSocketFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+Note: we will upgrade to OkHttp 4 in the next major version.
+
+## How to create a lot of clients
+
+By default, you won't be able to create more than 5 Socket.IO clients (any additional client will be disconnected with "transport error" or "ping timeout" reason). That is due to the default OkHttp [dispatcher](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-dispatcher/), whose `maxRequestsPerHost` is set to 5 by default.
+
+You can overwrite it by providing your own OkHttp client:
+
+```java
+int MAX_CLIENTS = 100;
+
+Dispatcher dispatcher = new Dispatcher();
+dispatcher.setMaxRequests(MAX_CLIENTS * 2);
+dispatcher.setMaxRequestsPerHost(MAX_CLIENTS * 2);
+
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .dispatcher(dispatcher)
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+options.webSocketFactory = okHttpClient;
+
+for (int i = 0; i < MAX_CLIENTS; i++) {
+ Socket socket = IO.socket(URI.create("https://example.com"), options);
+}
+```
+
+Note: we use `MAX_CLIENTS * 2` because a client in HTTP long-polling mode will have one long-running GET request for receiving data from the server, and will create a POST request for sending data to the server.
+
+## How to properly close a client
+
+Calling `socket.disconnect()` may not be sufficient, because the underlying OkHttp client [creates](https://github.com/square/okhttp/blob/06d38cb795d82d086f13c595a62ce0cbe60904ac/okhttp/src/main/java/okhttp3/Dispatcher.java#L65-L66) a ThreadPoolExecutor that will prevent your Java program from quitting for 60 seconds.
+
+As a workaround, you can manually shut down this ThreadPoolExecutor:
+
+```java
+Dispatcher dispatcher = new Dispatcher();
+
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .dispatcher(dispatcher)
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+options.webSocketFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+
+socket.connect();
+
+// then later
+
+socket.disconnect();
+dispatcher.executorService().shutdown();
+```
+
+## How to map the event arguments to POJO
+
+This library uses the [JSONTokener](https://developer.android.com/reference/org/json/JSONTokener) class from the `org.json` package in order to parse the packets that are sent by the server, which means you will receive [JSONObjects](https://developer.android.com/reference/org/json/JSONObject) in your listeners.
+
+Here's how you can convert these JSONObjects to Plain Old Java Objects (POJO):
+
+- [with Jackson](#With_Jackson)
+- [with Gson](#With_Gson)
+
+### With Jackson
+
+`pom.xml`
+
+```xml
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.13.3
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.13.3
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-json-org
+ 2.13.3
+
+ ...
+
+ ...
+
+```
+
+Maven repository:
+
+- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
+- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind
+- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-json-org
+
+`src/main/java/MyApp.java`
+
+```java
+public class MyApp {
+ private static final ObjectMapper MAPPER = new ObjectMapper().registerModule(new JsonOrgModule());
+
+ public static void main(String[] argz) throws Exception {
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on("my-event", (args) -> {
+ MyObject object = MAPPER.convertValue(args[0], MyObject.class);
+
+ // ...
+ });
+
+ socket.connect();
+ }
+
+ public static class MyObject {
+ public int id;
+ public String label;
+ }
+}
+```
+
+### With Gson
+
+`pom.xml`
+
+```xml
+
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.9
+
+ ...
+
+ ...
+
+```
+
+Maven repository:
+
+- https://mvnrepository.com/artifact/com.google.code.gson/gson
+
+`src/main/java/MyApp.java`
+
+You can either call `toString()` on the `JSONObject`:
+
+```java
+public class MyApp {
+ private static final Gson GSON = new Gson();
+
+ public static void main(String[] argz) throws Exception {
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on("my-event", (args) -> {
+ MyObject object = GSON.fromJson(args[0].toString(), MyObject.class);
+
+ // ...
+ });
+
+ socket.connect();
+ }
+
+ public static class MyObject {
+ public int id;
+ public String label;
+ }
+}
+```
+
+Or manually convert the `JSONObject` to a `JsonObject` (for performance purposes):
+
+```java
+public class MyApp {
+ private static final Gson GSON = new Gson();
+
+ public static void main(String[] argz) throws Exception {
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on("my-event", (args) -> {
+ MyObject object = GSON.fromJson(map(args[0]), MyObject.class);
+
+ // ...
+ });
+
+ socket.connect();
+ }
+
+ public static class MyObject {
+ public int id;
+ public String label;
+ }
+
+ public static JsonObject map(JSONObject source) throws JSONException {
+ JsonObject output = new JsonObject();
+
+ Iterator iterator = source.keys();
+ while (iterator.hasNext()) {
+ String key = iterator.next();
+ Object value = source.get(key);
+
+ if (value instanceof JSONObject) {
+ output.add(key, map((JSONObject) value));
+ } else if (value instanceof JSONArray) {
+ output.add(key, map((JSONArray) value));
+ } else if (value instanceof Number) {
+ output.addProperty(key, (Number) value);
+ } else if (value instanceof String) {
+ output.addProperty(key, (String) value);
+ } else if (value instanceof Boolean) {
+ output.addProperty(key, (Boolean) value);
+ } else if (value instanceof Character) {
+ output.addProperty(key, (Character) value);
+ }
+ }
+
+ return output;
+ }
+
+ public static JsonArray map(JSONArray source) throws JSONException {
+ JsonArray output = new JsonArray();
+
+ for (int i = 0; i < source.length(); i++) {
+ Object value = source.get(i);
+
+ if (value instanceof JSONObject) {
+ output.add(map((JSONObject) value));
+ } else if (value instanceof JSONArray) {
+ output.add(map((JSONArray) value));
+ } else if (value instanceof Number) {
+ output.add((Number) value);
+ } else if (value instanceof String) {
+ output.add((String) value);
+ } else if (value instanceof Boolean) {
+ output.add((Boolean) value);
+ } else if (value instanceof Character) {
+ output.add((Character) value);
+ }
+ }
+
+ return output;
+ }
+}
+```
\ No newline at end of file
diff --git a/src/site/markdown/initialization.md b/src/site/markdown/initialization.md
index efef638f..0311b39e 100644
--- a/src/site/markdown/initialization.md
+++ b/src/site/markdown/initialization.md
@@ -25,7 +25,7 @@ Socket socket = IO.socket("wss://example.com"); // OK, similar to the example ab
Socket socket = IO.socket("192.168.0.1:1234"); // NOT OK, missing the scheme part
```
-The path represents the [Namespace](https://socket.io/docs/v3/namespaces/), and not the actual path (see [below](#path)) of the HTTP requests:
+The path represents the [Namespace](https://socket.io/docs/v4/namespaces/), and not the actual path (see [below](#path)) of the HTTP requests:
```java
Socket socket = IO.socket(URI.create("https://example.com")); // the main namespace
@@ -76,7 +76,7 @@ Whether to create a new Manager instance.
A Manager instance is in charge of the low-level connection to the server (established with HTTP long-polling or WebSocket). It handles the reconnection logic.
-A Socket instance is the interface which is used to sends events to — and receive events from — the server. It belongs to a given [namespace](https://socket.io/docs/v3/namespaces).
+A Socket instance is the interface which is used to sends events to — and receive events from — the server. It belongs to a given [namespace](https://socket.io/docs/v4/namespaces).
A single Manager can be attached to several Socket instances.
@@ -131,7 +131,7 @@ IO.Options options = IO.Options.builder()
Socket socket = IO.socket(URI.create("https://example.com"), options);
```
-Note: in that case, sticky sessions are not required on the server side (more information [here](https://socket.io/docs/v3/using-multiple-nodes/)).
+Note: in that case, sticky sessions are not required on the server side (more information [here](https://socket.io/docs/v4/using-multiple-nodes/)).
#### `upgrade`
@@ -153,7 +153,17 @@ It is the name of the path that is captured on the server side.
The server and the client values must match:
-*Server*
+*Client*
+
+```java
+IO.Options options = IO.Options.builder()
+ .setPath("/my-custom-path/")
+ .build();
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+*JavaScript Server*
```js
import { Server } from "socket.io";
@@ -167,17 +177,7 @@ io.on("connection", (socket) => {
});
```
-*Client*
-
-```java
-IO.Options options = IO.Options.builder()
- .setPath("/my-custom-path/")
- .build();
-
-Socket socket = IO.socket(URI.create("https://example.com"), options);
-```
-
-Please note that this is different from the path in the URI, which represents the [Namespace](https://socket.io/docs/v3/namespaces/).
+Please note that this is different from the path in the URI, which represents the [Namespace](https://socket.io/docs/v4/namespaces/).
Example:
@@ -200,14 +200,6 @@ Additional query parameters (then found in `socket.handshake.query` object on th
Example:
-*Server*
-
-```js
-io.on("connection", (socket) => {
- console.log(socket.handshake.query); // prints { x: '42', EIO: '4', transport: 'polling' }
-});
-```
-
*Client*
```java
@@ -218,6 +210,14 @@ IO.Options options = IO.Options.builder()
Socket socket = IO.socket(URI.create("https://example.com"), options);
```
+*JavaScript Server*
+
+```js
+io.on("connection", (socket) => {
+ console.log(socket.handshake.query); // prints { x: '42', EIO: '4', transport: 'polling' }
+});
+```
+
Note: The `socket.handshake.query` object contains the query parameters that were sent during the Socket.IO handshake, it won't be updated for the duration of the current session, which means changing the `query` on the client-side will only be effective when the current session is closed and a new one is created:
```java
@@ -237,14 +237,6 @@ Additional headers (then found in `socket.handshake.headers` object on the serve
Example:
-*Server*
-
-```js
-io.on("connection", (socket) => {
- console.log(socket.handshake.headers); // prints { accept: '*/*', authorization: 'bearer 1234', connection: 'Keep-Alive', 'accept-encoding': 'gzip', 'user-agent': 'okhttp/3.12.12' }
-});
-```
-
*Client*
```java
@@ -255,6 +247,14 @@ IO.Options options = IO.Options.builder()
Socket socket = IO.socket(URI.create("https://example.com"), options);
```
+*JavaScript Server*
+
+```js
+io.on("connection", (socket) => {
+ console.log(socket.handshake.headers); // prints { accept: '*/*', authorization: 'bearer 1234', connection: 'Keep-Alive', 'accept-encoding': 'gzip', 'user-agent': 'okhttp/3.12.12' }
+});
+```
+
Note: Similar to the `query` option above, the `socket.handshake.headers` object contains the headers that were sent during the Socket.IO handshake, it won't be updated for the duration of the current session, which means changing the `extraHeaders` on the client-side will only be effective when the current session is closed and a new one is created:
```java
@@ -266,6 +266,84 @@ socket.io().on(Manager.EVENT_RECONNECT_ATTEMPT, new Emitter.Listener() {
});
```
+#### `callFactory`
+
+The [OkHttpClient instance](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/) to use for HTTP long-polling requests.
+
+```java
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+#### `webSocketFactory`
+
+The [OkHttpClient instance](https://square.github.io/okhttp/4.x/okhttp/okhttp3/-ok-http-client/) to use for WebSocket connections.
+
+```java
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .minWebSocketMessageToCompress(2048)
+ .build();
+
+IO.Options options = new IO.Options();
+options.webSocketFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+### Manager options
+
+These settings will be shared by all Socket instances attached to the same Manager.
+
+#### `reconnection`
+
+Default value: `true`
+
+Whether reconnection is enabled or not. If set to `false`, you need to manually reconnect.
+
+#### `reconnectionAttempts`
+
+Default value: `Integer.MAX_VALUE`
+
+The number of reconnection attempts before giving up.
+
+#### `reconnectionDelay`
+
+Default value: `1_000`
+
+The initial delay before reconnection in milliseconds (affected by the [randomizationFactor](#randomizationfactor) value).
+
+#### `reconnectionDelayMax`
+
+Default value: `5_000`
+
+The maximum delay between two reconnection attempts. Each attempt increases the reconnection delay by 2x.
+
+#### `randomizationFactor`
+
+Default value: `0.5`
+
+The randomization factor used when reconnecting (so that the clients do not reconnect at the exact same time after a server crash, for example).
+
+Example with the default values:
+
+- 1st reconnection attempt happens between 500 and 1500 ms (`1000 * 2^0 * ()`)
+- 2nd reconnection attempt happens between 1000 and 3000 ms (`1000 * 2^1 * ()`)
+- 3rd reconnection attempt happens between 2000 and 5000 ms (`1000 * 2^2 * ()`)
+- next reconnection attempts happen after 5000 ms
+
+#### `timeout`
+
+Default value: `20_000`
+
+The timeout in milliseconds for each connection attempt.
+
+
### Socket options
These settings are specific to the given Socket instance.
@@ -274,18 +352,10 @@ These settings are specific to the given Socket instance.
Default value: -
-Credentials that are sent when accessing a namespace (see also [here](https://socket.io/docs/v3/middlewares/#Sending-credentials)).
+Credentials that are sent when accessing a namespace (see also [here](https://socket.io/docs/v4/middlewares/#sending-credentials)).
Example:
-*Server*
-
-```js
-io.on("connection", (socket) => {
- console.log(socket.handshake.auth); // prints { token: 'abcd' }
-});
-```
-
*Client*
```java
@@ -296,6 +366,14 @@ IO.Options options = IO.Options.builder()
Socket socket = IO.socket(URI.create("https://example.com"), options);
```
+*JavaScript Server*
+
+```js
+io.on("connection", (socket) => {
+ console.log(socket.handshake.auth); // prints { token: 'abcd' }
+});
+```
+
You can update the `auth` map when the access to the Namespace is denied:
```java
@@ -314,3 +392,135 @@ Or manually force the Socket instance to reconnect:
options.auth.put("token", "efgh");
socket.disconnect().connect();
```
+
+## SSL connections
+
+### With a keystore
+
+```java
+HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return hostname.equals("example.com");
+ }
+};
+
+KeyStore ks = KeyStore.getInstance("JKS");
+File file = new File("path/to/the/keystore.jks");
+ks.load(new FileInputStream(file), "password".toCharArray());
+
+KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+kmf.init(ks, "password".toCharArray());
+
+TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
+tmf.init(ks);
+
+SSLContext sslContext = SSLContext.getInstance("TLS");
+sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .hostnameVerifier(hostnameVerifier)
+ .sslSocketFactory(sslContext.getSocketFactory(), (X509TrustManager) tmf.getTrustManagers()[0])
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+options.webSocketFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+### Trust all certificates
+
+Please use with caution, as this defeats the whole purpose of using secure connections.
+
+This is equivalent to `rejectUnauthorized: false` for the JavaScript client.
+
+```java
+HostnameVerifier hostnameVerifier = new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession sslSession) {
+ return true;
+ }
+};
+
+X509TrustManager trustManager = new X509TrustManager() {
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[] {};
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
+ // not implemented
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
+ // not implemented
+ }
+};
+
+SSLContext sslContext = SSLContext.getInstance("TLS");
+sslContext.init(null, new TrustManager[] { trustManager }, null);
+
+OkHttpClient okHttpClient = new OkHttpClient.Builder()
+ .hostnameVerifier(hostnameVerifier)
+ .sslSocketFactory(sslContext.getSocketFactory(), trustManager)
+ .readTimeout(1, TimeUnit.MINUTES) // important for HTTP long-polling
+ .build();
+
+IO.Options options = new IO.Options();
+options.callFactory = okHttpClient;
+options.webSocketFactory = okHttpClient;
+
+Socket socket = IO.socket(URI.create("https://example.com"), options);
+```
+
+## Multiplexing
+
+The Java client does support multiplexing: this allows to split the logic of your application into distinct modules, while using one single WebSocket connection to the server.
+
+Reference: https://socket.io/docs/v4/namespaces/
+
+```java
+Socket socket = IO.socket(URI.create("https://example.com")); // the main namespace
+Socket productSocket = IO.socket(URI.create("https://example.com/product")); // the "product" namespace
+Socket orderSocket = IO.socket(URI.create("https://example.com/order")); // the "order" namespace
+
+// all 3 sockets share the same Manager
+System.out.println(socket.io() == productSocket.io()); // true
+System.out.println(socket.io() == orderSocket.io()); // true
+```
+
+Please note that multiplexing will be disabled in the following cases:
+
+- multiple creation for the same namespace
+
+```java
+Socket socket = IO.socket(URI.create("https://example.com"));
+Socket socket2 = IO.socket(URI.create("https://example.com"));
+
+System.out.println(socket.io() == socket2.io()); // false
+```
+
+- different domains
+
+```java
+Socket socket = IO.socket(URI.create("https://first.example.com"));
+Socket socket2 = IO.socket(URI.create("https://second.example.com"));
+
+System.out.println(socket.io() == socket2.io()); // false
+```
+
+- usage of the [forceNew](#forceNew) option
+
+```java
+IO.Options options = IO.Options.builder()
+ .setForceNew(true)
+ .build();
+
+Socket socket = IO.socket(URI.create("https://example.com"));
+Socket socket2 = IO.socket(URI.create("https://example.com/admin"), options);
+
+System.out.println(socket.io() == socket2.io()); // false
+```
diff --git a/src/site/markdown/installation.md b/src/site/markdown/installation.md
index 1eb05d80..a14593a8 100644
--- a/src/site/markdown/installation.md
+++ b/src/site/markdown/installation.md
@@ -3,7 +3,7 @@
| Client version | Socket.IO server |
| -------------- | ---------------- |
| 0.9.x | 1.x |
-| 1.x | 2.x (or 3.1.x / 4.x with [`allowEIO3: true`](https://socket.io/docs/v4/server-initialization/#allowEIO3)) |
+| 1.x | 2.x (or 3.1.x / 4.x with [`allowEIO3: true`](https://socket.io/docs/v4/server-options/#alloweio3)) |
| 2.x | 3.x / 4.x |
## Installation
@@ -17,7 +17,7 @@ Add the following dependency to your `pom.xml`.
io.socket
socket.io-client
- 2.0.1
+ 2.1.0
```
@@ -26,8 +26,18 @@ Add the following dependency to your `pom.xml`.
Add it as a gradle dependency for Android Studio, in `build.gradle`:
```groovy
-compile ('io.socket:socket.io-client:2.0.1') {
+implementation ('io.socket:socket.io-client:2.1.0') {
// excluding org.json which is provided by Android
exclude group: 'org.json', module: 'json'
}
```
+
+## Dependency tree
+
+| `socket.io-client` | `engine.io-client` | `okhttp` |
+|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------|
+| `2.1.0` ([diff](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-2.0.1...socket.io-client-2.1.0)) | `2.1.0` ([diff](https://github.com/socketio/engine.io-client-java/compare/engine.io-client-2.0.0...engine.io-client-2.1.0)) | `3.12.12` |
+| `2.0.1` ([diff](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-2.0.0...socket.io-client-2.0.1)) | `2.0.0` | `3.12.12` |
+| `2.0.0` ([diff](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-1.0.1...socket.io-client-2.0.0)) | `2.0.0` ([diff](https://github.com/socketio/engine.io-client-java/compare/engine.io-client-1.0.1...engine.io-client-2.0.0)) | `3.12.12` |
+| `1.0.1` ([diff](https://github.com/socketio/socket.io-client-java/compare/socket.io-client-1.0.0...socket.io-client-1.0.1)) | `1.0.1` ([diff](https://github.com/socketio/engine.io-client-java/compare/engine.io-client-1.0.0...engine.io-client-1.0.1)) | `3.12.12` ([changelog](https://square.github.io/okhttp/changelogs/changelog_3x/#version-31212)) |
+| `1.0.0` | `1.0.0` | `3.8.1` |
diff --git a/src/site/markdown/listening_to_events.md b/src/site/markdown/listening_to_events.md
index 6caf0a61..843fb182 100644
--- a/src/site/markdown/listening_to_events.md
+++ b/src/site/markdown/listening_to_events.md
@@ -1,6 +1,6 @@
# Listening to events
-See also: https://socket.io/docs/v3/listening-to-events/
+See also: https://socket.io/docs/v4/listening-to-events/
**Table of content**
@@ -69,3 +69,27 @@ Removes all listeners (for any event).
```java
socket.off();
```
+
+## Catch-all listeners
+
+### For incoming packets
+
+```java
+socket.onAnyIncoming(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+```
+
+### For outgoing packets
+
+```java
+socket.onAnyOutgoing(new Emitter.Listener() {
+ @Override
+ public void call(Object... args) {
+ // ...
+ }
+});
+```
diff --git a/src/site/markdown/logging.md b/src/site/markdown/logging.md
new file mode 100644
index 00000000..0dc9adbd
--- /dev/null
+++ b/src/site/markdown/logging.md
@@ -0,0 +1,213 @@
+# Logging
+
+This library uses JUL (`java.util.logging`) for its debug logs.
+
+Here's how you can display those logs, depending on your logging library:
+
+
+
+## Usage with JUL
+
+`src/main/resources/logging.properties`
+
+```properties
+handlers = java.util.logging.ConsoleHandler
+
+java.util.logging.ConsoleHandler.level = ALL
+
+.level = INFO
+io.socket.level = FINE
+```
+
+`src/main/java/MyApp.java`
+
+```java
+public class MyApp {
+ private static final Logger logger = Logger.getLogger("MyApp");
+
+ public static void main(String[] argz) throws Exception {
+ InputStream stream = MyApp.class.getResourceAsStream("logging.properties");
+ LogManager.getLogManager().readConfiguration(stream);
+
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on(Socket.EVENT_CONNECT, args -> logger.info("connected!"));
+
+ socket.connect();
+ }
+}
+```
+
+Reference: https://docs.oracle.com/en/java/javase/17/core/java-logging-overview.html
+
+## Usage with Log4j2
+
+`pom.xml`
+
+```xml
+
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.18.0
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.18.0
+
+
+ org.apache.logging.log4j
+ log4j-jul
+ 2.18.0
+
+ ...
+
+ ...
+
+```
+
+Maven repository:
+
+- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api
+- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
+- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-jul
+
+`src/main/resources/log4j2.xml`
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+`src/main/java/MyApp.java`
+
+Either by setting the `java.util.logging.manager` environment variable:
+
+```java
+public class MyApp {
+ private static final Logger logger;
+
+ static {
+ System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
+ logger = LogManager.getLogger(MyApp.class);
+ }
+
+ public static void main(String[] argz) throws Exception {
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on(Socket.EVENT_CONNECT, args -> logger.info("connected!"));
+
+ socket.connect();
+ }
+}
+```
+
+Or with the `Log4jBridgeHandler` class:
+
+```java
+public class MyApp {
+ private static final Logger logger = LogManager.getLogger(MyApp.class);
+
+ public static void main(String[] argz) throws Exception {
+ Log4jBridgeHandler.install(true, "", true);
+
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on(Socket.EVENT_CONNECT, args -> logger.info("connected!"));
+
+ socket.connect();
+ }
+}
+```
+
+Reference: https://logging.apache.org/log4j/2.x/log4j-jul/index.html
+
+## Usage with Slf4j + logback
+
+`pom.xml`
+
+```xml
+
+
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.11
+
+
+ org.slf4j
+ jul-to-slf4j
+ 1.7.36
+
+ ...
+
+ ...
+
+```
+
+Maven repository:
+
+- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
+- https://mvnrepository.com/artifact/org.slf4j/jul-to-slf4j
+
+`src/main/resources/logback.xml`
+
+```xml
+
+
+ true
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+```
+
+`src/main/java/MyApp.java`
+
+```java
+public class MyApp {
+ private static final Logger logger = LoggerFactory.getLogger(MyApp.class);
+
+ static {
+ SLF4JBridgeHandler.install();
+ }
+
+ public static void main(String[] argz) throws Exception {
+ Socket socket = IO.socket(URI.create("https://example.com"));
+
+ socket.on(Socket.EVENT_CONNECT, args -> logger.info("connected!"));
+
+ socket.connect();
+ }
+}
+```
+
+Reference: https://www.slf4j.org/manual.html
diff --git a/src/site/markdown/migrating_from_1_x.md b/src/site/markdown/migrating_from_1_x.md
index f13bebf3..37c56550 100644
--- a/src/site/markdown/migrating_from_1_x.md
+++ b/src/site/markdown/migrating_from_1_x.md
@@ -7,12 +7,12 @@ Here is the compatibility table:
| Java client version | Socket.IO server |
| -------------- | ---------------- |
| 0.9.x | 1.x |
-| 1.x | 2.x (or 3.1.x / 4.x with [`allowEIO3: true`](https://socket.io/docs/v4/server-initialization/#allowEIO3)) |
+| 1.x | 2.x (or 3.1.x / 4.x with [`allowEIO3: true`](https://socket.io/docs/v4/server-options/#alloweio3)) |
| 2.x | 3.x / 4.x |
**Important note:** due to the backward incompatible changes to the Socket.IO protocol, a 2.x Java client will not be able to reach a 2.x server, and vice-versa
-Since the Java client matches the Javascript client quite closely, most of the changes listed in the migration guide [here](https://socket.io/docs/v3/migrating-from-2-x-to-3-0) also apply to the Java client:
+Since the Java client matches the Javascript client quite closely, most of the changes listed in the migration guide [here](https://socket.io/docs/v4/migrating-from-2-x-to-3-0) also apply to the Java client:
- [A middleware error will now emit an Error object](#A_middleware_error_will_now_emit_an_Error_object)
- [The Socket `query` option is renamed to `auth`](#The_Socket_query_option_is_renamed_to_auth)
diff --git a/src/site/markdown/socket_instance.md b/src/site/markdown/socket_instance.md
index 26457164..5a40be45 100644
--- a/src/site/markdown/socket_instance.md
+++ b/src/site/markdown/socket_instance.md
@@ -113,7 +113,7 @@ socket.on("data", new Emitter.Listener() {
### `Socket.EVENT_CONNECT_ERROR`
-This event is fired when the server does not accept the connection (in a [middleware function](https://socket.io/docs/v3/middlewares/#Sending-credentials)).
+This event is fired when the server does not accept the connection (in a [middleware function](https://socket.io/docs/v4/middlewares/#sending-credentials)).
You need to manually reconnect. You might need to update the credentials:
@@ -144,7 +144,7 @@ Here is the list of possible reasons:
Reason | Description
------ | -----------
-`io server disconnect` | The server has forcefully disconnected the socket with [socket.disconnect()](https://socket.io/docs/v3/server-api/#socket-disconnect-close)
+`io server disconnect` | The server has forcefully disconnected the socket with [socket.disconnect()](https://socket.io/docs/v4/server-api/#socketdisconnectclose)
`io client disconnect` | The socket was manually disconnected using `socket.disconnect()`
`ping timeout` | The server did not respond in the `pingTimeout` range
`transport close` | The connection was closed (example: the user has lost connection, or the network was changed from WiFi to 4G)
diff --git a/src/site/site.xml b/src/site/site.xml
index 3f81c927..5d1d34ac 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -30,6 +30,9 @@
+
+
+