diff options
Diffstat (limited to 'plugins/cordova-plugin-websocket/src/android/com')
9 files changed, 737 insertions, 0 deletions
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java new file mode 100644 index 00000000..f2a3b138 --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java @@ -0,0 +1,150 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; +import org.apache.cordova.PluginResult.Status; +import org.eclipse.jetty.websocket.PerMessageDeflateExtension; +import org.eclipse.jetty.websocket.WebSocket.Connection; +import org.eclipse.jetty.websocket.WebSocketClient; +import org.eclipse.jetty.websocket.WebSocketClientFactory; +import org.json.JSONArray; +import org.json.JSONObject; + +import com.knowledgecode.cordova.websocket.TaskRunner.Task; +import com.knowledgecode.cordova.websocket.WebSocketGenerator.OnCloseListener; +import com.knowledgecode.cordova.websocket.WebSocketGenerator.OnOpenListener; + +import android.util.SparseArray; +import android.webkit.CookieManager; + +/** + * Connect to server. + */ +class ConnectionTask implements Task { + + private static final long MAX_CONNECT_TIME = 75000; + private static final int MAX_TEXT_MESSAGE_SIZE = -1; + private static final int MAX_BINARY_MESSAGE_SIZE = -1; + + private final WebSocketClientFactory _factory; + private final SparseArray<Connection> _map; + + /** + * Constructor + * + * @param factory + * @param map + */ + public ConnectionTask(WebSocketClientFactory factory, SparseArray<Connection> map) { + _factory = factory; + _map = map; + + if (!_factory.isRunning()) { + try { + _factory.start(); + } catch (Exception e) { + } + } + } + + /** + * Set cookies, if any. + * + * @param cookies + * @param url + */ + private static void setCookie(Map<String, String> cookies, String url) { + String cookie = CookieManager.getInstance().getCookie(url); + + if (cookie != null) { + for (String c : cookie.split(";")) { + String[] pair = c.split("="); + + if (pair.length == 2) { + cookies.put(pair[0], pair[1]); + } + } + } + } + + @Override + public void execute(String rawArgs, CallbackContext ctx) { + try { + WebSocketClient client = _factory.newWebSocketClient(); + + JSONArray args = new JSONArray(rawArgs); + int id = Integer.parseInt(args.getString(0), 16); + URI uri = new URI(args.getString(1)); + String protocol = args.getString(2); + JSONObject options = args.getJSONObject(5); + String origin = options.optString("origin", args.getString(3)); + String agent = options.optString("agent", args.getString(4)); + boolean deflate = options.optBoolean("perMessageDeflate", true); + long maxConnectTime = options.optLong("maxConnectTime", MAX_CONNECT_TIME); + + client.setMaxTextMessageSize(options.optInt("maxTextMessageSize", MAX_TEXT_MESSAGE_SIZE)); + client.setMaxBinaryMessageSize(options.optInt("maxBinaryMessageSize", MAX_BINARY_MESSAGE_SIZE)); + if (protocol.length() > 0) { + client.setProtocol(protocol); + } + if (origin.length() > 0) { + client.setOrigin(origin); + } + if (agent.length() > 0) { + client.setAgent(agent); + } + if (deflate) { + client.getExtensions().add(new PerMessageDeflateExtension()); + } + + setCookie(client.getCookies(), uri.getHost()); + + WebSocketGenerator gen = new WebSocketGenerator(id, ctx); + + gen.setOnOpenListener(new OnOpenListener() { + @Override + public void onOpen(int id, Connection conn) { + _map.put(id, conn); + } + }); + gen.setOnCloseListener(new OnCloseListener() { + @Override + public void onClose(int id) { + if (_map.indexOfKey(id) >= 0) { + _map.remove(id); + } + } + }); + client.open(uri, gen, maxConnectTime, TimeUnit.MILLISECONDS); + } catch (Exception e) { + if (!ctx.isFinished()) { + PluginResult result = new PluginResult(Status.ERROR); + result.setKeepCallback(true); + ctx.sendPluginResult(result); + } + } + } +} + diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java new file mode 100644 index 00000000..dcf13445 --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; +import org.eclipse.jetty.websocket.WebSocket.Connection; +import org.eclipse.jetty.websocket.WebSocketClientFactory; + +import com.knowledgecode.cordova.websocket.TaskRunner.Task; + +import android.util.SparseArray; + +/** + * Stop WebSocket client. + */ +class DestroyTask implements Task { + + private final WebSocketClientFactory _factory; + private final SparseArray<Connection> _map; + + /** + * Constructor + * + * @param factory + * @param map + */ + public DestroyTask(WebSocketClientFactory factory, SparseArray<Connection> map) { + _factory = factory; + _map = map; + } + + @Override + public void execute(String rawArgs, CallbackContext ctx) { + for (int i = 0; i < _map.size(); i++) { + int key = _map.keyAt(i); + + if (_map.get(key).isOpen()) { + _map.get(key).close(); + } + } + _map.clear(); + + if (_factory.isRunning()) { + try { + _factory.stop(); + } catch (Exception e) { + } + } + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java new file mode 100644 index 00000000..18502488 --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; +import org.apache.cordova.PluginResult.Status; +import org.eclipse.jetty.websocket.WebSocket.Connection; +import org.json.JSONArray; + +import com.knowledgecode.cordova.websocket.TaskRunner.Task; + +import android.util.SparseArray; + +/** + * Close a connection. + */ +class DisconnectionTask implements Task { + + private final SparseArray<Connection> _map; + + /** + * Constructor + * + * @param map + */ + public DisconnectionTask(SparseArray<Connection> map) { + _map = map; + } + + @Override + public void execute(String rawArgs, CallbackContext ctx) { + try { + JSONArray args = new JSONArray(rawArgs); + int id = Integer.parseInt(args.getString(0), 16); + int code = args.getInt(1); + String reason = args.getString(2); + Connection conn = _map.get(id); + + if (conn != null) { + if (code > 0) { + conn.close(code, reason); + } else { + conn.close(); + } + } + } catch (Exception e) { + if (!ctx.isFinished()) { + PluginResult result = new PluginResult(Status.ERROR); + result.setKeepCallback(true); + ctx.sendPluginResult(result); + } + } + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java new file mode 100644 index 00000000..16d4267b --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; +import org.eclipse.jetty.websocket.WebSocket.Connection; + +import com.knowledgecode.cordova.websocket.TaskRunner.Task; + +import android.util.SparseArray; + +/** + * Close all connections. + */ +class ResetTask implements Task { + + private final SparseArray<Connection> _map; + + /** + * Constructor + * + * @param map + */ + public ResetTask(SparseArray<Connection> map) { + _map = map; + } + + @Override + public void execute(String rawArgs, CallbackContext ctx) { + for (int i = 0; i < _map.size(); i++) { + int key = _map.keyAt(i); + + if (_map.get(key).isOpen()) { + _map.get(key).close(); + } + } + _map.clear(); + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java new file mode 100644 index 00000000..ae73da2b --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; +import org.apache.cordova.PluginResult.Status; +import org.eclipse.jetty.websocket.WebSocket.Connection; + +import com.knowledgecode.cordova.websocket.TaskRunner.Task; + +import android.util.Base64; +import android.util.SparseArray; + +/** + * Send text/binary data. + */ +class SendingTask implements Task { + + private final SparseArray<Connection> _map; + + /** + * Constructor + * + * @param map + */ + public SendingTask(SparseArray<Connection> map) { + _map = map; + } + + @Override + public void execute(String rawArgs, CallbackContext ctx) { + try { + Connection conn = _map.get(Integer.parseInt(rawArgs.substring(2, 10), 16)); + + if (conn != null) { + if (rawArgs.charAt(10) == '1') { + byte[] binary = Base64.decode(rawArgs.substring(rawArgs.indexOf(',') + 1, rawArgs.length() - 2), + Base64.NO_WRAP); + conn.sendMessage(binary, 0, binary.length); + } else { + conn.sendMessage(rawArgs.substring(11, rawArgs.length() - 2)); + } + } + } catch (Exception e) { + if (!ctx.isFinished()) { + PluginResult result = new PluginResult(Status.ERROR); + result.setKeepCallback(true); + ctx.sendPluginResult(result); + } + } + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java new file mode 100644 index 00000000..444f3824 --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java @@ -0,0 +1,33 @@ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; + +class TaskBean { + private final String _action; + private final String _rawArgs; + private final CallbackContext _ctx; + + public TaskBean(final String action) { + _action = action; + _rawArgs = "[]"; + _ctx = null; + } + + public TaskBean(final String action, final String rawArgs, final CallbackContext ctx) { + _action = action; + _rawArgs = rawArgs; + _ctx = ctx; + } + + public String getAction() { + return _action; + } + + public String getRawArgs() { + return _rawArgs; + } + + public CallbackContext getCtx() { + return _ctx; + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java new file mode 100644 index 00000000..9fe2ad59 --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.apache.cordova.CallbackContext; + +class TaskRunner implements Runnable { + + interface Task { + public void execute(String rawArgs, CallbackContext ctx); + } + + private BlockingQueue<TaskBean> _queue; + private Map<String, Task> _map; + + public TaskRunner() { + _queue = new LinkedBlockingQueue<TaskBean>(); + _map = new HashMap<String, Task>(); + } + + public void setTask(String action, Task task) { + _map.put(action, task); + } + + public boolean addTaskQueue(TaskBean bean) { + try { + _queue.put(bean); + } catch (InterruptedException e) { + return false; + } + return true; + } + + @Override + public void run() { + while (true) { + TaskBean task; + + try { + task = _queue.take(); + } catch (InterruptedException e) { + break; + } + String action = task.getAction(); + + _map.get(action).execute(task.getRawArgs(), task.getCtx()); + if (WebSocket.DESTROY_TASK.equals(action)) { + break; + } + } + _queue.clear(); + _map.clear(); + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java new file mode 100644 index 00000000..479de7ee --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.eclipse.jetty.websocket.WebSocket.Connection; +import org.eclipse.jetty.websocket.WebSocketClientFactory; + +import android.util.SparseArray; + +/** + * Cordova WebSocket Plugin for Android + * This plugin is using Jetty under the terms of the Apache License v2.0. + * + * @author KNOWLEDGECODE <knowledgecode@gmail.com> + * @version 0.11.0 + */ +public class WebSocket extends CordovaPlugin { + + static final String CREATE_TASK = "create"; + static final String SEND_TASK = "send"; + static final String CLOSE_TASK = "close"; + static final String RESET_TASK = "reset"; + static final String DESTROY_TASK = "destroy"; + + private WebSocketClientFactory _factory; + private SparseArray<Connection> _conn; + private ExecutorService _executor; + private TaskRunner _runner; + + @Override + public void initialize(CordovaInterface cordova, final CordovaWebView webView) { + super.initialize(cordova, webView); + _factory = new WebSocketClientFactory(); + _conn = new SparseArray<Connection>(); + _executor = Executors.newSingleThreadExecutor(); + _runner = new TaskRunner(); + _runner.setTask(CREATE_TASK, new ConnectionTask(_factory, _conn)); + _runner.setTask(SEND_TASK, new SendingTask(_conn)); + _runner.setTask(CLOSE_TASK, new DisconnectionTask(_conn)); + _runner.setTask(RESET_TASK, new ResetTask(_conn)); + _runner.setTask(DESTROY_TASK, new DestroyTask(_factory, _conn)); + _executor.execute(_runner); + } + + @Override + public boolean execute(String action, String rawArgs, CallbackContext ctx) { + return _runner.addTaskQueue(new TaskBean(action, rawArgs, ctx)); + }; + + @Override + public void onReset() { + _runner.addTaskQueue(new TaskBean(RESET_TASK)); + super.onReset(); + } + + @Override + public void onDestroy() { + _runner.addTaskQueue(new TaskBean(DESTROY_TASK)); + _executor.shutdown(); + try { + _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + } + super.onDestroy(); + } +} diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java new file mode 100644 index 00000000..3132240a --- /dev/null +++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.knowledgecode.cordova.websocket; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.PluginResult; +import org.apache.cordova.PluginResult.Status; + +import android.util.Base64; + +class WebSocketGenerator implements + org.eclipse.jetty.websocket.WebSocket.OnTextMessage, + org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage { + + interface OnOpenListener { + public void onOpen(int id, Connection conn); + } + + interface OnCloseListener { + public void onClose(int id); + } + + private final int _id; + private final CallbackContext _ctx; + private OnOpenListener _openListener; + private OnCloseListener _closeListener; + + /** + * Constructor + * + * @param id + * @param ctx + */ + public WebSocketGenerator(int id, CallbackContext ctx) { + _id = id; + _ctx = ctx; + _openListener = new OnOpenListener() { + @Override + public void onOpen(int id, Connection conn) { + // NOP + } + }; + _closeListener = new OnCloseListener() { + @Override + public void onClose(int id) { + // NOP + } + }; + } + + /** + * Set OnOpen listener. + * + * @param l + */ + public void setOnOpenListener(OnOpenListener l) { + _openListener = l; + } + + /** + * Set OnClose listener. + * + * @param l + */ + public void setOnCloseListener(OnCloseListener l) { + _closeListener = l; + } + + @Override + public void onOpen(Connection conn) { + _openListener.onOpen(_id, conn); + + String protocol = conn.getProtocol(); + String extensions = conn.getExtensions(); + protocol = protocol == null ? "" : protocol; + extensions = extensions == null ? "" : extensions; + sendCallback(String.format("O[\"%s\",\"%s\"]", protocol, extensions), true); + } + + @Override + public void onMessage(String data) { + sendCallback("T" + data, true); + } + + @Override + public void onMessage(byte[] data, int offset, int length) { + sendCallback("B" + Base64.encodeToString(data, offset, length, Base64.NO_WRAP), true); + } + + @Override + public void onClose(int code, String reason) { + _closeListener.onClose(_id); + + String wasClean = code == 1000 ? "1" : "0"; + reason = reason == null ? "" : reason; + sendCallback(String.format("C%s%4d%s", wasClean, code, reason), false); + } + + /** + * Send plugin result. + * + * @param callbackString + * @param keepCallback + */ + private void sendCallback(String callbackString, boolean keepCallback) { + if (!_ctx.isFinished()) { + PluginResult result = new PluginResult(Status.OK, callbackString); + result.setKeepCallback(keepCallback); + _ctx.sendPluginResult(result); + } + } +} |
