Skip to content

Commit 4f27c2b

Browse files
feat: enhance WebSocketInstance with binary message support and improved event handling
- Added support for binary messages in `WebSocketInstance` with a new `onMessage` method for handling `ByteString`. - Updated `sendEvent` method to include a flag indicating if the message is binary. - Modified JavaScript interface to handle binary data and added logging for better debugging. - Updated `WebSocketPlugin.connect` to accept a `binaryType` parameter for configuration.
1 parent 48a831b commit 4f27c2b

3 files changed

Lines changed: 104 additions & 20 deletions

File tree

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

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ public class WebSocketInstance extends WebSocketListener {
2323
private final CordovaInterface cordova;
2424
private final String instanceId;
2525
private String extensions = "";
26+
private String protocol = "";
27+
private String binaryType = "";
2628
private int readyState = 0; // CONNECTING
2729

2830
// okHttpMainClient parameter is used. To have a single main client(singleton), with per-websocket configuration using newBuilder method.
29-
public WebSocketInstance(String url, JSONArray protocols, JSONObject headers, OkHttpClient okHttpMainClient, CordovaInterface cordova, String instanceId) {
31+
public WebSocketInstance(String url, JSONArray protocols, JSONObject headers, String binaryType, OkHttpClient okHttpMainClient, CordovaInterface cordova, String instanceId) {
3032
this.cordova = cordova;
3133
this.instanceId = instanceId;
34+
this.binaryType = binaryType;
3235

3336
OkHttpClient client = okHttpMainClient.newBuilder()
3437
.connectTimeout(10, TimeUnit.SECONDS)
@@ -112,14 +115,33 @@ public void onOpen(@NonNull WebSocket webSocket, Response response) {
112115
this.webSocket = webSocket;
113116
this.readyState = 1; // OPEN
114117
this.extensions = response.headers("Sec-WebSocket-Extensions").toString();
118+
this.protocol = response.header("Sec-WebSocket-Protocol");
115119
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Opened" + "received extensions=" + this.extensions);
116-
sendEvent("open", null);
120+
sendEvent("open", null, false);
117121
}
118122

119123
@Override
120124
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
121-
sendEvent("message", text);
122125
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Received message: " + text);
126+
sendEvent("message", text, false);
127+
}
128+
129+
// This is called when the Websocket server sends a binary(type 0x2) message.
130+
@Override
131+
public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
132+
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Received message(bytes): " + bytes.toString());
133+
134+
try {
135+
if ("arraybuffer".equals(this.binaryType)) {
136+
String base64 = bytes.base64();
137+
sendEvent("message", base64, true);
138+
} else {
139+
sendEvent("message", bytes.utf8(), true);
140+
}
141+
} catch (Exception e) {
142+
Log.e("WebSocketInstance", "Error sending message", e);
143+
}
144+
123145
}
124146

125147
@Override
@@ -139,24 +161,26 @@ public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String rea
139161
} catch (JSONException e) {
140162
Log.e("WebSocketInstance", "Error creating close event", e);
141163
}
142-
sendEvent("close", closedEvent.toString());
164+
sendEvent("close", closedEvent.toString(), false);
143165
}
144166

145167
@Override
146168
public void onFailure(@NonNull WebSocket webSocket, Throwable t, Response response) {
147169
this.readyState = 3; // CLOSED
148-
sendEvent("error", t.getMessage());
170+
sendEvent("error", t.getMessage(), false);
149171
Log.e("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Error: " + t.getMessage());
150172
}
151173

