-
Notifications
You must be signed in to change notification settings - Fork 0
/
SsdpServer.js
127 lines (100 loc) · 2.88 KB
/
SsdpServer.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
const os = require('os');
const dgram = require('dgram');
class SsdpServer{
constructor(devices){
this.devices = devices;
this.notifyInterval = 0;
this.socket = dgram.createSocket({
type: 'udp4',
reuseAddr: true
});
this.socket.on('listening', this.onListening.bind(this));
this.socket.on('message', this.onMessage.bind(this));
this.socket.on('close', this.onClose.bind(this));
}
start(){
this.socket.bind(1900);
}
stop(){
this.socket.close();
}
onListening(){
const address = this.socket.address();
console.log(`Server listening on ${address.address}:${address.port}`);
this.socket.addMembership('239.255.255.250');
this.notifyAll();
this.notifyInterval = setInterval(this.notifyAll.bind(this), 36000);
}
onClose(){
clearInterval(this.notifyInterval);
}
parseMessage(message){
const rows = message.trim().split('\r\n');
const data = {type: rows.shift()};
rows.forEach((row) => {
const col = row.indexOf(':');
if(col >= 0){
const key = row.substring(0, col).trim().toLowerCase();
const value = row.substring(col + 1).trim();
data[key] = value;
}
});
return data;
}
notifyAll(){
this.devices.forEach((device) => {
const payload = this.notifyMessage(device.target, device.location, device.serial);
const buffer = Buffer.from(payload);
setTimeout(() => {
this.socket.send(payload, 0, payload.length, 1900, '239.255.255.250');
}, Math.random() * 1000);
});
}
notifyMessage(target, location, serial){
return [
'NOTIFY * HTTP/1.1',
'HOST: 239.255.255.250:1900',
'CACHE-CONTROL: max-age=3600',
`LOCATION: ${location}`,
'NTS: ssdp:alive',
`NT: ${target}`,
`SERVER: ${os.type()}/${os.release()} UPnP/1.0 Node/1.0`,
`USN: uuid:${serial}::${target}`,
'',
''
].join('\r\n');
}
searchResponse(target, location, uuid, serial){
return [
'HTTP/1.1 200 OK',
'CACHE-CONTROL: max-age=3600',
`DATE: ${new Date().toGMTString()}`,
'EXT:',
`LOCATION: ${location}`,
'OPT: "http://schemas.upnp.org/upnp/1/0/"; ns=01',
`01-NLS: ${uuid}`,
`SERVER: ${os.type()}/${os.release()} UPnP/1.0 Node/1.0`,
`ST: ${target}`,
`USN: uuid:${serial}::${target}`,
'',
''
].join('\r\n');
}
onMessage(message, rinfo){
const parsedMessage = this.parseMessage(message.toString());
if(parsedMessage.type.indexOf('M-SEARCH *') === 0 && parsedMessage.mx && parsedMessage.st){
const respondTime = Math.min(parseInt(parsedMessage.mx, 10), 5);
this.devices.forEach((device) => {
if(device.target === parsedMessage.st){
const payload = this.searchResponse(device.target, device.location, device.uuid, device.serial);
const buffer = Buffer.from(payload);
// console.log(payload);
setTimeout(() => {
this.socket.send(payload, 0, payload.length, rinfo.port, rinfo.address);
}, respondTime * 1000);
}
});
}
}
}
module.exports = SsdpServer;