Subversion Repositories qbpwcf-lib(archive)

Rev

Rev 915 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 liveuser 1
/************************************************************************
2
 *  Copyright 2010-2015 Brian McKelvey.
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 ***********************************************************************/
16
 
17
var WebSocketClient = require('./WebSocketClient');
18
var toBuffer = require('typedarray-to-buffer');
19
var yaeti = require('yaeti');
20
 
21
 
22
const CONNECTING = 0;
23
const OPEN = 1;
24
const CLOSING = 2;
25
const CLOSED = 3;
26
 
27
 
28
module.exports = W3CWebSocket;
29
 
30
 
31
function W3CWebSocket(url, protocols, origin, headers, requestOptions, clientConfig) {
32
    // Make this an EventTarget.
33
    yaeti.EventTarget.call(this);
34
 
35
    // Sanitize clientConfig.
36
    clientConfig = clientConfig || {};
37
    clientConfig.assembleFragments = true;  // Required in the W3C API.
38
 
39
    var self = this;
40
 
41
    this._url = url;
42
    this._readyState = CONNECTING;
43
    this._protocol = undefined;
44
    this._extensions = '';
45
    this._bufferedAmount = 0;  // Hack, always 0.
46
    this._binaryType = 'arraybuffer';  // TODO: Should be 'blob' by default, but Node has no Blob.
47
 
48
    // The WebSocketConnection instance.
49
    this._connection = undefined;
50
 
51
    // WebSocketClient instance.
52
    this._client = new WebSocketClient(clientConfig);
53
 
54
    this._client.on('connect', function(connection) {
55
        onConnect.call(self, connection);
56
    });
57
 
58
    this._client.on('connectFailed', function() {
59
        onConnectFailed.call(self);
60
    });
61
 
62
    this._client.connect(url, protocols, origin, headers, requestOptions);
63
}
64
 
65
 
66
// Expose W3C read only attributes.
67
Object.defineProperties(W3CWebSocket.prototype, {
68
    url:            { get: function() { return this._url;            } },
69
    readyState:     { get: function() { return this._readyState;     } },
70
    protocol:       { get: function() { return this._protocol;       } },
71
    extensions:     { get: function() { return this._extensions;     } },
72
    bufferedAmount: { get: function() { return this._bufferedAmount; } }
73
});
74
 
75
 
76
// Expose W3C write/read attributes.
77
Object.defineProperties(W3CWebSocket.prototype, {
78
    binaryType: {
79
        get: function() {
80
            return this._binaryType;
81
        },
82
        set: function(type) {
83
            // TODO: Just 'arraybuffer' supported.
84
            if (type !== 'arraybuffer') {
85
                throw new SyntaxError('just "arraybuffer" type allowed for "binaryType" attribute');
86
            }
87
            this._binaryType = type;
88
        }
89
    }
90
});
91
 
92
 
93
// Expose W3C readyState constants into the WebSocket instance as W3C states.
94
[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
95
    Object.defineProperty(W3CWebSocket.prototype, property[0], {
96
        get: function() { return property[1]; }
97
    });
98
});
99
 
100
// Also expone W3C readyState constants into the WebSocket class (not defined by the W3C,
101
// but there are so many libs relying on them).
102
[['CONNECTING',CONNECTING], ['OPEN',OPEN], ['CLOSING',CLOSING], ['CLOSED',CLOSED]].forEach(function(property) {
103
    Object.defineProperty(W3CWebSocket, property[0], {
104
        get: function() { return property[1]; }
105
    });
106
});
107
 
108
 
