/** * This file is part of ObjectFabric (http://objectfabric.org). * * ObjectFabric is licensed under the Apache License, Version 2.0, the terms * of which may be found at http://www.apache.org/licenses/LICENSE-2.0.html. * * Copyright ObjectFabric Inc. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ package org.objectfabric; import org.objectfabric.CloseCounter.Callback; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.user.client.Window.Navigator; class WebSocketConnection extends Connection { private JavaScriptObject _webSocket; private final boolean _firefox = firefox(); WebSocketConnection(Remote remote) { this(remote, null); } WebSocketConnection(Location location, JavaScriptObject webSocket) { super(location, null); _webSocket = webSocket; } final void connect() { Address address = ((Remote) location()).address(); _webSocket = createWebSocket(address.toString() + Remote.WS_PATH, this); } private native JavaScriptObject createWebSocket(String url, WebSocketConnection callback) /*-{ var socket = new WebSocket(url); socket.binaryType = "arraybuffer"; socket.onopen = function() { callback.@org.objectfabric.WebSocketConnection::onOpen()(); } socket.onclose = function() { callback.@org.objectfabric.WebSocketConnection::onClose()(); } socket.onerror = function() { callback.@org.objectfabric.WebSocketConnection::onError()(); } socket.onmessage = function(event) { if (event.data instanceof ArrayBuffer || event.data instanceof Buffer) { var array = new Uint8Array(event.data); callback.@org.objectfabric.WebSocketConnection::onMessage(Lorg/objectfabric/Uint8Array;)(array); } } return socket; }-*/; final void onOpen() { if (location() instanceof Remote) ((Remote) location()).onConnection(this); onStarted(); } private void onClose() { ((Remote) location()).onError(this, Strings.DISCONNECTED, true); } private void onError() { ((Remote) location()).onError(this, Strings.DISCONNECTED, true); } final void onMessage(Uint8Array buffer) { if (resumeRead()) { int offset = 0; int length = buffer.length(); while (offset < length) { // Copy for getLargestUnsplitable() offset GWTBuff buff = (GWTBuff) Buff.getOrCreate(); buff.position(Buff.getLargestUnsplitable()); int copy = Math.min(length - offset, buff.remaining()); Uint8Array sub = buffer.subarray(offset, offset + copy); buff.typed().set(sub, buff.position()); buff.limit(buff.position() + copy); offset += copy; read(buff); buff.recycle(); } suspendRead(); } } @Override void onClose(Callback callback) { super.onClose(callback); try { close(_webSocket); } catch (Exception _) { // Ignore } } private native void close(JavaScriptObject webSocket) /*-{ webSocket.close(); }-*/; @Override protected void write() { Queue<Buff> buffs = fill(0xffff); if (buffs != null) { Exception ex = null; try { for (int i = 0; i < buffs.size(); i++) { GWTBuff buff = (GWTBuff) buffs.get(i); send(_webSocket, buff.subarray(), _firefox); } } catch (Exception e) { ex = e; } while (buffs.size() > 0) buffs.poll().recycle(); writeComplete(); if (ex != null && location() instanceof Remote) ((Remote) location()).onError(this, ex.toString(), true); } } boolean firefox() { return Navigator.getUserAgent().toLowerCase().indexOf("firefox") >= 0; } native void send(JavaScriptObject webSocket, Uint8Array array, boolean firefox) /*-{ if (firefox) { // Firefox doesn't like sending Uint8Array directly, or the slice method if (!ArrayBuffer.prototype.slice) { ArrayBuffer.prototype.slice = function(start, end) { var that = new Uint8Array(this); if (end == undefined) end = that.length; var result = new ArrayBuffer(end - start); var resultArray = new Uint8Array(result); for ( var i = 0; i < resultArray.length; i++) resultArray[i] = that[i + start]; return result; } } array = array.buffer.slice(buffer.byteOffset, buffer.length); } webSocket.send(array); }-*/; }