Skip to content

Commit 2f98b4e

Browse files
feat: enhance WebSocket plugin with binary message support and improved API
- Updated `WebSocketPlugin.connect` to accept an optional `binaryType` parameter. - Modified `WebSocketInstance.send` method to handle both string and binary messages. - Enhanced JavaScript interface to support binary message sending and receiving. - Improved event handling for binary messages in both Java and JavaScript layers. - Added logging for better debugging of message sending and receiving.
1 parent 7b65cb4 commit 2f98b4e

File tree

4 files changed

+221
-115
lines changed

4 files changed

+221
-115
lines changed

src/plugins/websocket/README.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,32 +45,38 @@ WebSocketPlugin.connect("wss://example.com/socket", ["protocol1", "protocol2"],
4545

4646
### Methods
4747

48-
* `WebSocketPlugin.connect(url, protocols, headers)`
49-
48+
* `WebSocketPlugin.connect(url, protocols, headers, binaryType)`
5049
* Connects to a WebSocket server.
5150
* `url`: The WebSocket server URL.
5251
* `protocols`: (Optional) An array of subprotocol strings.
53-
* `headers` (object, optional): Custom headers as key-value pairs.
52+
* `headers`: (Optional) Custom headers as key-value pairs.
53+
* `binaryType`: (Optional) Initial binary type setting.
5454
* **Returns:** A Promise that resolves to a `WebSocketInstance`.
55+
5556
* `WebSocketPlugin.listClients()`
5657
* Lists all stored webSocket instance IDs.
57-
* **Returns:** `Promise`that resolves to an array of `instanceId` strings.
58-
59-
* `WebSocketPlugin.send(instanceId, message)`
60-
* same as `WebSocketInstance.send(message)` but needs `instanceId`.
61-
* **Returns:** `Promise` that resolves.
58+
* **Returns:** `Promise` that resolves to an array of `instanceId` strings.
59+
60+
* `WebSocketPlugin.send(instanceId, message, binary)`
61+
* Sends a message to the server using an instance ID.
62+
* `instanceId`: The ID of the WebSocket instance.
63+
* `message`: The message to send (string or ArrayBuffer/ArrayBufferView).
64+
* `binary`: (Optional) Whether to send the message as binary, accepts `boolean`
65+
* **Returns:** `Promise` that resolves when the message is sent.
6266

6367
* `WebSocketPlugin.close(instanceId, code, reason)`
6468
* same as `WebSocketInstance.close(code, reason)` but needs `instanceId`.
6569
* **Returns:** `Promise` that resolves.
6670

67-
* `WebSocketInstance.send(message)`
71+
### WebSocketInstance Methods
6872

73+
* `WebSocketInstance.send(message, binary)`
6974
* Sends a message to the server.
75+
* `message`: The message to send (string or ArrayBuffer/ArrayBufferView).
76+
* `binary`: (Optional) Whether to send the message as binary. accepts `boolean`
7077
* Throws an error if the connection is not open.
7178

7279
* `WebSocketInstance.close(code, reason)`
73-
7480
* Closes the connection.
7581
* `code`: (Optional) If unspecified, a close code for the connection is automatically set: to 1000 for a normal closure, or otherwise to [another standard value in the range 1001-1015](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1) that indicates the actual reason the connection was closed.
7682
* `reason`: A string providing a [custom WebSocket connection close reason](https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.6) (a concise human-readable prose explanation for the closure). The value must be no longer than 123 bytes (encoded in UTF-8).
@@ -84,12 +90,40 @@ WebSocketPlugin.connect("wss://example.com/socket", ["protocol1", "protocol2"],
8490
* `onclose`: Event listener for connection close.
8591
* `onerror`: Event listener for errors.
8692
* `readyState`: (number) The state of the connection.
87-
8893
* 0 (`CONNECTING`): Socket created, not yet open.
8994
* 1 (`OPEN`): Connection is open and ready.
9095
* 2 (`CLOSING`): Connection is closing.
9196
* 3 (`CLOSED`): Connection is closed or couldn't be opened.
92-
* `extensions`: (string) Extensions negotiated by the server (usually empty or a list).
97+
* `extensions`: (string) Extensions negotiated by the server.
98+
* `binaryType`: (string) Type of binary data to use ('arraybuffer' or '' (binary payload returned as strings.)).
99+
* `url`: (string) The WebSocket server URL.
100+
* `instanceId`: (string) Unique identifier for this WebSocket instance.
101+
102+
### Event Handling
103+
104+
`WebSocketInstance` extends `EventTarget`, providing standard event handling methods:
105+
106+
* `addEventListener(type, listener)`: Registers an event listener.
107+
* `removeEventListener(type, listener)`: Removes an event listener.
108+
* `dispatchEvent(event)`: Dispatches an event to the object.
109+
110+
Example of using event listeners:
111+
```javascript
112+
const ws = await WebSocketPlugin.connect("wss://example.com/socket");
113+
114+
// Using on* properties
115+
ws.onmessage = (event) => console.log("Message:", event.data);
116+
117+
// Using addEventListener
118+
ws.addEventListener('message', (event) => console.log("Message:", event.data));
119+
```
120+
121+
### Constants
122+
123+
* `WebSocketInstance.CONNECTING`: 0
124+
* `WebSocketInstance.OPEN`: 1
125+
* `WebSocketInstance.CLOSING`: 2
126+
* `WebSocketInstance.CLOSED`: 3
93127

94128
---
95129

src/plugins/websocket/src/android/WebSocketInstance.java

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.foxdebug.websocket;
22

3+
import android.util.Base64;
34
import android.util.Log;
45

56
import androidx.annotation.NonNull;
@@ -17,6 +18,7 @@
1718
import okio.ByteString;
1819

1920
public class WebSocketInstance extends WebSocketListener {
21+
private static final String TAG = "WebSocketInstance";
2022
private static final int DEFAULT_CLOSE_CODE = 1000;
2123
private static final String DEFAULT_CLOSE_REASON = "Normal closure";
2224

@@ -73,21 +75,34 @@ public void setCallback(CallbackContext callbackContext) {
7375
callbackContext.sendPluginResult(result);
7476
}
7577

76-
public void send(String message) {
78+
public void send(String message, boolean isBinary) {
7779
if (this.webSocket != null) {
80+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " received send(..., isBinary=" + isBinary + ") action call, sending message=" + message);
81+
if(isBinary) {
82+
this.sendBinary(message);
83+
return;
84+
}
7885
this.webSocket.send(message);
79-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received send() action call, sending message=" + message);
8086
} else {
81-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received send() action call, ignoring... as webSocket is null (not present)");
87+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " received send(..., isBinary=" + isBinary + ") ignoring... as webSocket is null (not present/connected)");
8288
}
8389
}
8490

91+
/**
92+
* Sends bytes as the data of a binary (type 0x2) message.
93+
* @param base64Data Binary Data received from JS bridge encoded as base64 String
94+
*/
95+
private void sendBinary(String base64Data) {
96+
byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
97+
this.webSocket.send(ByteString.of(data));
98+
}
99+
85100
public String close(int code, String reason) {
86101
if (this.webSocket != null) {
87102
this.readyState = 2; // CLOSING
88103
try {
89104
boolean result = this.webSocket.close(code, reason);
90-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received close() action call");
105+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " received close() action call, code=" + code + " reason=" + reason + " close method result: " + result);
91106

92107
// if a graceful shutdown was already underway...
93108
// or if the web socket is already closed or canceled. do nothing.
@@ -100,14 +115,14 @@ public String close(int code, String reason) {
100115

101116
return null;
102117
} else {
103-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received close() action call, ignoring... as webSocket is null (not present)");
118+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " received close() action call, ignoring... as webSocket is null (not present)");
104119
// TODO: finding a better way of telling it wasn't successful.
105120
return "";
106121
}
107122
}
108123

109124
public String close() {
110-
Log.d("WebSocketInstance", "WebSocket instanceId=" + this.instanceId + " close() called with no arguments. Using defaults.");
125+
Log.d(TAG, "WebSocket instanceId=" + this.instanceId + " close() called with no arguments. Using defaults.");
111126
// Calls the more specific version with default values
112127
return close(DEFAULT_CLOSE_CODE, DEFAULT_CLOSE_REASON);
113128
}
@@ -118,76 +133,81 @@ public void onOpen(@NonNull WebSocket webSocket, Response response) {
118133
this.readyState = 1; // OPEN
119134
this.extensions = response.headers("Sec-WebSocket-Extensions").toString();
120135
this.protocol = response.header("Sec-WebSocket-Protocol");
121-
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Opened" + "received extensions=" + this.extensions);
122-
sendEvent("open", null, false);
136+
Log.i(TAG, "websocket instanceId=" + this.instanceId + " Opened" + "received extensions=" + this.extensions);
137+
sendEvent("open", null, false, false);
123138
}
124139

125140
@Override
126141
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
127-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Received message: " + text);
128-
sendEvent("message", text, false);
142+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " Received message: " + text);
143+
sendEvent("message", text, false, false);
129144
}
130145

