@sawa-siemens/socket.io-stream 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ v20.19.4
package/.travis.yml ADDED
@@ -0,0 +1,28 @@
1
+ language: node_js
2
+ install:
3
+ - npm install
4
+ - make install
5
+ node_js:
6
+ - 0.10
7
+ - 0.12
8
+ - 4.2
9
+ sudo: false
10
+ env:
11
+ global:
12
+ - secure: BOJOcmv3/8m1e2W7c4MStir5VraXxCeXlslW1xg926FUFry4kayWJhG9FD9GzWopVpeERAHPvr63pou7ylp7m2lkt1zUnacicKdTtXrdSlq2518LasnGJSxfFB59JOTdmdHn/gBAZKrhLTtsYw+mU6AanctXE/NyCVsSZXAig1Y=
13
+ - secure: JoPDfwbrYEKY96XjVc59SSZ/cJ7ll2Vj2vmXqVk0yIwQiJdoXEHg3vwgZRPyCztwXlkjyQsLSLuJN1LSA/b04V8UdreAIILIbSazpQFDjH2ize18v6EVtyvP1PbQoicOsbLON8GdZGfP4kCJ4VNdr8JWLlpHS2Ef/Y0eCIQOxN0=
14
+ matrix:
15
+ - SOCKETIO_VERSION=
16
+ - SOCKETIO_VERSION=0.9
17
+ matrix:
18
+ include:
19
+ - node_js: 0.12
20
+ env: BROWSER_NAME=chrome BROWSER_VERSION=latest
21
+ - node_js: 0.12
22
+ env: BROWSER_NAME=safari BROWSER_VERSION=latest
23
+ - node_js: 0.12
24
+ env: BROWSER_NAME=ie BROWSER_VERSION=latest
25
+ - node_js: 0.12
26
+ env: BROWSER_NAME=iphone BROWSER_VERSION=9.0
27
+ - node_js: 0.12
28
+ env: BROWSER_NAME=android BROWSER_VERSION=5.1
package/.zuul.yml ADDED
@@ -0,0 +1,6 @@
1
+ ui: mocha-bdd
2
+ server: ./test/support/server.js
3
+ tunnel:
4
+ type: ngrok
5
+ authtoken: XP5uKk4Sm6C5XfdTZozb
6
+ proto: tcp
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Naoyuki Kanezawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # Socket.IO stream
2
+
3
+ [![Build Status](https://travis-ci.org/nkzawa/socket.io-stream.png?branch=master)](https://travis-ci.org/nkzawa/socket.io-stream)
4
+ [![NPM version](https://badge.fury.io/js/socket.io-stream.png)](http://badge.fury.io/js/socket.io-stream)
5
+
6
+ This is the module for bidirectional binary data transfer with Stream API through [Socket.IO](https://github.com/socketio/socket.io).
7
+
8
+ ## Installation
9
+
10
+ npm install socket.io-stream
11
+
12
+ ## Usage
13
+
14
+ If you are not familiar with Stream API, be sure to check out [the docs](http://nodejs.org/api/stream.html).
15
+ I also recommend checking out the awesome [Stream Handbook](https://github.com/substack/stream-handbook).
16
+
17
+ For streaming between server and client, you will send stream instances first.
18
+ To receive streams, you just wrap `socket` with `socket.io-stream`, then listen any events as usual.
19
+
20
+ Server:
21
+
22
+ ```js
23
+ var io = require('socket.io').listen(80);
24
+ var ss = require('socket.io-stream');
25
+ var path = require('path');
26
+
27
+ io.of('/user').on('connection', function(socket) {
28
+ ss(socket).on('profile-image', function(stream, data) {
29
+ var filename = path.basename(data.name);
30
+ stream.pipe(fs.createWriteStream(filename));
31
+ });
32
+ });
33
+ ```
34
+
35
+ `createStream()` returns a new stream which can be sent by `emit()`.
36
+
37
+ Client:
38
+
39
+ ```js
40
+ var io = require('socket.io-client');
41
+ var ss = require('socket.io-stream');
42
+
43
+ var socket = io.connect('http://example.com/user');
44
+ var stream = ss.createStream();
45
+ var filename = 'profile.jpg';
46
+
47
+ ss(socket).emit('profile-image', stream, {name: filename});
48
+ fs.createReadStream(filename).pipe(stream);
49
+ ```
50
+
51
+ You can stream data from a client to server, and vice versa.
52
+
53
+ ```js
54
+ // send data
55
+ ss(socket).on('file', function(stream) {
56
+ fs.createReadStream('/path/to/file').pipe(stream);
57
+ });
58
+
59
+ // receive data
60
+ ss(socket).emit('file', stream);
61
+ stream.pipe(fs.createWriteStream('file.txt'));
62
+ ```
63
+
64
+ ### Browser
65
+
66
+ This module can be used on the browser. To do so, just copy a file to a public directory.
67
+
68
+ $ cp node_modules/socket.io-stream/socket.io-stream.js somewhere/public/
69
+
70
+ You can also use [browserify](http://github.com/substack/node-browserify) to create your own bundle.
71
+
72
+ $ npm install browserify -g
73
+ $ cd node_modules/socket.io-stream
74
+ $ browserify index.js -s ss > socket.io-stream.js
75
+
76
+ ```html
77
+ <input id="file" type="file" />
78
+
79
+ <script src="/socket.io/socket.io.js"></script>
80
+ <script src="/js/socket.io-stream.js"></script>
81
+ <script src="/js/jquery.js"></script>
82
+ <script>
83
+ $(function() {
84
+ var socket = io.connect('/foo');
85
+
86
+ $('#file').change(function(e) {
87
+ var file = e.target.files[0];
88
+ var stream = ss.createStream();
89
+
90
+ // upload a file to the server.
91
+ ss(socket).emit('file', stream, {size: file.size});
92
+ ss.createBlobReadStream(file).pipe(stream);
93
+ });
94
+ });
95
+ </script>
96
+ ```
97
+
98
+ #### Upload progress
99
+
100
+ You can track upload progress like the following:
101
+
102
+ ```js
103
+ var blobStream = ss.createBlobReadStream(file);
104
+ var size = 0;
105
+
106
+ blobStream.on('data', function(chunk) {
107
+ size += chunk.length;
108
+ console.log(Math.floor(size / file.size * 100) + '%');
109
+ // -> e.g. '42%'
110
+ });
111
+
112
+ blobStream.pipe(stream);
113
+ ```
114
+
115
+ ### Socket.IO v0.9 support
116
+
117
+ You have to set `forceBase64` option `true` when using the library with socket.io v0.9.x.
118
+
119
+ ```js
120
+ ss.forceBase64 = true;
121
+ ```
122
+
123
+
124
+ ## Documentation
125
+
126
+ ### ss(sio)
127
+
128
+ - sio `socket.io Socket` A socket of Socket.IO, both for client and server
129
+ - return `Socket`
130
+
131
+ Look up an existing `Socket` instance based on `sio` (a socket of Socket.IO), or create one if it doesn't exist.
132
+
133
+ ### socket.emit(event, [arg1], [arg2], [...])
134
+
135
+ - event `String` The event name
136
+
137
+ Emit an `event` with variable number of arguments including at least a stream.
138
+
139
+ ```js
140
+ ss(socket).emit('myevent', stream, {name: 'thefilename'}, function() { ... });
141
+
142
+ // send some streams at a time.
143
+ ss(socket).emit('multiple-streams', stream1, stream2);
144
+
145
+ // as members of array or object.
146
+ ss(socket).emit('flexible', [stream1, { foo: stream2 }]);
147
+
148
+ // get streams through the ack callback
149
+ ss(socket).emit('ack', function(stream1, stream2) { ... });
150
+ ```
151
+
152
+ ### socket.on(event, listener)
153
+
154
+ - event `String` The event name
155
+ - listener `Function` The event handler function
156
+
157
+ Add a `listener` for `event`. `listener` will take stream(s) with any data as arguments.
158
+
159
+ ```js
160
+ ss(socket).on('myevent', function(stream, data, callback) { ... });
161
+
162
+ // access stream options
163
+ ss(socket).on('foo', function(stream) {
164
+ if (stream.options && stream.options.highWaterMark > 1024) {
165
+ console.error('Too big highWaterMark.');
166
+ return;
167
+ }
168
+ });
169
+ ```
170
+
171
+ ### ss.createStream([options])
172
+
173
+ - options `Object`
174
+ - highWaterMark `Number`
175
+ - encoding `String`
176
+ - decodeStrings `Boolean`
177
+ - objectMode `Boolean`
178
+ - allowHalfOpen `Boolean` if `true`, then the stream won't automatically close when the other endpoint ends. Default to `false`.
179
+ - return `Duplex Stream`
180
+
181
+ Create a new duplex stream. See [the docs](http://nodejs.org/api/stream.html) for the details of stream and `options`.
182
+
183
+ ```js
184
+ var stream = ss.createStream();
185
+
186
+ // with options
187
+ var stream = ss.createStream({
188
+ highWaterMark: 1024,
189
+ objectMode: true,
190
+ allowHalfOpen: true
191
+ });
192
+ ```
193
+
194
+ ### ss.createBlobReadStream(blob, [options])
195
+
196
+ - options `Object`
197
+ - highWaterMark `Number`
198
+ - encoding `String`
199
+ - objectMode `Boolean`
200
+ - return `Readable Stream`
201
+
202
+ Create a new readable stream for [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [File](https://developer.mozilla.org/en-US/docs/Web/API/File) on browser. See [the docs](http://nodejs.org/api/stream.html) for the details of stream and `options`.
203
+
204
+ ```js
205
+ var stream = ss.createBlobReadStream(new Blob([1, 2, 3]));
206
+ ```
207
+
208
+ ### ss.Buffer
209
+
210
+ [Node Buffer](https://nodejs.org/api/buffer.html) class to use on browser, which is exposed for convenience. On Node environment, you should just use normal `Buffer`.
211
+
212
+ ```js
213
+ var stream = ss.createStream();
214
+ stream.write(new ss.Buffer([0, 1, 2]));
215
+ ```
216
+
217
+ ## License
218
+
219
+ MIT
package/index.d.ts ADDED
@@ -0,0 +1,236 @@
1
+ /// <reference types="node" />
2
+
3
+ import { EventEmitter } from 'events';
4
+ import { Duplex, Readable, DuplexOptions } from 'stream';
5
+
6
+ /**
7
+ * Socket.IO socket interface (compatible with both server and client sockets)
8
+ */
9
+ export interface SocketIOSocketLike {
10
+ on(event: string, callback: (...args: any[]) => void): this;
11
+ emit(event: string, ...args: any[]): this;
12
+ _streamSocket?: any; // Socket instance (using any to avoid circular reference)
13
+ }
14
+
15
+ /**
16
+ * Options for creating a Socket instance
17
+ */
18
+ export interface SocketOptions {
19
+ /**
20
+ * Forces base 64 encoding when emitting. Must be set to true for Socket.IO v0.9 or lower.
21
+ * @default false
22
+ */
23
+ forceBase64?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Options for creating an IOStream
28
+ */
29
+ export interface IOStreamOptions extends DuplexOptions {
30
+ /**
31
+ * Allow half-open sockets. If set to false, then the stream will automatically
32
+ * end the writable side when the readable side ends.
33
+ * @default false
34
+ */
35
+ allowHalfOpen?: boolean;
36
+ }
37
+
38
+ /**
39
+ * Options for creating a BlobReadStream
40
+ */
41
+ export interface BlobReadStreamOptions {
42
+ /**
43
+ * Use synchronous FileReader API
44
+ * @default false
45
+ */
46
+ synchronous?: boolean;
47
+ highWaterMark?: number;
48
+ }
49
+
50
+ /**
51
+ * Bidirectional stream socket which wraps Socket.IO.
52
+ * Extends EventEmitter and provides stream-based communication over Socket.IO.
53
+ */
54
+ export class Socket extends EventEmitter {
55
+ /**
56
+ * Base event name for messaging.
57
+ */
58
+ static readonly event: string;
59
+
60
+ /**
61
+ * List of events that are handled directly by EventEmitter.
62
+ */
63
+ static readonly events: string[];
64
+
65
+ /**
66
+ * The underlying Socket.IO socket instance.
67
+ */
68
+ readonly sio: SocketIOSocketLike;
69
+
70
+ /**
71
+ * Whether to force base64 encoding.
72
+ */
73
+ readonly forceBase64: boolean;
74
+
75
+ /**
76
+ * Creates a new Socket instance.
77
+ * @param sio - Socket.IO socket instance (server or client)
78
+ * @param options - Socket options
79
+ */
80
+ constructor(sio: SocketIOSocketLike, options?: SocketOptions);
81
+
82
+ /**
83
+ * Emits streams to the corresponding server/client.
84
+ * @param type - Event type
85
+ * @param args - Additional arguments, which may include IOStream instances
86
+ * @returns This Socket instance for chaining
87
+ */
88
+ emit(type: string, ...args: any[]): this;
89
+
90
+ /**
91
+ * Listens for stream events.
92
+ * @param type - Event type
93
+ * @param listener - Event listener function that receives streams and other arguments
94
+ * @returns This Socket instance for chaining
95
+ */
96
+ on(type: string, listener: (...args: any[]) => void): this;
97
+
98
+ /**
99
+ * Listens for stream events (one-time).
100
+ * @param type - Event type
101
+ * @param listener - Event listener function that receives streams and other arguments
102
+ * @returns This Socket instance for chaining
103
+ */
104
+ once(type: string, listener: (...args: any[]) => void): this;
105
+
106
+ /**
107
+ * Removes a listener for stream events.
108
+ * @param type - Event type
109
+ * @param listener - Event listener function
110
+ * @returns This Socket instance for chaining
111
+ */
112
+ removeListener(type: string, listener: (...args: any[]) => void): this;
113
+
114
+ /**
115
+ * Removes all listeners for a specific event type.
116
+ * @param type - Event type
117
+ * @returns This Socket instance for chaining
118
+ */
119
+ removeAllListeners(type?: string): this;
120
+
121
+ /**
122
+ * Cleans up a stream by its ID.
123
+ * @param id - Stream ID
124
+ */
125
+ cleanup(id: string): void;
126
+ }
127
+
128
+ /**
129
+ * Duplex stream for Socket.IO communication.
130
+ * Extends Node.js Duplex stream and can be used for bidirectional data transfer.
131
+ */
132
+ export class IOStream extends Duplex {
133
+ /**
134
+ * Unique identifier for this stream.
135
+ */
136
+ readonly id: string;
137
+
138
+ /**
139
+ * The Socket instance this stream is associated with.
140
+ */
141
+ socket: Socket | null;
142
+
143
+ /**
144
+ * Whether this stream has been destroyed.
145
+ */
146
+ readonly destroyed: boolean;
147
+
148
+ /**
149
+ * Options passed during construction.
150
+ */
151
+ readonly options?: IOStreamOptions;
152
+
153
+ /**
154
+ * Creates a new IOStream instance.
155
+ * @param options - Stream options
156
+ */
157
+ constructor(options?: IOStreamOptions);
158
+
159
+ /**
160
+ * Ensures that no more I/O activity happens on this stream.
161
+ * Not necessary in the usual case.
162
+ */
163
+ destroy(): void;
164
+ }
165
+
166
+ /**
167
+ * Readable stream for Blob and File objects on the browser.
168
+ * Extends Node.js Readable stream.
169
+ */
170
+ export class BlobReadStream extends Readable {
171
+ /**
172
+ * The Blob or File object being read.
173
+ */
174
+ readonly blob: Blob | File;
175
+
176
+ /**
177
+ * Creates a new BlobReadStream instance.
178
+ * @param blob - Blob or File object to read from
179
+ * @param options - Stream options
180
+ */
181
+ constructor(blob: Blob | File, options?: BlobReadStreamOptions);
182
+ }
183
+
184
+ /**
185
+ * Main module function to look up or create a Socket instance.
186
+ * This function also has properties attached to it (Socket, IOStream, etc.)
187
+ */
188
+ declare function lookup(sio: SocketIOSocketLike, options?: SocketOptions): Socket;
189
+
190
+ declare namespace lookup {
191
+ /**
192
+ * Socket constructor class
193
+ */
194
+ const Socket: typeof Socket;
195
+
196
+ /**
197
+ * IOStream constructor class
198
+ */
199
+ const IOStream: typeof IOStream;
200
+
201
+ /**
202
+ * BlobReadStream constructor class
203
+ */
204
+ const BlobReadStream: typeof BlobReadStream;
205
+
206
+ /**
207
+ * Forces base 64 encoding when emitting. Must be set to true for Socket.IO v0.9 or lower.
208
+ * @default false
209
+ */
210
+ let forceBase64: boolean;
211
+
212
+ /**
213
+ * Exposes Node Buffer for browser compatibility.
214
+ */
215
+ const Buffer: typeof global.Buffer;
216
+
217
+ /**
218
+ * Creates a new duplex stream.
219
+ * @param options - Stream options
220
+ * @returns New IOStream instance
221
+ */
222
+ function createStream(options?: IOStreamOptions): IOStream;
223
+
224
+ /**
225
+ * Creates a new readable stream for Blob/File on browser.
226
+ * @param blob - Blob or File object to read from
227
+ * @param options - Stream options
228
+ * @returns New BlobReadStream instance
229
+ */
230
+ function createBlobReadStream(
231
+ blob: Blob | File,
232
+ options?: BlobReadStreamOptions
233
+ ): BlobReadStream;
234
+ }
235
+
236
+ export = lookup;
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+
2
+ module.exports = require('./lib');
3
+
@@ -0,0 +1,67 @@
1
+ var util = require('util');
2
+ var Readable = require('readable-stream').Readable;
3
+ var bind = require('component-bind');
4
+
5
+
6
+ module.exports = BlobReadStream;
7
+
8
+ util.inherits(BlobReadStream, Readable);
9
+
10
+ /**
11
+ * Readable stream for Blob and File on browser.
12
+ *
13
+ * @param {Object} options
14
+ * @api private
15
+ */
16
+ function BlobReadStream(blob, options) {
17
+ if (!(this instanceof BlobReadStream)) {
18
+ return new BlobReadStream(blob, options);
19
+ }
20
+
21
+ Readable.call(this, options);
22
+
23
+ options = options || {};
24
+ this.blob = blob;
25
+ this.slice = blob.slice || blob.webkitSlice || blob.mozSlice;
26
+ this.start = 0;
27
+ this.sync = options.synchronous || false;
28
+
29
+ var fileReader;
30
+
31
+ if (options.synchronous) {
32
+ fileReader = this.fileReader = new FileReaderSync();
33
+ } else {
34
+ fileReader = this.fileReader = new FileReader();
35
+ }
36
+
37
+ fileReader.onload = bind(this, '_onload');
38
+ fileReader.onerror = bind(this, '_onerror');
39
+ }
40
+
41
+ BlobReadStream.prototype._read = function(size) {
42
+ var start = this.start;
43
+ var end = this.start = this.start + size;
44
+ var chunk = this.slice.call(this.blob, start, end);
45
+
46
+ if (chunk.size) {
47
+ if (this.sync) {
48
+ var bufferChunk = new Buffer(new Uint8Array(this.fileReader.readAsArrayBuffer(chunk)));
49
+ this.push(bufferChunk);
50
+ } else {
51
+ this.fileReader.readAsArrayBuffer(chunk);
52
+ }
53
+ } else {
54
+ this.push(null);
55
+ }
56
+ }
57
+
58
+ BlobReadStream.prototype._onload = function(e) {
59
+ var chunk = new Buffer(new Uint8Array(e.target.result));
60
+ this.push(chunk);
61
+ };
62
+
63
+ BlobReadStream.prototype._onerror = function(e) {
64
+ var err = e.target.error;
65
+ this.emit('error', err);
66
+ };
67
+
package/lib/index.js ADDED
@@ -0,0 +1,77 @@
1
+ var Socket = require('./socket');
2
+ var IOStream = require('./iostream');
3
+ var BlobReadStream = require('./blob-read-stream');
4
+
5
+
6
+ exports = module.exports = lookup;
7
+
8
+ /**
9
+ * Expose Node Buffer for browser.
10
+ *
11
+ * @api public
12
+ */
13
+ exports.Buffer = Buffer;
14
+
15
+ /**
16
+ * Expose Socket constructor.
17
+ *
18
+ * @api public
19
+ */
20
+ exports.Socket = Socket;
21
+
22
+ /**
23
+ * Expose IOStream constructor.
24
+ *
25
+ * @api public
26
+ */
27
+ exports.IOStream = IOStream;
28
+
29
+ /**
30
+ * Forces base 64 encoding when emitting. Must be set to true for Socket.IO v0.9 or lower.
31
+ *
32
+ * @api public
33
+ */
34
+ exports.forceBase64 = false;
35
+
36
+ /**
37
+ * Look up an existing Socket.
38
+ *
39
+ * @param {socket.io#Socket} socket.io
40
+ * @param {Object} options
41
+ * @return {Socket} Socket instance
42
+ * @api public
43
+ */
44
+ function lookup(sio, options) {
45
+ options = options || {};
46
+ if (null == options.forceBase64) {
47
+ options.forceBase64 = exports.forceBase64;
48
+ }
49
+
50
+ if (!sio._streamSocket) {
51
+ sio._streamSocket = new Socket(sio, options);
52
+ }
53
+ return sio._streamSocket;
54
+ }
55
+
56
+ /**
57
+ * Creates a new duplex stream.
58
+ *
59
+ * @param {Object} options
60
+ * @return {IOStream} duplex stream
61
+ * @api public
62
+ */
63
+ exports.createStream = function(options) {
64
+ return new IOStream(options);
65
+ };
66
+
67
+ /**
68
+ * Creates a new readable stream for Blob/File on browser.
69
+ *
70
+ * @param {Blob} blob
71
+ * @param {Object} options
72
+ * @return {BlobReadStream} stream
73
+ * @api public
74
+ */
75
+ exports.createBlobReadStream = function(blob, options) {
76
+ return new BlobReadStream(blob, options);
77
+ };
@@ -0,0 +1,265 @@
1
+ var util = require('util');
2
+ var Duplex = require('readable-stream').Duplex;
3
+ var bind = require('component-bind');
4
+ var uuid = require('./uuid');
5
+ var debug = require('debug')('socket.io-stream:iostream');
6
+
7
+
8
+ module.exports = IOStream;
9
+
10
+ util.inherits(IOStream, Duplex);
11
+
12
+ /**
13
+ * Duplex
14
+ *
15
+ * @param {Object} options
16
+ * @api private
17
+ */
18
+ function IOStream(options) {
19
+ if (!(this instanceof IOStream)) {
20
+ return new IOStream(options);
21
+ }
22
+
23
+ IOStream.super_.call(this, options);
24
+
25
+ this.options = options;
26
+ this.id = uuid();
27
+ this.socket = null;
28
+
29
+ // Buffers
30
+ this.pushBuffer = [];
31
+ this.writeBuffer = [];
32
+
33
+ // Op states
34
+ this._readable = false;
35
+ this._writable = false;
36
+ this.destroyed = false;
37
+
38
+ // default to *not* allowing half open sockets
39
+ this.allowHalfOpen = options && options.allowHalfOpen || false;
40
+
41
+ this.on('finish', this._onfinish);
42
+ this.on('end', this._onend);
43
+ this.on('error', this._onerror);
44
+ }
45
+
46
+ /**
47
+ * Ensures that no more I/O activity happens on this stream.
48
+ * Not necessary in the usual case.
49
+ *
50
+ * @api public
51
+ */
52
+ IOStream.prototype.destroy = function() {
53
+ debug('destroy');
54
+
55
+ if (this.destroyed) {
56
+ debug('already destroyed');
57
+ return;
58
+ }
59
+
60
+ this.readable = this.writable = false;
61
+
62
+ if (this.socket) {
63
+ debug('clean up');
64
+ this.socket.cleanup(this.id);
65
+ this.socket = null;
66
+ }
67
+
68
+ this.destroyed = true;
69
+ };
70
+
71
+ /**
72
+ * Local read
73
+ *
74
+ * @api private
75
+ */
76
+ IOStream.prototype._read = function(size) {
77
+ var push;
78
+
79
+ // We can not read from the socket if it's destroyed obviously ...
80
+ if (this.destroyed) return;
81
+
82
+ if (this.pushBuffer.length) {
83
+ // flush buffer and end if it exists.
84
+ while (push = this.pushBuffer.shift()) {
85
+ if (!push()) break;
86
+ }
87
+ return;
88
+ }
89
+
90
+ this._readable = true;
91
+
92
+ // Go get data from remote stream
93
+ // Calls
94
+ // ._onread remotely
95
+ // then
96
+ // ._onwrite locally
97
+ this.socket._read(this.id, size);
98
+ };
99
+
100
+
101
+ /**
102
+ * Read from remote stream
103
+ *
104
+ * @api private
105
+ */
106
+ IOStream.prototype._onread = function(size) {
107
+ var write = this.writeBuffer.shift();
108
+ if (write) return write();
109
+
110
+ this._writable = true;
111
+ };
112
+
113
+ /**
114
+ * Write local data to remote stream
115
+ * Calls
116
+ * remtote ._onwrite
117
+ *
118
+ * @api private
119
+ */
120
+ IOStream.prototype._write = function(chunk, encoding, callback) {
121
+ var self = this;
122
+
123
+ function write() {
124
+ // We can not write to the socket if it's destroyed obviously ...
125
+ if (self.destroyed) return;
126
+
127
+ self._writable = false;
128
+ self.socket._write(self.id, chunk, encoding, callback);
129
+ }
130
+
131
+ if (this._writable) {
132
+ write();
133
+ } else {
134
+ this.writeBuffer.push(write);
135
+ }
136
+ };
137
+
138
+ /**
139
+ * Write the data fetched remotely
140
+ * so that we can now read locally
141
+ *
142
+ * @api private
143
+ */
144
+ IOStream.prototype._onwrite = function(chunk, encoding, callback) {
145
+ var self = this;
146
+
147
+ function push() {
148
+ self._readable = false;
149
+ var ret = self.push(chunk || '', encoding);
150
+ callback();
151
+ return ret;
152
+ }
153
+
154
+ if (this._readable) {
155
+ push();
156
+ } else {
157
+ this.pushBuffer.push(push);
158
+ }
159
+ };
160
+
161
+ /**
162
+ * When ending send 'end' event to remote stream
163
+ *
164
+ * @api private
165
+ */
166
+ IOStream.prototype._end = function() {
167
+ if (this.pushBuffer.length) {
168
+ // end after flushing buffer.
169
+ this.pushBuffer.push(bind(this, '_done'));
170
+ } else {
171
+ this._done();
172
+ }
173
+ };
174
+
175
+ /**
176
+ * Remote stream just ended
177
+ *
178
+ * @api private
179
+ */
180
+ IOStream.prototype._done = function() {
181
+ this._readable = false;
182
+
183
+ // signal the end of the data.
184
+ return this.push(null);
185
+ };
186
+
187
+ /**
188
+ * the user has called .end(), and all the bytes have been
189
+ * sent out to the other side.
190
+ * If allowHalfOpen is false, or if the readable side has
191
+ * ended already, then destroy.
192
+ * If allowHalfOpen is true, then we need to set writable false,
193
+ * so that only the writable side will be cleaned up.
194
+ *
195
+ * @api private
196
+ */
197
+ IOStream.prototype._onfinish = function() {
198
+ debug('_onfinish');
199
+ // Local socket just finished
200
+ // send 'end' event to remote
201
+ if (this.socket) {
202
+ this.socket._end(this.id);
203
+ }
204
+
205
+ this.writable = false;
206
+ this._writableState.ended = true;
207
+
208
+ if (!this.readable || this._readableState.ended) {
209
+ debug('_onfinish: ended, destroy %s', this._readableState);
210
+ return this.destroy();
211
+ }
212
+
213
+ debug('_onfinish: not ended');
214
+
215
+ if (!this.allowHalfOpen) {
216
+ this.push(null);
217
+
218
+ // just in case we're waiting for an EOF.
219
+ if (this.readable && !this._readableState.endEmitted) {
220
+ this.read(0);
221
+ }
222
+ }
223
+ };
224
+
225
+ /**
226
+ * the EOF has been received, and no more bytes are coming.
227
+ * if the writable side has ended already, then clean everything
228
+ * up.
229
+ *
230
+ * @api private
231
+ */
232
+ IOStream.prototype._onend = function() {
233
+ debug('_onend');
234
+ this.readable = false;
235
+ this._readableState.ended = true;
236
+
237
+ if (!this.writable || this._writableState.finished) {
238
+ debug('_onend: %s', this._writableState);
239
+ return this.destroy();
240
+ }
241
+
242
+ debug('_onend: not finished');
243
+
244
+ if (!this.allowHalfOpen) {
245
+ this.end();
246
+ }
247
+ };
248
+
249
+ /**
250
+ * When error in local stream
251
+ * notyify remote
252
+ * if err.remote = true
253
+ * then error happened on remote stream
254
+ *
255
+ * @api private
256
+ */
257
+ IOStream.prototype._onerror = function(err) {
258
+ // check if the error came from remote stream.
259
+ if (!err.remote && this.socket) {
260
+ // notify the error to the corresponding remote stream.
261
+ this.socket._error(this.id, err);
262
+ }
263
+
264
+ this.destroy();
265
+ };
package/lib/parser.js ADDED
@@ -0,0 +1,105 @@
1
+ var util = require('util');
2
+ var EventEmitter = require('events').EventEmitter;
3
+ var IOStream = require('./iostream');
4
+ var slice = Array.prototype.slice;
5
+
6
+ exports.Encoder = Encoder;
7
+ exports.Decoder = Decoder;
8
+
9
+ util.inherits(Encoder, EventEmitter);
10
+
11
+ function Encoder() {
12
+ EventEmitter.call(this);
13
+ }
14
+
15
+ /**
16
+ * Encode streams to placeholder objects.
17
+ *
18
+ * @api public
19
+ */
20
+ Encoder.prototype.encode = function(v) {
21
+ if (v instanceof IOStream) {
22
+ return this.encodeStream(v);
23
+ } else if (util.isArray(v)) {
24
+ return this.encodeArray(v);
25
+ } else if (v && 'object' == typeof v) {
26
+ return this.encodeObject(v);
27
+ }
28
+ return v;
29
+ }
30
+
31
+ Encoder.prototype.encodeStream = function(stream) {
32
+ this.emit('stream', stream);
33
+
34
+ // represent a stream in an object.
35
+ var v = { $stream: stream.id };
36
+ if (stream.options) {
37
+ v.options = stream.options;
38
+ }
39
+ return v;
40
+ }
41
+
42
+ Encoder.prototype.encodeArray = function(arr) {
43
+ var v = [];
44
+ for (var i = 0, len = arr.length; i < len; i++) {
45
+ v.push(this.encode(arr[i]));
46
+ }
47
+ return v;
48
+ }
49
+
50
+ Encoder.prototype.encodeObject = function(obj) {
51
+ var v = {};
52
+ for (var k in obj) {
53
+ if (obj.hasOwnProperty(k)) {
54
+ v[k] = this.encode(obj[k]);
55
+ }
56
+ }
57
+ return v;
58
+ }
59
+
60
+ util.inherits(Decoder, EventEmitter);
61
+
62
+ function Decoder() {
63
+ EventEmitter.call(this);
64
+ }
65
+
66
+ /**
67
+ * Decode placeholder objects to streams.
68
+ *
69
+ * @api public
70
+ */
71
+ Decoder.prototype.decode = function(v) {
72
+ if (v && v.$stream) {
73
+ return this.decodeStream(v);
74
+ } else if (util.isArray(v)) {
75
+ return this.decodeArray(v);
76
+ } else if (v && 'object' == typeof v) {
77
+ return this.decodeObject(v);
78
+ }
79
+ return v;
80
+ }
81
+
82
+ Decoder.prototype.decodeStream = function(obj) {
83
+ var stream = new IOStream(obj.options);
84
+ stream.id = obj.$stream;
85
+ this.emit('stream', stream);
86
+ return stream;
87
+ }
88
+
89
+ Decoder.prototype.decodeArray = function(arr) {
90
+ var v = [];
91
+ for (var i = 0, len = arr.length; i < len; i++) {
92
+ v.push(this.decode(arr[i]));
93
+ }
94
+ return v;
95
+ }
96
+
97
+ Decoder.prototype.decodeObject = function(obj) {
98
+ var v = {};
99
+ for (var k in obj) {
100
+ if (obj.hasOwnProperty(k)) {
101
+ v[k] = this.decode(obj[k]);
102
+ }
103
+ }
104
+ return v;
105
+ }
package/lib/socket.js ADDED
@@ -0,0 +1,287 @@
1
+ var util = require('util');
2
+ var EventEmitter = require('events').EventEmitter;
3
+ var bind = require('component-bind');
4
+ var IOStream = require('./iostream');
5
+ var parser = require('./parser');
6
+ var debug = require('debug')('socket.io-stream:socket');
7
+ var emit = EventEmitter.prototype.emit;
8
+ var on = EventEmitter.prototype.on;
9
+ var slice = Array.prototype.slice;
10
+
11
+
12
+ exports = module.exports = Socket;
13
+
14
+ /**
15
+ * Base event name for messaging.
16
+ *
17
+ * @api public
18
+ */
19
+ exports.event = '$stream';
20
+
21
+ exports.events = [
22
+ 'error',
23
+ 'newListener',
24
+ 'removeListener'
25
+ ];
26
+
27
+ util.inherits(Socket, EventEmitter);
28
+
29
+ /**
30
+ * Bidirectional stream socket which wraps Socket.IO.
31
+ *
32
+ * @param {socket.io#Socket} socket.io
33
+ * @api public
34
+ */
35
+ function Socket(sio, options) {
36
+ if (!(this instanceof Socket)) {
37
+ return new Socket(sio, options);
38
+ }
39
+
40
+ EventEmitter.call(this);
41
+
42
+ options = options || {};
43
+
44
+ this.sio = sio;
45
+ this.forceBase64 = !!options.forceBase64;
46
+ this.streams = {};
47
+ this.encoder = new parser.Encoder();
48
+ this.decoder = new parser.Decoder();
49
+
50
+ var eventName = exports.event;
51
+ sio.on(eventName, bind(this, emit));
52
+ sio.on(eventName + '-read', bind(this, '_onread'));
53
+ sio.on(eventName + '-write', bind(this, '_onwrite'));
54
+ sio.on(eventName + '-end', bind(this, '_onend'));
55
+ sio.on(eventName + '-error', bind(this, '_onerror'));
56
+ sio.on('error', bind(this, emit, 'error'));
57
+ sio.on('disconnect', bind(this, '_ondisconnect'));
58
+
59
+ this.encoder.on('stream', bind(this, '_onencode'));
60
+ this.decoder.on('stream', bind(this, '_ondecode'));
61
+ }
62
+
63
+ /**
64
+ * Original emit function.
65
+ *
66
+ * @api private
67
+ */
68
+ Socket.prototype.$emit = emit;
69
+
70
+ /**
71
+ * Emits streams to this corresponding server/client.
72
+ *
73
+ * @return {Socket} self
74
+ * @api public
75
+ */
76
+ Socket.prototype.emit = function(type) {
77
+ if (~exports.events.indexOf(type)) {
78
+ return emit.apply(this, arguments);
79
+ }
80
+ this._stream.apply(this, arguments);
81
+ return this;
82
+ };
83
+
84
+ Socket.prototype.on = function(type, listener) {
85
+ if (~exports.events.indexOf(type)) {
86
+ return on.apply(this, arguments);
87
+ }
88
+
89
+ this._onstream(type, listener);
90
+ return this;
91
+ };
92
+
93
+ /**
94
+ * Sends a new stream request.
95
+ *
96
+ * @param {String} event type
97
+ * @api private
98
+ */
99
+ Socket.prototype._stream = function(type) {
100
+ debug('sending new streams');
101
+
102
+ var self = this;
103
+ var args = slice.call(arguments, 1);
104
+ var ack = args[args.length - 1];
105
+ if ('function' == typeof ack) {
106
+ args[args.length - 1] = function() {
107
+ var args = slice.call(arguments);
108
+ args = self.decoder.decode(args);
109
+ ack.apply(this, args);
110
+ };
111
+ }
112
+
113
+ args = this.encoder.encode(args);
114
+ var sio = this.sio;
115
+ sio.emit.apply(sio, [exports.event, type].concat(args));
116
+ };
117
+
118
+ /**
119
+ * Notifies the read event.
120
+ *
121
+ * @api private
122
+ */
123
+ Socket.prototype._read = function(id, size) {
124
+ this.sio.emit(exports.event + '-read', id, size);
125
+ };
126
+
127
+ /**
128
+ * Requests to write a chunk.
129
+ *
130
+ * @api private
131
+ */
132
+ Socket.prototype._write = function(id, chunk, encoding, callback) {
133
+ if (Buffer.isBuffer(chunk)) {
134
+ if (this.forceBase64) {
135
+ encoding = 'base64';
136
+ chunk = chunk.toString(encoding);
137
+ } else if (!global.Buffer) {
138
+ // socket.io can't handle Buffer when using browserify.
139
+ if (chunk.toArrayBuffer) {
140
+ chunk = chunk.toArrayBuffer();
141
+ } else {
142
+ chunk = chunk.buffer;
143
+ }
144
+ }
145
+ }
146
+ this.sio.emit(exports.event + '-write', id, chunk, encoding, callback);
147
+ };
148
+
149
+ Socket.prototype._end = function(id) {
150
+ this.sio.emit(exports.event + '-end', id);
151
+ };
152
+
153
+ Socket.prototype._error = function(id, err) {
154
+ this.sio.emit(exports.event + '-error', id, err.message || err);
155
+ };
156
+
157
+ /**
158
+ * Handles a new stream request.
159
+ *
160
+ * @param {String} event type
161
+ * @param {Function} listener
162
+ *
163
+ * @api private
164
+ */
165
+ Socket.prototype._onstream = function(type, listener) {
166
+ if ('function' != typeof listener) {
167
+ throw TypeError('listener must be a function');
168
+ }
169
+
170
+ function onstream() {
171
+ debug('new streams');
172
+ var self = this;
173
+ var args = slice.call(arguments);
174
+ var ack = args[args.length - 1];
175
+ if ('function' == typeof ack) {
176
+ args[args.length - 1] = function() {
177
+ var args = slice.call(arguments);
178
+ args = self.encoder.encode(args);
179
+ ack.apply(this, args);
180
+ };
181
+ }
182
+
183
+ args = this.decoder.decode(args);
184
+ listener.apply(this, args);
185
+ }
186
+
187
+ // for removeListener
188
+ onstream.listener = listener;
189
+
190
+ on.call(this, type, onstream);
191
+ };
192
+
193
+ Socket.prototype._onread = function(id, size) {
194
+ debug('read: "%s"', id);
195
+
196
+ var stream = this.streams[id];
197
+ if (stream) {
198
+ stream._onread(size);
199
+ } else {
200
+ debug('ignore invalid stream id');
201
+ }
202
+ };
203
+
204
+ Socket.prototype._onwrite = function(id, chunk, encoding, callback) {
205
+ debug('write: "%s"', id);
206
+
207
+ var stream = this.streams[id];
208
+ if (!stream) {
209
+ callback('invalid stream id: ' + id);
210
+ return;
211
+ }
212
+
213
+ if (global.ArrayBuffer && chunk instanceof ArrayBuffer) {
214
+ // make sure that chunk is a buffer for stream
215
+ chunk = new Buffer(new Uint8Array(chunk));
216
+ }
217
+ stream._onwrite(chunk, encoding, callback);
218
+ };
219
+
220
+ Socket.prototype._onend = function(id) {
221
+ debug('end: "%s"', id);
222
+
223
+ var stream = this.streams[id];
224
+ if (!stream) {
225
+ debug('ignore non-existent stream id: "%s"', id);
226
+ return;
227
+ }
228
+
229
+ stream._end();
230
+ };
231
+
232
+ Socket.prototype._onerror = function(id, message) {
233
+ debug('error: "%s", "%s"', id, message);
234
+
235
+ var stream = this.streams[id];
236
+ if (!stream) {
237
+ debug('invalid stream id: "%s"', id);
238
+ return;
239
+ }
240
+
241
+ var err = new Error(message);
242
+ err.remote = true;
243
+ stream.emit('error', err);
244
+ };
245
+
246
+ Socket.prototype._ondisconnect = function() {
247
+ var stream;
248
+ for (var id in this.streams) {
249
+ stream = this.streams[id];
250
+ stream.destroy();
251
+
252
+ // Close streams when the underlaying
253
+ // socket.io connection is closed (regardless why)
254
+ stream.emit('close');
255
+ stream.emit('error', new Error('Connection aborted'));
256
+ }
257
+ };
258
+
259
+ Socket.prototype._onencode = function(stream) {
260
+ if (stream.socket || stream.destroyed) {
261
+ throw new Error('stream has already been sent.');
262
+ }
263
+
264
+ var id = stream.id;
265
+ if (this.streams[id]) {
266
+ throw new Error('Encoded stream already exists: ' + id);
267
+ }
268
+
269
+ this.streams[id] = stream;
270
+ stream.socket = this;
271
+ };
272
+
273
+ Socket.prototype._ondecode = function(stream) {
274
+ var id = stream.id;
275
+ if (this.streams[id]) {
276
+ this._error(id, new Error('Decoded stream already exists: ' + id));
277
+ return;
278
+ }
279
+
280
+ this.streams[id] = stream;
281
+ stream.socket = this;
282
+ };
283
+
284
+ Socket.prototype.cleanup = function(id) {
285
+ delete this.streams[id];
286
+ };
287
+
package/lib/uuid.js ADDED
@@ -0,0 +1,25 @@
1
+ // UUID function from https://gist.github.com/jed/982883
2
+ // More lightweight than node-uuid
3
+ function b(
4
+ a // placeholder
5
+ ){
6
+ return a // if the placeholder was passed, return
7
+ ? ( // a random number from 0 to 15
8
+ a ^ // unless b is 8,
9
+ Math.random() // in which case
10
+ * 16 // a random number from
11
+ >> a/4 // 8 to 11
12
+ ).toString(16) // in hexadecimal
13
+ : ( // or otherwise a concatenated string:
14
+ [1e7] + // 10000000 +
15
+ -1e3 + // -1000 +
16
+ -4e3 + // -4000 +
17
+ -8e3 + // -80000000 +
18
+ -1e11 // -100000000000,
19
+ ).replace( // replacing
20
+ /[018]/g, // zeroes, ones, and eights with
21
+ b // random hex digits
22
+ )
23
+ }
24
+
25
+ module.exports = b;
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@sawa-siemens/socket.io-stream",
3
+ "version": "0.10.0",
4
+ "description": "stream for socket.io",
5
+ "author": "Naoyuki Kanezawa <naoyuki.kanezawa@gmail.com>",
6
+ "contributors": [
7
+ {
8
+ "name": "Naoyuki Kanezawa",
9
+ "email": "naoyuki.kanezawa@gmail.com"
10
+ },
11
+ {
12
+ "name": "Aaron O'Mullan",
13
+ "email": "aaron.omullan@friendco.de"
14
+ },
15
+ {
16
+ "name": "Sawa",
17
+ "email": "sawa.yeung.ext@siemens.com"
18
+ }
19
+ ],
20
+ "keywords": [
21
+ "stream",
22
+ "socket.io",
23
+ "binary",
24
+ "file",
25
+ "upload",
26
+ "download"
27
+ ],
28
+ "main": "index.js",
29
+ "types": "index.d.ts",
30
+ "browser": "socket.io-stream.js",
31
+ "exports": {
32
+ ".": {
33
+ "node": "./index.js",
34
+ "browser": "./socket.io-stream.js",
35
+ "default": "./index.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "engines": {
40
+ "node": ">=0.10.0"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/sawa-siemens/socket.io-stream.git"
45
+ },
46
+ "scripts": {
47
+ "prepublish": "make build",
48
+ "test": "make test"
49
+ },
50
+ "dependencies": {
51
+ "component-bind": "~1.0.0",
52
+ "debug": "^4.3.4"
53
+ },
54
+ "devDependencies": {
55
+ "browserify": "^17.0.0",
56
+ "expect.js": "~0.3.1",
57
+ "socket.io": "^4.8.3",
58
+ "socket.io-client": "^4.8.3"
59
+ }
60
+ }
@@ -0,0 +1,4 @@
1
+ (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ss = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
2
+
3
+ },{}]},{},[1])(1)
4
+ });