UNPKG

4.1 kBJavaScriptView Raw
1var WS = require('pull-ws')
2var URL = require('url')
3var pull = require('pull-stream/pull')
4var Map = require('pull-stream/throughs/map')
5var scopes = require('multiserver-scopes')
6var http = require('http')
7var debug = require('debug')('multiserver:ws')
8
9function safe_origin (origin, address, port) {
10
11 //if the connection is not localhost, we shouldn't trust
12 //the origin header. So, use address instead of origin
13 //if origin not set, then it's definitely not a browser.
14 if(!(address === '::1' || address === '127.0.0.1') || origin == undefined)
15 return 'ws:' + address + (port ? ':' + port : '')
16
17 //note: origin "null" (as string) can happen a bunch of ways
18 // it can be a html opened as a file
19 // or certain types of CORS
20 // https://www.w3.org/TR/cors/#resource-sharing-check-0
21 // and webworkers if loaded from data-url?
22 if(origin === 'null')
23 return 'ws:null'
24
25 //a connection from the browser on localhost,
26 //we choose to trust this came from a browser.
27 return origin.replace(/^http/, 'ws')
28
29}
30
31module.exports = function (opts) {
32 opts = opts || {}
33 opts.binaryType = (opts.binaryType || 'arraybuffer')
34 var scope = opts.scope || 'device'
35 function isScoped (s) {
36 return s === scope || Array.isArray(scope) && ~scope.indexOf(s)
37 }
38
39 var secure = opts.server && !!opts.server.key
40 return {
41 name: 'ws',
42 scope: function() { return opts.scope || 'device' },
43 server: function (onConnect, startedCb) {
44
45 if(!WS.createServer) return
46 // Choose a dynamic port between 49152 and 65535
47 // https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers#Dynamic,_private_or_ephemeral_ports
48 opts.port = opts.port || Math.floor(49152 + (65535 - 49152 + 1) * Math.random())
49
50 var server = opts.server || http.createServer(opts.handler)
51
52 var ws_server = WS.createServer(Object.assign({}, opts, {server: server}), function (stream) {
53 stream.address = safe_origin(
54 stream.headers.origin,
55 stream.remoteAddress,
56 stream.remotePort
57 )
58 onConnect(stream)
59 })
60
61 if(!opts.server) {
62 debug('Listening on %s:%d', opts.host, opts.port)
63 server.listen(opts.port, opts.host, function () {
64 startedCb && startedCb(null, true)
65 })
66 }
67 else
68 startedCb && startedCb(null, true)
69
70 return function (cb) {
71 debug('Closing server on %s:%d', opts.host, opts.port)
72 server.close(function(err) {
73 if (err) console.error(err)
74 else debug('No longer listening on %s:%d', opts.host, opts.port)
75 if (cb) cb(err)
76 })
77 }
78 },
79 client: function (addr, cb) {
80 if(!addr.host) {
81 addr.hostname = addr.hostname || opts.host || 'localhost'
82 addr.slashes = true
83 addr = URL.format(addr)
84 }
85 if('string' !== typeof addr)
86 addr = URL.format(addr)
87
88 var stream = WS.connect(addr, {
89 binaryType: opts.binaryType,
90 onConnect: function (err) {
91 //ensure stream is a stream of node buffers
92 stream.source = pull(stream.source, Map(Buffer.from.bind(Buffer)))
93 cb(err, stream)
94 }
95 })
96 stream.address = addr
97
98 return function () {
99 stream.close(cb)
100 }
101 },
102 stringify: function (scope) {
103 scope = scope || 'device'
104 if(!isScoped(scope)) return null
105 if(!WS.createServer) return null
106 var port
107 if(opts.server)
108 port = opts.server.address().port
109 else
110 port = opts.port
111
112 var host = (scope == 'public' && opts.external) || scopes.host(scope)
113 //if a public scope was requested, but a public ip is not available, return
114 if(!host) return null
115
116 return URL.format({
117 protocol: secure ? 'wss' : 'ws',
118 slashes: true,
119 hostname: host,
120 port: (secure ? port == 443 : port == 80) ? undefined : port
121 })
122 },
123 parse: function (str) {
124 var addr = URL.parse(str)
125 if(!/^wss?\:$/.test(addr.protocol)) return null
126 return addr
127 }
128 }
129}
130
131
132
133