109
W3CWebSocket.prototype.send = function(data) {
110
    if (this._readyState !== OPEN) {
111
        throw new Error('cannot call send() while not connected');
112
    }
113
 
114
    // Text.
115
    if (typeof data === 'string' || data instanceof String) {
116
        this._connection.sendUTF(data);
117
    }
118
    // Binary.
119
    else {
120
        // Node Buffer.
121
        if (data instanceof Buffer) {
122
            this._connection.sendBytes(data);
123
        }
124
        // If ArrayBuffer or ArrayBufferView convert it to Node Buffer.
125
        else if (data.byteLength || data.byteLength === 0) {
126
            data = toBuffer(data);
127
            this._connection.sendBytes(data);
128
        }
129
        else {
130
            throw new Error('unknown binary data:', data);
131
        }
132
    }
133
};
134
 
135
 
136
W3CWebSocket.prototype.close = function(code, reason) {
137
    switch(this._readyState) {
138
        case CONNECTING:
139
            // NOTE: We don't have the WebSocketConnection instance yet so no
140
            // way to close the TCP connection.
141
            // Artificially invoke the onConnectFailed event.
142
            onConnectFailed.call(this);
143
            // And close if it connects after a while.
144
            this._client.on('connect', function(connection) {
145
                if (code) {
146
                    connection.close(code, reason);
147
                } else {
148
                    connection.close();
149
                }
150
            });
151
            break;
152
        case OPEN:
153
            this._readyState = CLOSING;
154
            if (code) {
155
                this._connection.close(code, reason);
156
            } else {
157
                this._connection.close();
158
            }
159
            break;
160
        case CLOSING:
161
        case CLOSED:
162
            break;
163
    }
164
};
165
 
166
 
167
/**
168
 * Private API.
169
 */
170
 
171
 
172
function createCloseEvent(code, reason) {
173
    var event = new yaeti.Event('close');
174
 
175
    event.code = code;
176
    event.reason = reason;
177
    event.wasClean = (typeof code === 'undefined' || code === 1000);
178
 
179
    return event;
180
}
181
 
182
 
183
function createMessageEvent(data) {
184
    var event = new yaeti.Event('message');
185
 
186
    event.data = data;
187
 
188
    return event;
189
}
190
 
191
 
192
function onConnect(connection) {
193
    var self = this;
194
 
195
    this._readyState = OPEN;
196
    this._connection = connection;
197
    this._protocol = connection.protocol;
198
    this._extensions = connection.extensions;
199
 
200
    this._connection.on('close', function(code, reason) {
201
        onClose.call(self, code, reason);
202
    });
203
 
204
    this._connection.on('message', function(msg) {
205
        onMessage.call(self, msg);
206
    });
207
 
208
    this.dispatchEvent(new yaeti.Event('open'));
209
}
210
 
211
 
212
function onConnectFailed() {
213
    destroy.call(this);
214
    this._readyState = CLOSED;
215
 
216
    try {
217
        this.dispatchEvent(new yaeti.Event('error'));
218
    } finally {
219
        this.dispatchEvent(createCloseEvent(1006, 'connection failed'));
220
    }
221
}
222
 
223
 
224
function onClose(code, reason) {
225
    destroy.call(this);
226
    this._readyState = CLOSED;
227
 
228
    this.dispatchEvent(createCloseEvent(code, reason || ''));
229
}
230
 
231
 
232
function onMessage(message) {
233
    if (message.utf8Data) {
234
        this.dispatchEvent(createMessageEvent(message.utf8Data));
235
    }
236
    else if (message.binaryData) {
237
        // Must convert from Node Buffer to ArrayBuffer.
238
        // TODO: or to a Blob (which does not exist in Node!).
239
        if (this.binaryType === 'arraybuffer') {
240
            var buffer = message.binaryData;
241
            var arraybuffer = new ArrayBuffer(buffer.length);
242
            var view = new Uint8Array(arraybuffer);
243
            for (var i=0, len=buffer.length; i<len; ++i) {
244
                view[i] = buffer[i];
245
            }
246
            this.dispatchEvent(createMessageEvent(arraybuffer));
247
        }
248
    }
249
}
250
 
251
 
252
function destroy() {
253
    this._client.removeAllListeners();
254
    if (this._connection) {
255
        this._connection.removeAllListeners();
256
    }
257
}