Convert ReconnectingWebSocket.java to Kotlin (#50614)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/50614

Convert Java to Kotlin

Changelog:
[Internal]

Reviewed By: cortinico

Differential Revision: D72750330

fbshipit-source-id: 03014c47938697a337109a6d213388837eccfb71
This commit is contained in:
Alan Lee
2025-04-10 19:52:23 -07:00
committed by Facebook GitHub Bot
parent 61c539fa6a
commit f62bd98a32
3 changed files with 160 additions and 200 deletions
@@ -3272,15 +3272,15 @@ public class com/facebook/react/packagerconnection/PackagerConnectionSettings {
public final class com/facebook/react/packagerconnection/ReconnectingWebSocket : okhttp3/WebSocketListener {
public fun <init> (Ljava/lang/String;Lcom/facebook/react/packagerconnection/ReconnectingWebSocket$MessageCallback;Lcom/facebook/react/packagerconnection/ReconnectingWebSocket$ConnectionCallback;)V
public fun closeQuietly ()V
public fun connect ()V
public final fun closeQuietly ()V
public final fun connect ()V
public fun onClosed (Lokhttp3/WebSocket;ILjava/lang/String;)V
public fun onFailure (Lokhttp3/WebSocket;Ljava/lang/Throwable;Lokhttp3/Response;)V
public fun onMessage (Lokhttp3/WebSocket;Ljava/lang/String;)V
public fun onMessage (Lokhttp3/WebSocket;Lokio/ByteString;)V
public fun onOpen (Lokhttp3/WebSocket;Lokhttp3/Response;)V
public fun sendMessage (Ljava/lang/String;)V
public fun sendMessage (Lokio/ByteString;)V
public final fun sendMessage (Ljava/lang/String;)V
public final fun sendMessage (Lokio/ByteString;)V
}
public abstract interface class com/facebook/react/packagerconnection/ReconnectingWebSocket$ConnectionCallback {
@@ -1,196 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.packagerconnection;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Nullsafe;
import java.io.IOException;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;
/** A wrapper around WebSocketClient that reconnects automatically */
@Nullsafe(Nullsafe.Mode.LOCAL)
public final class ReconnectingWebSocket extends WebSocketListener {
private static final String TAG = ReconnectingWebSocket.class.getSimpleName();
private static final int RECONNECT_DELAY_MS = 2000;
public interface MessageCallback {
void onMessage(String text);
void onMessage(ByteString bytes);
}
public interface ConnectionCallback {
void onConnected();
void onDisconnected();
}
private final String mUrl;
private final Handler mHandler;
private final OkHttpClient mOkHttpClient;
private boolean mClosed = false;
private boolean mSuppressConnectionErrors;
private @Nullable WebSocket mWebSocket;
private @Nullable MessageCallback mMessageCallback;
private @Nullable ConnectionCallback mConnectionCallback;
public ReconnectingWebSocket(
String url,
@Nullable MessageCallback messageCallback,
@Nullable ConnectionCallback connectionCallback) {
super();
mUrl = url;
mMessageCallback = messageCallback;
mConnectionCallback = connectionCallback;
mHandler = new Handler(Looper.getMainLooper());
mOkHttpClient =
new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
.build();
}
public void connect() {
if (mClosed) {
throw new IllegalStateException("Can't connect closed client");
}
Request request = new Request.Builder().url(mUrl).build();
mOkHttpClient.newWebSocket(request, this);
}
private synchronized void delayedReconnect() {
// check that we haven't been closed in the meantime
if (!mClosed) {
connect();
}
}
private void reconnect() {
if (mClosed) {
throw new IllegalStateException("Can't reconnect closed client");
}
if (!mSuppressConnectionErrors) {
FLog.w(TAG, "Couldn't connect to \"" + mUrl + "\", will silently retry");
mSuppressConnectionErrors = true;
}
mHandler.postDelayed(
new Runnable() {
@Override
public void run() {
delayedReconnect();
}
},
RECONNECT_DELAY_MS);
}
public void closeQuietly() {
mClosed = true;
closeWebSocketQuietly();
mMessageCallback = null;
if (mConnectionCallback != null) {
mConnectionCallback.onDisconnected();
}
}
private void closeWebSocketQuietly() {
if (mWebSocket != null) {
try {
mWebSocket.close(1000, "End of session");
} catch (Exception e) {
// swallow, no need to handle it here
}
mWebSocket = null;
}
}
private void abort(String message, Throwable cause) {
FLog.e(TAG, "Error occurred, shutting down websocket connection: " + message, cause);
closeWebSocketQuietly();
}
@Override
public synchronized void onOpen(WebSocket webSocket, Response response) {
mWebSocket = webSocket;
mSuppressConnectionErrors = false;
if (mConnectionCallback != null) {
mConnectionCallback.onConnected();
}
}
@Override
public synchronized void onFailure(
@Nullable WebSocket webSocket, Throwable t, @Nullable Response response) {
if (mWebSocket != null) {
abort("Websocket exception", t);
}
if (!mClosed) {
if (mConnectionCallback != null) {
mConnectionCallback.onDisconnected();
}
reconnect();
}
}
@Override
public synchronized void onMessage(WebSocket webSocket, String text) {
if (mMessageCallback != null) {
mMessageCallback.onMessage(text);
}
}
@Override
public synchronized void onMessage(WebSocket webSocket, ByteString bytes) {
if (mMessageCallback != null) {
mMessageCallback.onMessage(bytes);
}
}
@Override
public synchronized void onClosed(WebSocket webSocket, int code, String reason) {
mWebSocket = null;
if (!mClosed) {
if (mConnectionCallback != null) {
mConnectionCallback.onDisconnected();
}
reconnect();
}
}
public synchronized void sendMessage(String message) throws IOException {
if (mWebSocket != null) {
mWebSocket.send(message);
} else {
throw new ClosedChannelException();
}
}
public synchronized void sendMessage(ByteString message) throws IOException {
if (mWebSocket != null) {
mWebSocket.send(message);
} else {
throw new ClosedChannelException();
}
}
}
@@ -0,0 +1,156 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.packagerconnection
import android.os.Handler
import android.os.Looper
import com.facebook.common.logging.FLog
import java.io.IOException
import java.nio.channels.ClosedChannelException
import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.WebSocket
import okhttp3.WebSocketListener
import okio.ByteString
/** A wrapper around WebSocketClient that reconnects automatically */
public class ReconnectingWebSocket(
private val url: String,
private var messageCallback: MessageCallback?,
private val connectionCallback: ConnectionCallback?
) : WebSocketListener() {
public interface MessageCallback {
public fun onMessage(text: String)
public fun onMessage(bytes: ByteString)
}
public interface ConnectionCallback {
public fun onConnected()
public fun onDisconnected()
}
private val handler = Handler(Looper.getMainLooper())
private val okHttpClient: OkHttpClient =
OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(0, TimeUnit.MINUTES) // Disable timeouts for read
.build()
private var closed = false
private var suppressConnectionErrors = false
private var webSocket: WebSocket? = null
public fun connect(): Unit {
check(!closed) { "Can't connect closed client" }
val request = Request.Builder().url(url).build()
okHttpClient.newWebSocket(request, this)
}
@Synchronized
private fun delayedReconnect() {
// check that we haven't been closed in the meantime
if (!closed) {
connect()
}
}
private fun reconnect() {
check(!closed) { "Can't reconnect closed client" }
if (!suppressConnectionErrors) {
FLog.w(TAG, "Couldn't connect to \"$url\", will silently retry")
suppressConnectionErrors = true
}
handler.postDelayed({ delayedReconnect() }, RECONNECT_DELAY_MS)
}
public fun closeQuietly(): Unit {
closed = true
closeWebSocketQuietly()
messageCallback = null
connectionCallback?.onDisconnected()
}
private fun closeWebSocketQuietly() {
try {
webSocket?.close(1_000, "End of session")
} catch (e: Exception) {
// swallow, no need to handle it here
}
webSocket = null
}
private fun abort(message: String, cause: Throwable) {
FLog.e(TAG, "Error occurred, shutting down websocket connection: $message", cause)
closeWebSocketQuietly()
}
@Synchronized
override fun onOpen(webSocket: WebSocket, response: Response) {
this.webSocket = webSocket
suppressConnectionErrors = false
connectionCallback?.onConnected()
}
@Synchronized
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
if (this.webSocket != null) {
abort("Websocket exception", t)
}
if (!closed) {
connectionCallback?.onDisconnected()
reconnect()
}
}
@Synchronized
override fun onMessage(webSocket: WebSocket, text: String) {
messageCallback?.onMessage(text)
}
@Synchronized
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
messageCallback?.onMessage(bytes)
}
@Synchronized
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
this.webSocket = null
if (!closed) {
connectionCallback?.onDisconnected()
reconnect()
}
}
@Synchronized
@Throws(IOException::class)
public fun sendMessage(message: String): Unit {
webSocket?.send(message) ?: throw ClosedChannelException()
}
@Synchronized
@Throws(IOException::class)
public fun sendMessage(message: ByteString): Unit {
webSocket?.send(message) ?: throw ClosedChannelException()
}
private companion object {
private val TAG: String = ReconnectingWebSocket::class.java.simpleName
private const val RECONNECT_DELAY_MS = 2_000L
}
}