-
Notifications
You must be signed in to change notification settings - Fork 8
/
index.js
130 lines (109 loc) · 3.5 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
var dgram = require("dgram");
var util = require("util");
var events = require("events");
var _ = require("underscore");
var ip = require("ip");
const SSDP_PORT = 1900;
const BROADCAST_ADDR = "239.255.255.250";
const SSDP_ALIVE = 'ssdp:alive';
const SSDP_BYEBYE = 'ssdp:byebye';
const SSDP_UPDATE = 'ssdp:update';
const SSDP_ALL = 'ssdp:all';
const SSDP_NTS_EVENTS = {
'ssdp:alive': 'DeviceAvailable',
'ssdp:byebye': 'DeviceUnavailable',
'ssdp:update': 'DeviceUpdate'
};
const UPNP_FIELDS = ['host', 'server', 'location', 'st', 'usn', 'nts', 'nt', 'bootid.upnp.org', 'configid.upnp.org', 'nextbootid.upnp.org', 'searchport.upnp.org'];
function messageLines(msg) {
return msg.toString('ascii').split('\r\n');
}
function toKeyPair(header) {
var result;
var splitCharIndex = header.indexOf(':');
if (splitCharIndex > 0) {
result = {};
result[header.slice(0,splitCharIndex).toLowerCase().trim()] = header.slice(splitCharIndex + 1).trim();
}
return result;
}
function mSearchResponseParser(msg, rinfo) {
var headers = messageLines(msg);
if (headers[0] === 'HTTP/1.1 200 OK') {
return _.chain(headers)
.map(toKeyPair)
.compact()
.reduce(_.extend, {})
.pick(UPNP_FIELDS)
.value();
}
return void 0;
}
function notifyResponseParser(msg, rinfo) {
var headers = messageLines(msg);
if (headers[0] === 'NOTIFY * HTTP/1.1') {
return _.chain(headers)
.map(toKeyPair)
.compact()
.reduce(_.extend, {})
.pick(UPNP_FIELDS)
.value();
}
return void 0;
}
function announceDiscoveredDevice(emitter) {
return function (msg, rinfo) {
var device = mSearchResponseParser(msg, rinfo);
if (device) {
emitter.emit('DeviceFound', device);
}
};
}
function announceDevice(emitter) {
return function (msg, rinfo) {
var device = notifyResponseParser(msg, rinfo);
if (device) {
emitter.emit(SSDP_NTS_EVENTS[device.nts], device);
emitter.emit(SSDP_NTS_EVENTS[device.nts]+':'+device.nt, device);
}
};
}
function Ssdp() {
events.EventEmitter.call(this);
var udpServer = dgram.createSocket({ type: 'udp4', reuseAddr: true }, announceDevice(this));
udpServer.bind(SSDP_PORT, function onConnected() {
udpServer.addMembership(BROADCAST_ADDR, ip.address());
});
this.close = function(callback) {
udpServer.close(callback);
}
this.mSearch = function(st) {
if (typeof st !== 'string') {
st = SSDP_ALL;
}
var message =
"M-SEARCH * HTTP/1.1\r\n"+
"Host:"+BROADCAST_ADDR+":"+SSDP_PORT+"\r\n"+
"ST:"+st+"\r\n"+
"Man:\"ssdp:discover\"\r\n"+
"MX:2\r\n\r\n";
var mSearchListener = dgram.createSocket({ type: 'udp4', reuseAddr: true }, announceDiscoveredDevice(this));
var mSearchRequester = dgram.createSocket({ type: 'udp4', reuseAddr: true });
mSearchListener.on('listening', function () {
mSearchRequester.send(new Buffer(message, "ascii"), 0, message.length, SSDP_PORT, BROADCAST_ADDR, function closeMSearchRequester() {
mSearchRequester.close();
});
});
mSearchRequester.on('listening', function () {
mSearchListener.bind(mSearchRequester.address().port);
});
// Otherwise it'll bind on 0.0.0.0
mSearchRequester.bind(undefined, ip.address());
// MX is set to 2, wait for 1 additional sec. before closing the server
setTimeout(function(){
mSearchListener.close();
}, 3000);
};
}
util.inherits(Ssdp, events.EventEmitter);
module.exports = new Ssdp();