131146
// This is called when the Websocket server sends a binary(type 0x2) message.
132147
@Override
133148
public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
134-
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Received message(bytes): " + bytes.toString());
149+
Log.d(TAG, "websocket instanceId=" + this.instanceId + " Received message(bytes/binary payload): " + bytes.toString());
135150

136151
try {
137152
if ("arraybuffer".equals(this.binaryType)) {
138153
String base64 = bytes.base64();
139-
sendEvent("message", base64, true);
154+
sendEvent("message", base64, true, false);
140155
} else {
141-
sendEvent("message", bytes.utf8(), true);
156+
sendEvent("message", bytes.utf8(), true, true);
142157
}
143158
} catch (Exception e) {
144-
Log.e("WebSocketInstance", "Error sending message", e);
159+
Log.e(TAG, "Error sending message", e);
145160
}
146161

147162
}
148163

149164
@Override
150165
public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
151166
this.readyState = 2; // CLOSING
152-
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " is Closing code: " + code + " reason: " + reason);
167+
Log.i(TAG, "websocket instanceId=" + this.instanceId + " is Closing code: " + code + " reason: " + reason);
153168
}
154169

155170
@Override
156171
public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
157172
this.readyState = 3; // CLOSED
158-
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Closed code: " + code + " reason: " + reason);
173+
Log.i(TAG, "websocket instanceId=" + this.instanceId + " Closed code: " + code + " reason: " + reason);
159174
JSONObject closedEvent = new JSONObject();
160175
try {
161176
closedEvent.put("code", code);
162177
closedEvent.put("reason", reason);
163178
} catch (JSONException e) {
164-
Log.e("WebSocketInstance", "Error creating close event", e);
179+
Log.e(TAG, "Error creating close event", e);
165180
}
166-
sendEvent("close", closedEvent.toString(), false);
181+
sendEvent("close", closedEvent.toString(), false, false);
167182
}
168183

