mirror of
https://github.com/schlagmichdoch/PairDrop.git
synced 2025-04-20 07:05:05 -04:00
move files for node only implementation
This commit is contained in:
parent
72f3bb0e7c
commit
947e088aeb
28 changed files with 27 additions and 0 deletions
337
server/index.js
337
server/index.js
|
@ -1,337 +0,0 @@
|
|||
var process = require('process')
|
||||
var net = require('net')
|
||||
// Handle SIGINT
|
||||
process.on('SIGINT', () => {
|
||||
console.info("SIGINT Received, exiting...")
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
// Handle SIGTERM
|
||||
process.on('SIGTERM', () => {
|
||||
console.info("SIGTERM Received, exiting...")
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
const parser = require('ua-parser-js');
|
||||
const { uniqueNamesGenerator, animals, colors } = require('unique-names-generator');
|
||||
|
||||
class SnapdropServer {
|
||||
|
||||
constructor(host, port) {
|
||||
const WebSocket = require('ws');
|
||||
this._wss = new WebSocket.Server({ host: host, port: port });
|
||||
this._wss.on('connection', (socket, request) => this._onConnection(new Peer(socket, request)));
|
||||
this._wss.on('headers', (headers, response) => this._onHeaders(headers, response));
|
||||
|
||||
this._rooms = {};
|
||||
|
||||
console.log('Snapdrop is running on port', port);
|
||||
}
|
||||
|
||||
_onConnection(peer) {
|
||||
this._joinRoom(peer);
|
||||
peer.socket.on('message', message => this._onMessage(peer, message));
|
||||
peer.socket.on('error', console.error);
|
||||
this._keepAlive(peer);
|
||||
|
||||
// send displayName
|
||||
this._send(peer, {
|
||||
type: 'display-name',
|
||||
message: {
|
||||
displayName: peer.name.displayName,
|
||||
deviceName: peer.name.deviceName
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_onHeaders(headers, request) {
|
||||
if (request.headers.cookie && request.headers.cookie.indexOf('peerid=') > -1) return;
|
||||
request.peerId = Peer.uuid();
|
||||
headers.push('Set-Cookie: peerid=' + request.peerId + "; SameSite=Strict; Secure");
|
||||
}
|
||||
|
||||
_onMessage(sender, message) {
|
||||
// Try to parse message
|
||||
try {
|
||||
message = JSON.parse(message);
|
||||
} catch (e) {
|
||||
return; // TODO: handle malformed JSON
|
||||
}
|
||||
|
||||
switch (message.type) {
|
||||
case 'disconnect':
|
||||
this._leaveRoom(sender);
|
||||
break;
|
||||
case 'pong':
|
||||
sender.lastBeat = Date.now();
|
||||
break;
|
||||
}
|
||||
|
||||
// relay message to recipient
|
||||
if (message.to && this._rooms[sender.ip]) {
|
||||
const recipientId = message.to; // TODO: sanitize
|
||||
const recipient = this._rooms[sender.ip][recipientId];
|
||||
delete message.to;
|
||||
// add sender id
|
||||
message.sender = sender.id;
|
||||
this._send(recipient, message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_joinRoom(peer) {
|
||||
// if room doesn't exist, create it
|
||||
if (!this._rooms[peer.ip]) {
|
||||
this._rooms[peer.ip] = {};
|
||||
}
|
||||
|
||||
// notify all other peers
|
||||
for (const otherPeerId in this._rooms[peer.ip]) {
|
||||
if (otherPeerId === peer.id) continue;
|
||||
const otherPeer = this._rooms[peer.ip][otherPeerId];
|
||||
this._send(otherPeer, {
|
||||
type: 'peer-joined',
|
||||
peer: peer.getInfo()
|
||||
});
|
||||
}
|
||||
|
||||
// notify peer about the other peers
|
||||
const otherPeers = [];
|
||||
for (const otherPeerId in this._rooms[peer.ip]) {
|
||||
if (otherPeerId === peer.id) continue;
|
||||
otherPeers.push(this._rooms[peer.ip][otherPeerId].getInfo());
|
||||
}
|
||||
|
||||
this._send(peer, {
|
||||
type: 'peers',
|
||||
peers: otherPeers
|
||||
});
|
||||
|
||||
// add peer to room
|
||||
this._rooms[peer.ip][peer.id] = peer;
|
||||
}
|
||||
|
||||
_leaveRoom(peer) {
|
||||
if (!this._rooms[peer.ip] || !this._rooms[peer.ip][peer.id]) return;
|
||||
this._cancelKeepAlive(this._rooms[peer.ip][peer.id]);
|
||||
|
||||
// delete the peer
|
||||
delete this._rooms[peer.ip][peer.id];
|
||||
|
||||
peer.socket.terminate();
|
||||
//if room is empty, delete the room
|
||||
if (!Object.keys(this._rooms[peer.ip]).length) {
|
||||
delete this._rooms[peer.ip];
|
||||
} else {
|
||||
// notify all other peers
|
||||
for (const otherPeerId in this._rooms[peer.ip]) {
|
||||
const otherPeer = this._rooms[peer.ip][otherPeerId];
|
||||
this._send(otherPeer, { type: 'peer-left', peerId: peer.id });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_send(peer, message) {
|
||||
if (!peer) return;
|
||||
if (this._wss.readyState !== this._wss.OPEN) return;
|
||||
message = JSON.stringify(message);
|
||||
peer.socket.send(message, error => '');
|
||||
}
|
||||
|
||||
_keepAlive(peer) {
|
||||
this._cancelKeepAlive(peer);
|
||||
var timeout = 500;
|
||||
if (!peer.lastBeat) {
|
||||
peer.lastBeat = Date.now();
|
||||
}
|
||||
if (Date.now() - peer.lastBeat > 2 * timeout) {
|
||||
this._leaveRoom(peer);
|
||||
return;
|
||||
}
|
||||
|
||||
this._send(peer, { type: 'ping' });
|
||||
|
||||
peer.timerId = setTimeout(() => this._keepAlive(peer), timeout);
|
||||
}
|
||||
|
||||
_cancelKeepAlive(peer) {
|
||||
if (peer && peer.timerId) {
|
||||
clearTimeout(peer.timerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class Peer {
|
||||
|
||||
constructor(socket, request) {
|
||||
// set socket
|
||||
this.socket = socket;
|
||||
|
||||
|
||||
// set remote ip
|
||||
this._setIP(request);
|
||||
|
||||
// set peer id
|
||||
this._setPeerId(request)
|
||||
// is WebRTC supported ?
|
||||
this.rtcSupported = request.url.indexOf('webrtc') > -1;
|
||||
// set name
|
||||
this._setName(request);
|
||||
// for keepalive
|
||||
this.timerId = 0;
|
||||
this.lastBeat = Date.now();
|
||||
console.debug(this.name.displayName)
|
||||
}
|
||||
|
||||
_setIP(request) {
|
||||
if (request.headers['cf-connecting-ip']) {
|
||||
this.ip = request.headers['cf-connecting-ip'].split(/\s*,\s*/)[0];
|
||||
} else if (request.headers['x-forwarded-for']) {
|
||||
this.ip = request.headers['x-forwarded-for'].split(/\s*,\s*/)[0];
|
||||
} else {
|
||||
this.ip = request.connection.remoteAddress;
|
||||
}
|
||||
// IPv4 and IPv6 use different values to refer to localhost
|
||||
// put all peers on the same network as the server into the same room as well
|
||||
if (this.ip === '::1' || this.ip === '::ffff:127.0.0.1' || this.ip === '::1' || this.ipIsPrivate(this.ip)) {
|
||||
this.ip = '127.0.0.1';
|
||||
}
|
||||
console.debug(this.ip)
|
||||
}
|
||||
|
||||
ipIsPrivate(ip) {
|
||||
if (ip.substring(0,7) === "::ffff:")
|
||||
ip = ip.substring(7);
|
||||
|
||||
if (net.isIPv4(ip)) {
|
||||
// 10.0.0.0 - 10.255.255.255 || 172.16.0.0 - 172.31.255.255 || 192.168.0.0 - 192.168.255.255
|
||||
return /^(10)\.(.*)\.(.*)\.(.*)$/.test(ip) || /^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip) || /^(192)\.(168)\.(.*)\.(.*)$/.test(ip)
|
||||
}
|
||||
|
||||
// else: ip is IPv6
|
||||
const firstWord = ip.split(":").find(el => !!el); //get first not empty word
|
||||
|
||||
// The original IPv6 Site Local addresses (fec0::/10) are deprecated. Range: fec0 - feff
|
||||
if (/^fe[c-f][0-f]$/.test(firstWord))
|
||||
return true;
|
||||
|
||||
// These days Unique Local Addresses (ULA) are used in place of Site Local.
|
||||
// Range: fc00 - fcff
|
||||
else if (/^fc[0-f]{2}$/.test(firstWord))
|
||||
return true;
|
||||
|
||||
// Range: fd00 - fcff
|
||||
else if (/^fd[0-f]{2}$/.test(firstWord))
|
||||
return true;
|
||||
|
||||
// Link local addresses (prefixed with fe80) are not routable
|
||||
else if (firstWord === "fe80")
|
||||
return true;
|
||||
|
||||
// Discard Prefix
|
||||
else if (firstWord === "100")
|
||||
return true;
|
||||
|
||||
// Any other IP address is not Unique Local Address (ULA)
|
||||
return false;
|
||||
}
|
||||
|
||||
_setPeerId(request) {
|
||||
if (request.peerId) {
|
||||
this.id = request.peerId;
|
||||
} else {
|
||||
this.id = request.headers.cookie.replace('peerid=', '');
|
||||
}
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `<Peer id=${this.id} ip=${this.ip} rtcSupported=${this.rtcSupported}>`
|
||||
}
|
||||
|
||||
_setName(req) {
|
||||
let ua = parser(req.headers['user-agent']);
|
||||
|
||||
|
||||
let deviceName = '';
|
||||
|
||||
if (ua.os && ua.os.name) {
|
||||
deviceName = ua.os.name.replace('Mac OS', 'Mac') + ' ';
|
||||
}
|
||||
|
||||
if (ua.device.model) {
|
||||
deviceName += ua.device.model;
|
||||
} else {
|
||||
deviceName += ua.browser.name;
|
||||
}
|
||||
|
||||
if(!deviceName)
|
||||
deviceName = 'Unknown Device';
|
||||
|
||||
const displayName = uniqueNamesGenerator({
|
||||
length: 2,
|
||||
separator: ' ',
|
||||
dictionaries: [colors, animals],
|
||||
style: 'capital',
|
||||
seed: this.id.hashCode()
|
||||
})
|
||||
|
||||
this.name = {
|
||||
model: ua.device.model,
|
||||
os: ua.os.name,
|
||||
browser: ua.browser.name,
|
||||
type: ua.device.type,
|
||||
deviceName,
|
||||
displayName
|
||||
};
|
||||
}
|
||||
|
||||
getInfo() {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
rtcSupported: this.rtcSupported
|
||||
}
|
||||
}
|
||||
|
||||
// return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
|
||||
static uuid() {
|
||||
let uuid = '',
|
||||
ii;
|
||||
for (ii = 0; ii < 32; ii += 1) {
|
||||
switch (ii) {
|
||||
case 8:
|
||||
case 20:
|
||||
uuid += '-';
|
||||
uuid += (Math.random() * 16 | 0).toString(16);
|
||||
break;
|
||||
case 12:
|
||||
uuid += '-';
|
||||
uuid += '4';
|
||||
break;
|
||||
case 16:
|
||||
uuid += '-';
|
||||
uuid += (Math.random() * 4 | 8).toString(16);
|
||||
break;
|
||||
default:
|
||||
uuid += (Math.random() * 16 | 0).toString(16);
|
||||
}
|
||||
}
|
||||
return uuid;
|
||||
};
|
||||
}
|
||||
|
||||
Object.defineProperty(String.prototype, 'hashCode', {
|
||||
value: function() {
|
||||
var hash = 0, i, chr;
|
||||
for (i = 0; i < this.length; i++) {
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
});
|
||||
|
||||
const server = new SnapdropServer(process.env.HOST || null, process.env.PORT || 3000);
|
Loading…
Add table
Add a link
Reference in a new issue