diff options
Diffstat (limited to 'js/src/Ice/browser')
-rw-r--r-- | js/src/Ice/browser/.gitignore | 2 | ||||
-rw-r--r-- | js/src/Ice/browser/Buffer.js | 419 | ||||
-rw-r--r-- | js/src/Ice/browser/Debug.js | 41 | ||||
-rw-r--r-- | js/src/Ice/browser/EndpointFactory.js | 51 | ||||
-rw-r--r-- | js/src/Ice/browser/EndpointI.js | 543 | ||||
-rw-r--r-- | js/src/Ice/browser/Transceiver.js | 418 |
6 files changed, 1474 insertions, 0 deletions
diff --git a/js/src/Ice/browser/.gitignore b/js/src/Ice/browser/.gitignore new file mode 100644 index 00000000000..9c5c0a3f6d2 --- /dev/null +++ b/js/src/Ice/browser/.gitignore @@ -0,0 +1,2 @@ +ConnectionInfo.js +EndpointInfo.js diff --git a/js/src/Ice/browser/Buffer.js b/js/src/Ice/browser/Buffer.js new file mode 100644 index 00000000000..5d6c33393fe --- /dev/null +++ b/js/src/Ice/browser/Buffer.js @@ -0,0 +1,419 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +(function(global){ + // + // IE 10 doesn't implement ArrayBuffer.slice + // + + if(!ArrayBuffer.prototype.slice) + { + ArrayBuffer.prototype.slice = function (start, end) + { + var b = new Uint8Array(this); + end = end === undefined ? b.length : end; + var result = new Uint8Array(new ArrayBuffer(end - start)); + for(var i = 0, length = result.length; i < length; i++) + { + result[i] = b[i + start]; + } + return result.buffer; + }; + } + + var __BufferOverflowException__ = "BufferOverflowException"; + var __BufferUnderflowException__ = "BufferUnderflowException"; + var __IndexOutOfBoundsException__ = "IndexOutOfBoundsException"; + + // + // Buffer implementation to be used by web browsers, it uses ArrayBuffer as + // the store. + // + + require("Ice/Class"); + require("Ice/Long"); + + var Ice = global.Ice || Ice; + var Long = Ice.Long; + + var Buffer = Ice.Class({ + __init__: function(buffer) + { + if(buffer !== undefined) + { + this.b = buffer; + this.v = new DataView(this.b); + } + else + { + this.b = null; // ArrayBuffer + this.v = null; // DataView + } + this._position = 0; + this._limit = 0; + this._shrinkCounter = 0; + }, + empty: function() + { + return this._limit === 0; + }, + resize: function(n) + { + if(n === 0) + { + this.clear(); + } + else if(n > this.capacity) + { + this.reserve(n); + } + this._limit = n; + }, + clear: function() + { + this.b = null; + this.v = null; + this._position = 0; + this._limit = 0; + }, + // + // Call expand(n) to add room for n additional bytes. Note that expand() + // examines the current position of the buffer first; we don't want to + // expand the buffer if the caller is writing to a location that is + // already in the buffer. + // + expand: function(n) + { + var sz = this.capacity === 0 ? n : this._position + n; + if(sz > this._limit) + { + this.resize(sz); + } + }, + reset: function() + { + if(this._limit > 0 && this._limit * 2 < this.capacity) + { + // + // If the current buffer size is smaller than the + // buffer capacity, we shrink the buffer memory to the + // current size. This is to avoid holding on to too much + // memory if it's not needed anymore. + // + if(++this._shrinkCounter > 2) + { + this.reserve(this._limit); + this._shrinkCounter = 0; + } + } + else + { + this._shrinkCounter = 0; + } + this._limit = 0; + this._position = 0; + }, + reserve: function(n) + { + if(n > this.capacity) + { + var capacity = Math.max(n, 2 * this.capacity); + capacity = Math.max(1024, capacity); + if(!this.b) + { + this.b = new ArrayBuffer(capacity); + } + else + { + var b = new Uint8Array(capacity); + b.set(new Uint8Array(this.b)); + this.b = b.buffer; + } + this.v = new DataView(this.b); + } + else if(n < this.capacity) + { + this.b = this.b.slice(0, this.capacity); + this.v = new DataView(this.b); + } + else + { + return; + } + }, + put: function(v) + { + if(this._position === this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setUint8(this._position, v); + this._position++; + }, + putAt: function(i, v) + { + if(i >= this._limit) + { + throw new Error(__IndexOutOfBoundsException__); + } + this.v.setUint8(i, v); + }, + putArray: function(v) + { + if(v.byteLength > 0) + { + //Expects an Uint8Array + if(this._position + v.length > this._limit) + { + throw new Error(__BufferOverflowException__); + } + new Uint8Array(this.b, 0, this.b.byteLength).set(v, this._position); + this._position += v.byteLength; + } + }, + putShort: function(v) + { + if(this._position + 2 > this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setInt16(this._position, v, true); + this._position += 2; + }, + putInt: function(v) + { + if(this._position + 4 > this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setInt32(this._position, v, true); + this._position += 4; + }, + putIntAt: function(i, v) + { + if(i + 4 > this._limit || i < 0) + { + throw new Error(__IndexOutOfBoundsException__); + } + this.v.setInt32(i, v, true); + }, + putFloat: function(v) + { + if(this._position + 4 > this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setFloat32(this._position, v, true); + this._position += 4; + }, + putDouble: function(v) + { + if(this._position + 8 > this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setFloat64(this._position, v, true); + this._position += 8; + }, + putLong: function(v) + { + if(this._position + 8 > this._limit) + { + throw new Error(__BufferOverflowException__); + } + this.v.setInt32(this._position, v.low, true); + this._position += 4; + this.v.setInt32(this._position, v.high, true); + this._position += 4; + }, + writeString: function(stream, v) + { + // + // Encode the string as utf8 + // + var encoded = unescape(encodeURIComponent(v)); + + stream.writeSize(encoded.length); + stream.expand(encoded.length); + this.putString(encoded, encoded.length); + }, + putString: function(v, sz) + { + if(this._position + sz > this._limit) + { + throw new Error(__BufferOverflowException__); + } + for(var i = 0; i < sz; ++i) + { + this.v.setUint8(this._position, v.charCodeAt(i)); + this._position++; + } + }, + get: function() + { + if(this._position >= this._limit) + { + throw new Error(__BufferUnderflowException__); + } + var v = this.v.getUint8(this._position); + this._position++; + return v; + }, + getAt: function(i) + { + if(i < 0 || i >= this._limit) + { + throw new Error(__IndexOutOfBoundsException__); + } + return this.v.getUint8(i); + }, + getArray: function(length) + { + if(this._position + length > this._limit) + { + throw new Error(__BufferUnderflowException__); + } + var buffer = this.b.slice(this._position, this._position + length); + this._position += length; + return new Uint8Array(buffer); + }, + getArrayAt: function(position, length) + { + if(position + length > this._limit) + { + throw new Error(__BufferUnderflowException__); + } + length = length === undefined ? (this.b.byteLength - position) : length; + return new Uint8Array(this.b.slice(position, position + length)); + }, + getShort: function() + { + if(this._limit - this._position < 2) + { + throw new Error(__BufferUnderflowException__); + } + var v = this.v.getInt16(this._position, true); + this._position += 2; + return v; + }, + getInt: function() + { + if(this._limit - this._position < 4) + { + throw new Error(__BufferUnderflowException__); + } + var v = this.v.getInt32(this._position, true); + this._position += 4; + return v; + }, + getFloat: function() + { + if(this._limit - this._position < 4) + { + throw new Error(__BufferUnderflowException__); + } + var v = this.v.getFloat32(this._position, true); + this._position += 4; + return v; + }, + getDouble: function() + { + if(this._limit - this._position < 8) + { + throw new Error(__BufferUnderflowException__); + } + var v = this.v.getFloat64(this._position, true); + this._position += 8; + return v; + }, + getLong: function() + { + if(this._limit - this._position < 8) + { + throw new Error(__BufferUnderflowException__); + } + var v = new Long(); + v.low = this.v.getInt32(this._position, true); + this._position += 4; + v.high = this.v.getInt32(this._position, true); + this._position += 4; + return v; + }, + getString: function(length) + { + if(this._position + length > this._limit) + { + throw new Error(__BufferUnderflowException__); + } + + var data = new DataView(this.b, this._position, length); + var s = ""; + + for(var i = 0; i < length; ++i) + { + s += String.fromCharCode(data.getUint8(i)); + } + this._position += length; + s = decodeURIComponent(escape(s)); + return s; + } + }); + + var prototype = Buffer.prototype; + + Object.defineProperty(prototype, "position", { + get: function() { return this._position; }, + set: function(position){ + if(position >= 0 && position <= this._limit) + { + this._position = position; + } + } + }); + + Object.defineProperty(prototype, "limit", { + get: function() { return this._limit; }, + set: function(limit){ + if(limit <= this.capacity) + { + this._limit = limit; + if(this._position > limit) + { + this._position = limit; + } + } + } + }); + + Object.defineProperty(prototype, "capacity", { + get: function() { return this.b === null ? 0 : this.b.byteLength; } + }); + + Object.defineProperty(prototype, "remaining", { + get: function() { return this._limit - this._position; } + }); + + // + // Create a native buffer from an array of bytes. + // + Buffer.createNative = function(data) + { + if(data === undefined) + { + return new Uint8Array(0); + } + else + { + return new Uint8Array(data); + } + }; + + Ice.Buffer = Buffer; + global.Ice = Ice; +}(typeof (global) === "undefined" ? window : global)); diff --git a/js/src/Ice/browser/Debug.js b/js/src/Ice/browser/Debug.js new file mode 100644 index 00000000000..c6739e40ebd --- /dev/null +++ b/js/src/Ice/browser/Debug.js @@ -0,0 +1,41 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +(function(global){ + require("Ice/Class"); + require("Ice/Exception"); + + var Ice = global.Ice || {}; + + var Exception = Ice.Exception; + + var AssertionFailedException = Ice.Class(Error, { + __init__: function(message) + { + Error.call(this); + Exception.captureStackTrace(this); + this.message = message; + } + }); + var Debug = {}; + + Debug.AssertionFailedException = AssertionFailedException; + + Debug.assert = function(b, msg) + { + if(!b) + { + console.log(msg === undefined ? "assertion failed" : msg); + console.log(Error().stack); + throw new AssertionFailedException(msg === undefined ? "assertion failed" : msg); + } + }; + Ice.Debug = Debug; + global.Ice = Ice; +}(typeof (global) === "undefined" ? window : global)); diff --git a/js/src/Ice/browser/EndpointFactory.js b/js/src/Ice/browser/EndpointFactory.js new file mode 100644 index 00000000000..c8481c654b5 --- /dev/null +++ b/js/src/Ice/browser/EndpointFactory.js @@ -0,0 +1,51 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +(function(global){ + require("Ice/Class"); + require("Ice/Endpoint"); + + require("Ice/browser/EndpointInfo"); + require("Ice/browser/EndpointI"); + + var Ice = global.Ice || {}; + var IceWS = global.IceWS || {}; + + var EndpointI = IceWS.EndpointI; + + var EndpointFactory = Ice.Class({ + __init__:function(instance, secure) + { + this._instance = instance; + this._secure = secure; + }, + type: function() + { + return this._secure ? IceWS.WSSEndpointType : IceWS.WSEndpointType; + }, + protocol: function() + { + return this._secure ? "wss" : "ws"; + }, + create: function(str, oaEndpoint) + { + return EndpointI.fromString(this._instance, this._secure, str, oaEndpoint); + }, + read: function(s) + { + return EndpointI.fromStream(s, this._secure); + }, + destroy: function() + { + this._instance = null; + } + }); + IceWS.EndpointFactory = EndpointFactory; + global.IceWS = IceWS; +}(typeof (global) === "undefined" ? window : global)); diff --git a/js/src/Ice/browser/EndpointI.js b/js/src/Ice/browser/EndpointI.js new file mode 100644 index 00000000000..f364a087922 --- /dev/null +++ b/js/src/Ice/browser/EndpointI.js @@ -0,0 +1,543 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +(function(global){ + require("Ice/Class"); + require("Ice/Address"); + require("Ice/HashUtil"); + require("Ice/StringUtil"); + require("Ice/Endpoint"); + require("Ice/LocalException"); + + require("Ice/browser/Transceiver"); + require("Ice/browser/EndpointInfo"); + + var Ice = global.Ice || {}; + var IceWS = global.IceWS || {}; + + var Address = Ice.Address; + var HashUtil = Ice.HashUtil; + var StringUtil = Ice.StringUtil; + var Transceiver = IceWS.Transceiver; + + var Class = Ice.Class; + + var EndpointI = Class(Ice.Endpoint, { + __init__: function(instance, secure, ho, po, ti, conId, co, re) + { + this._instance = instance; + this._secure = secure; + this._host = ho; + this._port = po; + this._timeout = ti; + this._connectionId = conId; + this._compress = co; + this._resource = re; + this.calcHashValue(); + }, + // + // Convert the endpoint to its string form + // + toString: function() + { + // + // WARNING: Certain features, such as proxy validation in Glacier2, + // depend on the format of proxy strings. Changes to toString() and + // methods called to generate parts of the reference string could break + // these features. Please review for all features that depend on the + // format of proxyToString() before changing this and related code. + // + var s = (this._secure ? "wss" : "ws"); + + if(this._host !== null && this._host.length > 0) + { + s += " -h "; + s += (this._host.indexOf(':') !== -1) ? ("\"" + this._host + "\"") : this._host; + } + + s += " -p " + this._port; + + if(this._timeout != -1) + { + s += " -t " + this._timeout; + } + if(this._compress) + { + s += " -z"; + } + if(this._resource !== null && this._resource.length > 0) + { + s += " -r "; + s += (this._resource.indexOf(':') !== -1) ? ("\"" + this._resource + "\"") : this._resource; + } + return s; + }, + // + // Return the endpoint information. + // + getInfo: function() + { + return new EndpointInfoI(this._secure, this._timeout, this._compress, this._host, this._port, this._resource); + }, + // + // Marshal the endpoint + // + streamWrite: function(s) + { + s.writeShort(this._secure ? IceWS.WSSEndpointType : IceWS.WSEndpointType); + s.startWriteEncaps(); + s.writeString(this._host); + s.writeInt(this._port); + s.writeInt(this._timeout); + s.writeBool(this._compress); + s.writeString(this._resource); + s.endWriteEncaps(); + }, + // + // Return the endpoint type + // + type: function() + { + return this._secure ? IceWS.WSSEndpointType : IceWS.WSEndpointType; + }, + // + // Return the timeout for the endpoint in milliseconds. 0 means + // non-blocking, -1 means no timeout. + // + timeout: function() + { + return this._timeout; + }, + // + // Return a new endpoint with a different timeout value, provided + // that timeouts are supported by the endpoint. Otherwise the same + // endpoint is returned. + // + changeTimeout: function(timeout) + { + if(timeout === this._timeout) + { + return this; + } + else + { + return new EndpointI(this._instance, this._secure, this._host, this._port, timeout, this._connectionId, this._compress, this._resource); + } + }, + // + // Return a new endpoint with a different connection id. + // + changeConnectionId: function(connectionId) + { + if(connectionId === this._connectionId) + { + return this; + } + else + { + return new EndpointI(this._instance, this._secure, this._host, this._port, this._timeout, connectionId, this._compress, this._resource); + } + }, + // + // Return true if the endpoints support bzip2 compress, or false + // otherwise. + // + compress: function() + { + return this._compress; + }, + // + // Return a new endpoint with a different compression value, + // provided that compression is supported by the + // endpoint. Otherwise the same endpoint is returned. + // + changeCompress: function(compress) + { + if(compress === this._compress) + { + return this; + } + else + { + return new EndpointI(this._instance, this._secure, this._host, this._port, this._timeout, this._connectionId, compress, this._resource); + } + }, + // + // Return true if the endpoint is datagram-based. + // + datagram: function() + { + return false; + }, + // + // Return true if the endpoint is secure. + // + secure: function() + { + return this._secure; + }, + // + // Return a server side transceiver for this endpoint, or null if a + // transceiver can only be created by an acceptor. In case a + // transceiver is created, this operation also returns a new + // "effective" endpoint, which might differ from this endpoint, + // for example, if a dynamic port number is assigned. + // + transceiver: function(endpoint) + { + return null; + }, + // + // Return an acceptor for this endpoint, or null if no acceptors + // is available. In case an acceptor is created, this operation + // also returns a new "effective" endpoint, which might differ + // from this endpoint, for example, if a dynamic port number is + // assigned. + // + acceptor: function(endpoint, adapterName) + { + return null; + }, + connect: function() + { + if(this._instance.traceLevels().network >= 2) + { + this._instance.initializationData().logger.trace(this._instance.traceLevels().networkCat, + "trying to establish " + (this._secure ? "wss" : "ws") + " connection to " + this._host + ":" + + this._port); + } + + return Transceiver.createOutgoing(this._instance, this._secure, new Address(this._host, this._port), + this._resource); + }, + hashCode: function() + { + return this._hashCode; + }, + // + // Compare endpoints for sorting purposes + // + equals: function(p) + { + if(!(p instanceof EndpointI)) + { + return false; + } + + if(this === p) + { + return true; + } + + if(this._host !== p._host) + { + return false; + } + + if(this._port !== p._port) + { + return false; + } + + if(this._timeout !== p._timeout) + { + return false; + } + + if(this._connectionId !== p._connectionId) + { + return false; + } + + if(this._compress !== p._compress) + { + return false; + } + + if(this._resource !== p._resource) + { + return false; + } + + return true; + }, + compareTo: function(p) + { + if(this === p) + { + return 0; + } + + if(p === null) + { + return 1; + } + + if(!(p instanceof EndpointI)) + { + return this.type() < p.type() ? -1 : 1; + } + + if(this._port < p._port) + { + return -1; + } + else if(p._port < this._port) + { + return 1; + } + + if(this._timeout < p._timeout) + { + return -1; + } + else if(p._timeout < this._timeout) + { + return 1; + } + + if(this._connectionId != p._connectionId) + { + return this._connectionId < p._connectionId ? -1 : 1; + } + + if(!this._compress && p._compress) + { + return -1; + } + else if(!p._compress && this._compress) + { + return 1; + } + + if(this._host < p._host) + { + return -1; + } + else if(p._host < this._host) + { + return 1; + } + + if(this._resource == p._resource) + { + return 0; + } + else + { + return this._resource < p._resource ? -1 : 1; + } + }, + calcHashValue: function() + { + var h = 5381; + h = HashUtil.addNumber(h, this._secure ? IceWS.WSSEndpointType : IceWS.WSEndpointType); + h = HashUtil.addString(h, this._host); + h = HashUtil.addNumber(h, this._port); + h = HashUtil.addNumber(h, this._timeout); + h = HashUtil.addString(h, this._connectionId); + h = HashUtil.addBoolean(h, this._compress); + h = HashUtil.addString(h, this._resource); + this._hashCode = h; + } + }); + + EndpointI.fromString = function(instance, secure, str, oaEndpoint) + { + var host = null; + var port = 0; + var timeout = -1; + var compress = false; + var resource = ""; + + var protocol = secure ? "wss" : "ws"; + + var arr = str.split(/[ \t\n\r]+/); + + var i = 0; + while(i < arr.length) + { + if(arr[i].length === 0) + { + i++; + continue; + } + + var option = arr[i++]; + if(option.length != 2 && option.charAt(0) != '-') + { + throw new Ice.EndpointParseException("expected an endpoint option but found `" + option + + "' in endpoint `" + protocol + " " + str + "'"); + } + + var argument = null; + if(i < arr.length && arr[i].charAt(0) != '-') + { + argument = arr[i++]; + if(argument.charAt(0) == '\"' && argument.charAt(argument.length - 1) == '\"') + { + argument = argument.substring(1, argument.length - 1); + } + } + + switch(option.charAt(1)) + { + case 'h': + { + if(argument === null) + { + throw new Ice.EndpointParseException("no argument provided for -h option in endpoint `" + + protocol + " " + str + "'"); + } + + host = argument; + break; + } + + case 'p': + { + if(argument === null) + { + throw new Ice.EndpointParseException("no argument provided for -p option in endpoint `" + + protocol + " " + str + "'"); + } + + try + { + port = StringUtil.toInt(argument); + } + catch(ex) + { + throw new Ice.EndpointParseException("invalid port value `" + argument + + "' in endpoint `" + protocol + " " + str + "'"); + } + + if(port < 0 || port > 65535) + { + throw new Ice.EndpointParseException("port value `" + argument + + "' out of range in endpoint `" + protocol + " " + str + + "'"); + } + + break; + } + + case 'r': + { + if(argument === null) + { + throw new Ice.EndpointParseException("no argument provided for -r option in endpoint `" + + protocol + " " + str + "'"); + } + + resource = argument; + break; + } + + case 't': + { + if(argument === null) + { + throw new Ice.EndpointParseException("no argument provided for -t option in endpoint `" + + protocol + " " + str + "'"); + } + + try + { + timeout = StringUtil.toInt(argument); + } + catch(ex) + { + throw new Ice.EndpointParseException("invalid timeout value `" + argument + + "' in endpoint `" + protocol + " " + str + "'"); + } + + break; + } + + case 'z': + { + if(argument !== null) + { + throw new Ice.EndpointParseException("unexpected argument `" + argument + + "' provided for -z option in `" + protocol + " " + str + + "'"); + } + + compress = true; + break; + } + + default: + { + throw new Ice.EndpointParseException("unknown option `" + option + "' in `" + protocol + " " + + str + "'"); + } + } + } + + if(host === null) + { + host = instance.defaultsAndOverrides().defaultHost; + } + else if(host == "*") + { + if(oaEndpoint) + { + host = null; + } + else + { + throw new Ice.EndpointParseException("`-h *' not valid for proxy endpoint `" + protocol + " " + str + + "'"); + } + } + + if(host === null) + { + host = ""; + } + return new EndpointI(instance, secure, host, port, timeout, "", compress, resource); + }; + + EndpointI.fromStream = function(s, secure) + { + s.startReadEncaps(); + var host = s.readString(); + var port = s.readInt(); + var timeout = s.readInt(); + var compress = s.readBool(); + var resource = s.readString(); + s.endReadEncaps(); + return new EndpointI(s.instance, secure, host, port, timeout, "", compress, resource); + }; + + IceWS.EndpointI = EndpointI; + global.IceWS = IceWS; + + var EndpointInfoI = Class(IceWS.EndpointInfo, { + __init__: function(secure, timeout, compress, host, port, resource) + { + IceWS.EndpointInfo.call(this, timeout, compress, host, port, resource); + this.secure = secure; + }, + type: function() + { + return this._secure ? IceWS.WSSEndpointType : IceWS.WSEndpointType; + }, + datagram: function() + { + return false; + }, + secure: function() + { + return this._secure; + } + }); +}(typeof (global) === "undefined" ? window : global)); diff --git a/js/src/Ice/browser/Transceiver.js b/js/src/Ice/browser/Transceiver.js new file mode 100644 index 00000000000..f700dfbfc55 --- /dev/null +++ b/js/src/Ice/browser/Transceiver.js @@ -0,0 +1,418 @@ +// ********************************************************************** +// +// Copyright (c) 2003-2014 ZeroC, Inc. All rights reserved. +// +// This copy of Ice is licensed to you under the terms described in the +// ICE_LICENSE file included in this distribution. +// +// ********************************************************************** + +(function(global){ + require("Ice/Class"); + require("Ice/Debug"); + require("Ice/ExUtil"); + require("Ice/Network"); + require("Ice/SocketOperation"); + require("Ice/Connection"); + require("Ice/Exception"); + require("Ice/LocalException"); + + require("Ice/browser/ConnectionInfo"); + + var Ice = global.Ice || {}; + var IceWS = global.IceWS || {}; + + var Debug = Ice.Debug; + var ExUtil = Ice.ExUtil; + var Network = Ice.Network; + var SocketOperation = Ice.SocketOperation; + var Conn = Ice.Connection; + var LocalException = Ice.LocalException; + var SocketException = Ice.SocketException; + + var StateNeedConnect = 0; + var StateConnectPending = 1; + var StateConnected = 2; + var StateClosePending = 3; + var StateClosed = 4; + + var IsFirefox = navigator.userAgent.indexOf("Firefox") !== -1; + + var Transceiver = Ice.Class({ + __init__: function(instance) + { + this._traceLevels = instance.traceLevels(); + this._logger = instance.initializationData().logger; + this._readBuffers = []; + this._readPosition = 0; + }, + setCallbacks: function(connectedCallback, bytesAvailableCallback, bytesWrittenCallback) + { + this._connectedCallback = connectedCallback; + this._bytesAvailableCallback = bytesAvailableCallback; + this._bytesWrittenCallback = bytesWrittenCallback; + }, + // + // Returns SocketOperation.None when initialization is complete. + // + initialize: function(readBuffer, writeBuffer) + { + try + { + if(this._exception) + { + throw this._exception; + } + + if(this._state === StateNeedConnect) + { + this._state = StateConnectPending; + this._fd = new WebSocket(this._url, "ice.zeroc.com"); + this._fd.binaryType = "arraybuffer"; + var self = this; + this._fd.onopen = function(e) { self.socketConnected(e); }; + this._fd.onmessage = function(e) { self.socketBytesAvailable(e.data); }; + this._fd.onclose = function(e) { self.socketClosed(e); }; + return SocketOperation.Connect; // Waiting for connect to complete. + } + else if(this._state === StateConnectPending) + { + // + // Socket is connected. + // + this._desc = fdToString(this._addr); + this._state = StateConnected; + } + } + catch(err) + { + if(!this._exception) + { + this._exception = translateError(this._state, err); + } + + if(this._traceLevels.network >= 2) + { + var s = []; + s.push("failed to establish " + this.type() + " connection\n"); + s.push(fdToString(this._addr)); + this._logger.trace(this._traceLevels.networkCat, s.join("")); + } + throw this._exception; + } + + Debug.assert(this._state === StateConnected); + if(this._traceLevels.network >= 1) + { + this._logger.trace(this._traceLevels.networkCat, this.type() + + " connection established\n" + this._desc); + } + return SocketOperation.None; + }, + register: function() + { + // + // Register the socket data listener. + // + this._registered = true; + if(this._hasBytesAvailable || this._exception) + { + this._bytesAvailableCallback(); + this._hasBytesAvailable = false; + } + }, + unregister: function() + { + // + // Unregister the socket data listener. + // + this._registered = false; + }, + close: function() + { + if(this._fd === null) + { + Debug.assert(this._exception); // Websocket creation failed. + return; + } + + // + // WORKAROUND: With Firefox, calling close() if the websocket isn't connected + // yet doesn't close the connection. The server doesn't receive any close frame + // and the underlying socket isn't closed causing the server to hang on closing + // the connection until the browser exits. + // + // To workaround this problem, we always wait for the socket to be connected + // or closed before closing the socket. + // + if(this._fd.readyState === WebSocket.CONNECTING && IsFirefox) + { + this._state = StateClosePending; + return; + } + + if(this._state == StateConnected && this._traceLevels.network >= 1) + { + this._logger.trace(this._traceLevels.networkCat, "closing " + this.type() + " connection\n" + + this._desc); + } + + Debug.assert(this._fd !== null); + try + { + this._state = StateClosed; + this._fd.close(); + } + catch(ex) + { + throw translateError(this._state, ex); + } + finally + { + this._fd = null; + } + }, + // + // Returns true if all of the data was flushed to the kernel buffer. + // + write: function(byteBuffer) + { + if(this._exception) + { + throw this._exception; + } + + var remaining = byteBuffer.remaining; + Debug.assert(remaining > 0); + Debug.assert(this._fd); + + // + // Delay the send if more than 1KB is already queued for + // sending on the socket. If less, we consider that it's + // fine to push more data on the socket. + // + if(this._fd.bufferedAmount < 1024) + { + // + // Create a slice of the source buffer representing the remaining data to be written. + // + var slice = byteBuffer.b.slice(byteBuffer.position, byteBuffer.position + remaining); + + // + // The socket will accept all of the data. + // + byteBuffer.position = byteBuffer.position + remaining; + this._fd.send(slice); + if(remaining > 0 && this._traceLevels.network >= 3) + { + var msg = "sent " + remaining + " of " + remaining + " bytes via " + this.type() + "\n" +this._desc; + this._logger.trace(this._traceLevels.networkCat, msg); + } + return true; + } + else + { + var transceiver = this; + var writtenCB = function() + { + if(transceiver._fd) + { + if(transceiver._fd.bufferedAmount === 0 || this._exception) + { + transceiver._bytesWrittenCallback(); + } + else + { + setTimeout(writtenCB, 50); + } + } + }; + setTimeout(writtenCB, 50); + return false; + } + }, + read: function(byteBuffer, moreData) + { + if(this._exception) + { + throw this._exception; + } + + moreData.value = false; + + if(this._readBuffers.length === 0) + { + return false; // No data available. + } + + var avail = this._readBuffers[0].byteLength - this._readPosition; + Debug.assert(avail > 0); + var remaining = byteBuffer.remaining; + + while(byteBuffer.remaining > 0) + { + if(avail > byteBuffer.remaining) + { + avail = byteBuffer.remaining; + } + + new Uint8Array(byteBuffer.b).set(new Uint8Array(this._readBuffers[0], this._readPosition, avail), + byteBuffer.position); + + byteBuffer.position += avail; + this._readPosition += avail; + if(this._readPosition === this._readBuffers[0].byteLength) + { + // + // We've exhausted the current read buffer. + // + this._readPosition = 0; + this._readBuffers.shift(); + if(this._readBuffers.length === 0) + { + break; // No more data - we're done. + } + else + { + avail = this._readBuffers[0].byteLength; + } + } + } + + var n = remaining - byteBuffer.remaining; + if(n > 0 && this._traceLevels.network >= 3) + { + var msg = "received " + n + " of " + remaining + " bytes via " + this.type() + "\n" + this._desc; + this._logger.trace(this._traceLevels.networkCat, msg); + } + + moreData.value = this._readBuffers.byteLength > 0; + + return byteBuffer.remaining === 0; + }, + type: function() + { + return this._secure ? "wss" : "ws"; + }, + getInfo: function() + { + Debug.assert(this._fd !== null); + var info = this.createInfo(); + + // + // The WebSocket API doens't provide this info + // + info.localAddress = ""; + info.localPort = -1; + info.remoteAddress = this._addr.host; + info.remotePort = this._addr.port; + return info; + }, + createInfo: function() + { + return new IceWS.ConnectionInfo(); + }, + checkSendSize: function(stream, messageSizeMax) + { + if(stream.size > messageSizeMax) + { + ExUtil.throwMemoryLimitException(stream.size, messageSizeMax); + } + }, + toString: function() + { + return this._desc; + }, + socketConnected: function(e) + { + if(this._state == StateClosePending) + { + this.close(); + return; + } + + Debug.assert(this._connectedCallback !== null); + this._connectedCallback(); + }, + socketBytesAvailable: function(buf) + { + Debug.assert(this._bytesAvailableCallback !== null); + if(buf.byteLength > 0) + { + this._readBuffers.push(buf); + if(this._registered) + { + this._bytesAvailableCallback(); + } + else if(!this._hasBytesAvailable) + { + this._hasBytesAvailable = true; + } + } + }, + socketClosed: function(err) + { + if(this._state == StateClosePending) + { + this.close(); + return; + } + + this._exception = translateError(this._state, err); + if(this._state < StateConnected) + { + this._connectedCallback(); + } + else if(this._registered) + { + this._bytesAvailableCallback(); + } + }, + }); + + function fdToString(address) + { + return "local address = <not available>\nremote address = " + address.host + ":" + address.port; + } + + function translateError(state, err) + { + if(state < StateConnected) + { + return new Ice.ConnectFailedException(err.code, err); + } + else + { + if(err === 1000) // CLOSE_NORMAL + { + throw new Ice.ConnectionLostException(); + } + return new Ice.SocketException(err.code, err); + } + } + + Transceiver.createOutgoing = function(instance, secure, addr, resource) + { + var transceiver = new Transceiver(instance); + + var url = secure ? "wss" : "ws"; + url += "://" + addr.host; + if(addr.port !== 80) + { + url += ":" + addr.port; + } + url += resource ? resource : "/"; + transceiver._url = url; + transceiver._fd = null; + transceiver._addr = addr; + transceiver._desc = "remote address: " + addr.host + ":" + addr.port + " <not connected>"; + transceiver._state = StateNeedConnect; + transceiver._secure = secure; + transceiver._exception = null; + + return transceiver; + }; + + IceWS.Transceiver = Transceiver; + global.IceWS = IceWS; +}(typeof (global) === "undefined" ? window : global)); |