169184
@Override
170185
public void onFailure(@NonNull WebSocket webSocket, Throwable t, Response response) {
171186
this.readyState = 3; // CLOSED
172-
sendEvent("error", t.getMessage(), false);
173-
Log.e("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Error: " + t.getMessage());
187+
sendEvent("error", t.getMessage(), false, false);
188+
Log.e(TAG, "websocket instanceId=" + this.instanceId + " Error: " + t.getMessage());
189+
}
190+
191+
public void setBinaryType(String binaryType) {
192+
this.binaryType = binaryType;
174193
}
175194

176-
private void sendEvent(String type, String data, boolean isBinary) {
195+
private void sendEvent(String type, String data, boolean isBinary, boolean parseAsText) {
177196
if (callbackContext != null) {
178197
try {
179198
JSONObject event = new JSONObject();
180199
event.put("type", type);
181200
event.put("extensions", this.extensions);
182201
event.put("readyState", this.readyState);
183-
event.put("isBinary", isBinary ? true : false);
202+
event.put("isBinary", isBinary);
203+
event.put("parseAsText", parseAsText);
184204
if (data != null) event.put("data", data);
185-
Log.d("WebSocketInstance", "sending event: " + type + " eventObj " + event.toString());
205+
Log.d(TAG, "sending event: " + type + " eventObj " + event.toString());
186206
PluginResult result = new PluginResult(PluginResult.Status.OK, event);
187207
result.setKeepCallback(true);
188208
callbackContext.sendPluginResult(result);
189209
} catch (Exception e) {
190-
Log.e("WebSocketInstance", "Error sending event", e);
210+
Log.e(TAG, "Error sending event", e);
191211
}
192212
}
193213
}

0 commit comments

Comments
 (0)