152-
private void sendEvent(String type, String data) {
174+
private void sendEvent(String type, String data, boolean isBinary) {
153175
if (callbackContext != null) {
154176
try {
155177
JSONObject event = new JSONObject();
156178
event.put("type", type);
157179
event.put("extensions", this.extensions);
158180
event.put("readyState", this.readyState);
181+
event.put("isBinary", isBinary ? true : false);
159182
if (data != null) event.put("data", data);
183+
Log.d("WebSocketInstance", "sending event: " + type + " eventObj " + event.toString());
160184
PluginResult result = new PluginResult(PluginResult.Status.OK, event);
161185
result.setKeepCallback(true);
162186
callbackContext.sendPluginResult(result);

src/plugins/websocket/src/android/WebSocketPlugin.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ public boolean execute(String action, JSONArray args, final CallbackContext call
2828
String url = args.optString(0);
2929
JSONArray protocols = args.optJSONArray(1);
3030
JSONObject headers = args.optJSONObject(2);
31+
String binaryType = args.optString(3, null);
3132
String id = UUID.randomUUID().toString();
32-
WebSocketInstance instance = new WebSocketInstance(url, protocols, headers, this.okHttpMainClient, cordova, id);
33+
WebSocketInstance instance = new WebSocketInstance(url, protocols, headers, binaryType, this.okHttpMainClient, cordova, id);
3334
instances.put(id, instance);
3435
callbackContext.success(id);
3536
return true;

src/plugins/websocket/www/websocket.js

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
var exec = require('cordova/exec');
2+
/**
3+
* Whether to log debug messages
4+
*/
5+
let DEBUG = false;
6+
7+
const logIfDebug = (...args) => {
8+
if (DEBUG) {
9+
console.log(...args);
10+
}
11+
};
212

3-
class WebSocketInstance {
13+
class WebSocketInstance extends EventTarget {
414
constructor(url, instanceId) {
15+
super();
516
this.instanceId = instanceId;
617
this.extensions = '';
718
this.readyState = WebSocketInstance.CONNECTING;
@@ -10,27 +21,66 @@ class WebSocketInstance {
1021
this.onclose = null;
1122
this.onerror = null;
1223
this.url = url;
24+
this.binaryType = ''; // empty as Default is string.
1325

1426
exec((event) => {
27+
logIfDebug(`[Cordova WebSocket - ID=${this.instanceId}] Event from native:`, event);
28+
1529
if (event.type === 'open') {
1630
this.readyState = WebSocketInstance.OPEN;
1731
this.extensions = event.extensions || '';
18-
if (this.onopen) this.onopen.bind(this)(event);
32+
if (this.onopen) this.onopen(event);
33+
this.dispatchEvent(new Event('open'));
34+
}
35+
36+
if (event.type === 'message') {
37+
let msgData = event.data;
38+
if (event.isBinary && this.binaryType === 'arraybuffer') {
39+
let binary = atob(msgData);
40+
let bytes = new Uint8Array(binary.length);
41+
for (let i = 0; i < binary.length; i++) {
42+
bytes[i] = binary.charCodeAt(i);
43+
}
44+
msgData = bytes.buffer;
45+
}
46+
logIfDebug(`[Cordova WebSocket - ID=${this.instanceId}] msg Event:`, event, msgData);
47+
const msgEvent = new MessageEvent('message', { data: msgData });
48+
if (this.onmessage) this.onmessage(msgEvent);
49+
this.dispatchEvent(msgEvent);
1950
}
20-
if (event.type === 'message' && this.onmessage) this.onmessage.bind(this)(event);
51+
2152
if (event.type === 'close') {
2253
this.readyState = WebSocketInstance.CLOSED;
23-
if (this.onclose) this.onclose.bind(this)({ code: event?.data?.code, reason: event?.data?.reason, type: event.type });
54+
const closeEvent = new CloseEvent('close', { code: event.data?.code, reason: event.data?.reason });
55+
if (this.onclose) this.onclose(closeEvent);
56+
this.dispatchEvent(closeEvent);
57+
}
58+
59+
if (event.type === 'error') {
60+
const errorEvent = new Event('error', { message: event?.data });
61+
if (this.onerror) this.onerror(errorEvent);
62+
this.dispatchEvent(errorEvent);
2463
}
25-
if (event.type === 'error' && this.onerror) this.onerror.bind(this)(event);
2664
}, null, "WebSocketPlugin", "registerListener", [this.instanceId]);
2765
}
2866

2967
send(message) {
30-
if (this.readyState === WebSocketInstance.OPEN) {
31-
exec(null, null, "WebSocketPlugin", "send", [this.instanceId, message]);
68+
if (this.readyState !== WebSocketInstance.OPEN) {
69+
throw new Error(`WebSocket is not open/connected`);
70+
}
71+
72+
let finalMessage = null;
73+
if (message instanceof ArrayBuffer || ArrayBuffer.isView(message)) {
74+
const uint8Array = message instanceof ArrayBuffer ? new Uint8Array(message) : message;
75+
finalMessage = btoa(String.fromCharCode.apply(null, uint8Array));
76+
77+
exec(() => logIfDebug(`[Cordova WebSocket - ID=${this.instanceId}] Sent message:`, finalMessage), (err) => console.error(`[Cordova WebSocket - ID=${this.instanceId}] Send error:`, err), "WebSocketPlugin", "send", [this.instanceId, finalMessage]);
78+
} else if (typeof message === 'string') {
79+
finalMessage = message;
80+
81+
exec(() => logIfDebug(`[Cordova WebSocket - ID=${this.instanceId}] Sent message:`, finalMessage), (err) => console.error(`[Cordova WebSocket - ID=${this.instanceId}] Send error:`, err), "WebSocketPlugin", "send", [this.instanceId, finalMessage]);
3282
} else {
33-
throw new Error("WebSocket is not open");
83+
throw new Error(`Unsupported message type: ${typeof message}`);
3484
}
3585
}
3686

@@ -42,7 +92,7 @@ class WebSocketInstance {
4292
*/
4393
close(code, reason) {
4494
this.readyState = WebSocketInstance.CLOSING;
45-
exec(null, null, "WebSocketPlugin", "close", [this.instanceId, code, reason]);
95+
exec(() => logIfDebug(`[Cordova WebSocket - ID=${this.instanceId}] Close requested`, code, reason), (err) => console.error(`[Cordova WebSocket - ID=${this.instanceId}] Close error`, err), "WebSocketPlugin", "close", [this.instanceId, code, reason]);
4696
}
4797
}
4898

@@ -51,9 +101,9 @@ WebSocketInstance.OPEN = 1;
51101
WebSocketInstance.CLOSING = 2;
52102
WebSocketInstance.CLOSED = 3;
53103

54-
const connect = function(url, protocols = null, headers = null) {
104+
const connect = function(url, protocols = null, headers = null, binaryType) {
55105
return new Promise((resolve, reject) => {
56-
exec(instanceId => resolve(new WebSocketInstance(url, instanceId)), reject, "WebSocketPlugin", "connect", [url, protocols, headers]);
106+
exec(instanceId => resolve(new WebSocketInstance(url, instanceId)), reject, "WebSocketPlugin", "connect", [url, protocols, binaryType, headers]);
57107
});
58108
};
59109

@@ -67,7 +117,16 @@ const listClients = function() {
67117

68118
const send = function(instanceId, message) {
69119
return new Promise((resolve, reject) => {
70-
exec(resolve, reject, "WebSocketPlugin", "send", [instanceId, message]);
120+
if (typeof message === 'string') {
121+
exec(resolve, reject, "WebSocketPlugin", "send", [instanceId, message]);
122+
} else if (message instanceof ArrayBuffer || ArrayBuffer.isView(message)) {
123+
const uint8Array = message instanceof ArrayBuffer ? new Uint8Array(message) : message;
124+
const base64Message = btoa(String.fromCharCode.apply(null, uint8Array));
125+
126+
exec(resolve, reject, "WebSocketPlugin", "send", [instanceId, base64Message]);
127+
} else {
128+
reject(`Unsupported message type: ${typeof message}`);
129+
}
71130
});
72131
};
73132

@@ -86,4 +145,4 @@ const close = function(instanceId, code, reason) {
86145
});
87146
};
88147

89-
module.exports = { connect, listClients, send, close };
148+
module.exports = { connect, listClients, send, close, DEBUG };

0 commit comments

Comments
 (0)