Skip to content

Commit 55aab1d

Browse files
feat: ✨ Native Websocket Plugin (uses okhttp)
1 parent f3450a9 commit 55aab1d

8 files changed

Lines changed: 374 additions & 1 deletion

File tree

package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@
3535
"cordova-plugin-system": {},
3636
"cordova-plugin-advanced-http": {
3737
"ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1"
38-
}
38+
},
39+
"cordova-plugin-websocket": {}
3940
},
4041
"platforms": [
4142
"android"
@@ -74,6 +75,7 @@
7475
"cordova-plugin-server": "file:src/plugins/server",
7576
"cordova-plugin-sftp": "file:src/plugins/sftp",
7677
"cordova-plugin-system": "file:src/plugins/system",
78+
"cordova-plugin-websocket": "file:src/plugins/websocket",
7779
"css-loader": "^7.1.2",
7880
"mini-css-extract-plugin": "^2.9.0",
7981
"path-browserify": "^1.0.1",

src/plugins/websocket/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Cordova Plugin: OkHttp WebSocket
2+
3+
A Cordova plugin that uses [OkHttp](https://square.github.io/okhttp/) to provide WebSocket support in your Cordova app.
4+
It aims to mimic the [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) in JavaScript, with additional features.
5+
6+
## Features
7+
8+
* ✅ WebSocket API-like interface
9+
* ✅ Event support: `onopen`, `onmessage`, `onerror`, `onclose`
10+
*`extensions` and `readyState` properties
11+
* ✅ Support for protocols
12+
* ✅ Compatible with Cordova for Android
13+
14+
15+
---
16+
17+
## Usage
18+
19+
### Import
20+
21+
```javascript
22+
const WebSocketPlugin = cordova.plugins.websocket;
23+
```
24+
25+
### Connect to WebSocket
26+
27+
```javascript
28+
WebSocketPlugin.connect("wss://example.com/socket", ["protocol1", "protocol2"])
29+
.then(ws => {
30+
ws.onopen = (e) => console.log("Connected!", e);
31+
ws.onmessage = (e) => console.log("Message:", e.data);
32+
ws.onerror = (e) => console.error("Error:", e);
33+
ws.onclose = (e) => console.log("Closed:", e);
34+
35+
ws.send("Hello from Cordova!");
36+
ws.close();
37+
})
38+
.catch(err => console.error("WebSocket connection failed:", err));
39+
```
40+
41+
---
42+
43+
## API Reference
44+
45+
### Methods
46+
47+
* `WebSocketPlugin.connect(url, protocols)`
48+
49+
* Connects to a WebSocket server.
50+
* `url`: The WebSocket server URL.
51+
* `protocols`: (Optional) An array of subprotocol strings.
52+
* Returns: A Promise that resolves to a `WebSocketInstance`.
53+
54+
* `WebSocketInstance.send(message)`
55+
56+
* Sends a message to the server.
57+
* Throws an error if the connection is not open.
58+
59+
* `WebSocketInstance.close()`
60+
61+
* Closes the connection.
62+
63+
---
64+
65+
### Properties of `WebSocketInstance`
66+
67+
* `onopen`: Event listener for connection open.
68+
* `onmessage`: Event listener for messages received.
69+
* `onclose`: Event listener for connection close.
70+
* `onerror`: Event listener for errors.
71+
* `readyState`: (number) The state of the connection.
72+
73+
* 0 (`CONNECTING`): Socket created, not yet open.
74+
* 1 (`OPEN`): Connection is open and ready.
75+
* 2 (`CLOSING`): Connection is closing.
76+
* 3 (`CLOSED`): Connection is closed or couldn't be opened.
77+
* `extensions`: (string) Extensions negotiated by the server (usually empty or a list).
78+
79+
---
80+
81+
## Notes
82+
83+
* Only supported on Android (via OkHttp).
84+
* Make sure to handle connection lifecycle properly (close sockets when done).
85+
86+
---

src/plugins/websocket/package.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "cordova-plugin-websocket",
3+
"version": "0.0.1",
4+
"description": "This cordova plugin is created to use WebSocket (client) in web/js.",
5+
"cordova": {
6+
"id": "cordova-plugin-websocket",
7+
"platforms": [
8+
"android"
9+
]
10+
},
11+
"keywords": [
12+
"cordova",
13+
"websocket",
14+
"cordova-android",
15+
"ws"
16+
],
17+
"author": "Acode-Foundation (created by UnschooledGamer)",
18+
"license": "Apache-2.0",
19+
"scripts": {
20+
"test": "echo \"Error: no test specified\" && exit 1"
21+
}
22+
}

src/plugins/websocket/plugin.xml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<plugin id="cordova-plugin-websocket" version="0.0.1" xmlns="http://apache.org/cordova/ns/plugins/1.0">
3+
<name>cordova-plugin-websocket</name>
4+
<description>Cordova Websocket</description>
5+
<license>MIT</license>
6+
<keywords>cordova,ws,WebSocket</keywords>
7+
<js-module src="www/websocket.js" name="WebSocket">
8+
<clobbers target="cordova.websocket" />
9+
</js-module>
10+
11+
<platform name="android">
12+
<config-file target="res/xml/config.xml" parent="/*">
13+
<feature name="WebSocketPlugin">
14+
<param name="android-package" value="com.foxdebug.websocket.WebSocketPlugin" />
15+
</feature>
16+
</config-file>
17+
<source-file src="src/android/WebSocketPlugin.java" target-dir="src/com/foxdebug/websocket" />
18+
<source-file src="src/android/WebSocketInstance.java" target-dir="src/com/foxdebug/websocket" />
19+
<framework src="com.squareup.okhttp3:okhttp:4.12.0" />
20+
</platform>
21+
</plugin>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.foxdebug.websocket;
2+
3+
import android.util.Log;
4+
5+
import androidx.annotation.NonNull;
6+
7+
import org.apache.cordova.*;
8+
import org.json.JSONArray;
9+
import org.json.JSONObject;
10+
11+
import java.util.concurrent.TimeUnit;
12+
13+
import okhttp3.*;
14+
15+
public class WebSocketInstance extends WebSocketListener {
16+
private WebSocket webSocket;
17+
private CallbackContext callbackContext;
18+
private CordovaInterface cordova;
19+
private String instanceId;
20+
private String extensions = "";
21+
private int readyState = 0; // CONNECTING
22+
23+
public WebSocketInstance(String url, JSONArray protocols, CordovaInterface cordova, String instanceId) {
24+
this.cordova = cordova;
25+
this.instanceId = instanceId;
26+
27+
OkHttpClient client = new OkHttpClient.Builder()
28+
.connectTimeout(10, TimeUnit.SECONDS)
29+
.build();
30+
31+
Request.Builder requestBuilder = new Request.Builder().url(url);
32+
if (protocols != null) {
33+
StringBuilder protocolHeader = new StringBuilder();
34+
for (int i = 0; i < protocols.length(); i++) {
35+
protocolHeader.append(protocols.optString(i)).append(",");
36+
}
37+
if (protocolHeader.length() > 0) {
38+
protocolHeader.setLength(protocolHeader.length() - 1);
39+
requestBuilder.addHeader("Sec-WebSocket-Protocol", protocolHeader.toString());
40+
}
41+
}
42+
43+
client.newWebSocket(requestBuilder.build(), this);
44+
}
45+
46+
public void setCallback(CallbackContext callbackContext) {
47+
this.callbackContext = callbackContext;
48+
PluginResult result = new PluginResult(PluginResult.Status.NO_RESULT);
49+
result.setKeepCallback(true);
50+
callbackContext.sendPluginResult(result);
51+
}
52+
53+
public void send(String message) {
54+
if (webSocket != null) {
55+
webSocket.send(message);
56+
}
57+
}
58+
59+
public void close() {
60+
if (webSocket != null) {
61+
readyState = 2; // CLOSING
62+
webSocket.close(1000, "Normal closure");
63+
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received close() action call");
64+
}
65+
66+
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " received close() action call, ignoring... as webSocket is null (not present)");
67+
}
68+
69+
@Override
70+
public void onOpen(@NonNull WebSocket webSocket, Response response) {
71+
this.webSocket = webSocket;
72+
this.readyState = 1; // OPEN
73+
this.extensions = response.headers("Sec-WebSocket-Extensions").toString();
74+
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Opened" + "received extensions=" + this.extensions);
75+
sendEvent("open", null);
76+
}
77+
78+
@Override
79+
public void onMessage(@NonNull WebSocket webSocket, String text) {
80+
sendEvent("message", text);
81+
Log.d("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Received message: " + text);
82+
}
83+
84+
@Override
85+
public void onClosing(WebSocket webSocket, int code, String reason) {
86+
this.readyState = 2; // CLOSING
87+
sendEvent("close", reason);
88+
Log.i("WebSocketInstance", "websocket instanceId=" + this.instanceId + " is Closing code: " + code + " reason: " + reason);
89+
}
90+
91+
@Override
92+
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
93+
this.readyState = 3; // CLOSED
94+
sendEvent("error", t.getMessage());
95+
Log.e("WebSocketInstance", "websocket instanceId=" + this.instanceId + " Error: " + t.getMessage());
96+
}
97+
98+
private void sendEvent(String type, String data) {
99+
if (callbackContext != null) {
100+
try {
101+
JSONObject event = new JSONObject();
102+
event.put("type", type);
103+
event.put("extensions", this.extensions);
104+
event.put("readyState", this.readyState);
105+
if (data != null) event.put("data", data);
106+
PluginResult result = new PluginResult(PluginResult.Status.OK, event);
107+
result.setKeepCallback(true);
108+
callbackContext.sendPluginResult(result);
109+
} catch (Exception e) {
110+
Log.e("WebSocketInstance", "Error sending event", e);
111+
}
112+
}
113+
}
114+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package com.foxdebug.websocket;
2+
3+
import org.apache.cordova.*;
4+
import org.json.*;
5+
6+
import java.util.HashMap;
7+
import java.util.UUID;
8+
9+
// @TODO: plugin init & plugin destroy(closing okhttp clients) lifecycles.
10+
public class WebSocketPlugin extends CordovaPlugin {
11+
private static final HashMap<String, WebSocketInstance> instances = new HashMap<>();
12+
13+
@Override
14+
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
15+
switch (action) {
16+
case "connect":
17+
String url = args.optString(0);
18+
JSONArray protocols = args.optJSONArray(1);
19+
String id = UUID.randomUUID().toString();
20+
WebSocketInstance instance = new WebSocketInstance(url, protocols, cordova, id);
21+
instances.put(id, instance);
22+
callbackContext.success(id);
23+
return true;
24+
25+
case "send":
26+
String instanceId = args.optString(0);
27+
String message = args.optString(1);
28+
WebSocketInstance inst = instances.get(instanceId);
29+
if (inst != null) {
30+
inst.send(message);
31+
callbackContext.success();
32+
} else {
33+
callbackContext.error("Invalid instance ID");
34+
}
35+
return true;
36+
37+
case "close":
38+
instanceId = args.optString(0);
39+
inst = instances.get(instanceId);
40+
if (inst != null) {
41+
inst.close();
42+
instances.remove(instanceId);
43+
callbackContext.success();
44+
} else {
45+
callbackContext.error("Invalid instance ID");
46+
}
47+
return true;
48+
49+
case "registerListener":
50+
instanceId = args.optString(0);
51+
inst = instances.get(instanceId);
52+
if (inst != null) {
53+
inst.setCallback(callbackContext);
54+
} else {
55+
callbackContext.error("Invalid instance ID");
56+
}
57+
return true;
58+
59+
default:
60+
return false;
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)