forked from bazwilliams/node-ssdp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
131 lines (110 loc) · 3.64 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
131
var dgram = require("dgram");
var util = require("util");
var events = require("events");
var _ = require("underscore");
const SSDP_PORT = 1900;
const SSDP_MSEARCHREPLY_PORT = 5000;
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(function (memo, obj) {return _.extend(memo, obj)}, {})
.value();
}
return void 0;
}
function notifyResponseParser(msg, rinfo) {
var headers = messageLines(msg);
// add address to headers
headers.push("address: " + rinfo.address);
if (headers[0] === 'NOTIFY * HTTP/1.1') {
return _.chain(headers)
.map(toKeyPair)
.compact()
.reduce(function (memo, obj) {return _.extend(memo, obj)}, {})
.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(interface) {
events.EventEmitter.call(this);
var udpServer = dgram.createSocket({ type: 'udp4', reuseAddr: true }, announceDevice(this));
udpServer.bind(SSDP_PORT, function onConnected() {
udpServer.addMembership(BROADCAST_ADDR, interface);
});
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);
});
mSearchRequester.bind(SSDP_MSEARCHREPLY_PORT, interface);
// 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.start = function(interface) {
return new Ssdp(interface);
};