This app provides monitoring and information features for the common freifunk user and the technical stuff of a freifunk community.
Code base is taken from a TUM Practical Course project and added here to see if Freifunk Altdorf can use it.
https://www.freifunk-altdorf.de
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
218 lines
5.9 KiB
218 lines
5.9 KiB
/** |
|
* Copyright (c) 2015-present, Facebook, Inc. |
|
* |
|
* This source code is licensed under the MIT license found in the |
|
* LICENSE file in the root directory of this source tree. |
|
*/ |
|
'use strict'; |
|
|
|
const url = require('url'); |
|
const WebSocketServer = require('ws').Server; |
|
const PROTOCOL_VERSION = 2; |
|
const notifier = require('node-notifier'); |
|
|
|
function parseMessage(data, binary) { |
|
if (binary) { |
|
console.error('Expected text message, got binary!'); |
|
return undefined; |
|
} |
|
try { |
|
const message = JSON.parse(data); |
|
if (message.version === PROTOCOL_VERSION) { |
|
return message; |
|
} |
|
console.error('Received message had wrong protocol version: ' |
|
+ message.version); |
|
} catch (e) { |
|
console.error('Failed to parse the message as JSON:\n' + data); |
|
} |
|
return undefined; |
|
} |
|
|
|
function isBroadcast(message) { |
|
return ( |
|
typeof message.method === 'string' && |
|
message.id === undefined && |
|
message.target === undefined |
|
); |
|
} |
|
|
|
function isRequest(message) { |
|
return ( |
|
typeof message.method === 'string' && |
|
typeof message.target === 'string'); |
|
} |
|
|
|
function isResponse(message) { |
|
return ( |
|
typeof message.id === 'object' && |
|
typeof message.id.requestId !== undefined && |
|
typeof message.id.clientId === 'string' && ( |
|
message.result !== undefined || |
|
message.error !== undefined |
|
)); |
|
} |
|
|
|
function attachToServer(server, path) { |
|
const wss = new WebSocketServer({ |
|
server: server, |
|
path: path |
|
}); |
|
const clients = new Map(); |
|
let nextClientId = 0; |
|
|
|
function getClientWs(clientId) { |
|
const clientWs = clients.get(clientId); |
|
if (clientWs === undefined) { |
|
throw `could not find id "${clientId}" while forwarding request`; |
|
} |
|
return clientWs; |
|
} |
|
|
|
function handleSendBroadcast(broadcasterId, message) { |
|
const forwarded = { |
|
version: PROTOCOL_VERSION, |
|
method: message.method, |
|
params: message.params, |
|
}; |
|
if (clients.size === 0) { |
|
notifier.notify({ |
|
'title': 'React Native: No apps connected', |
|
'message': `Sending '${message.method}' to all React Native apps ` + |
|
'failed. Make sure your app is running in the simulator ' + |
|
'or on a phone connected via USB.' |
|
}); |
|
} |
|
for (const [otherId, otherWs] of clients) { |
|
if (otherId !== broadcasterId) { |
|
try { |
|
otherWs.send(JSON.stringify(forwarded)); |
|
} catch (e) { |
|
console.error(`Failed to send broadcast to client: '${otherId}' ` + |
|
`due to:\n ${e.toString()}`); |
|
} |
|
} |
|
} |
|
} |
|
|
|
wss.on('connection', function(clientWs) { |
|
const clientId = `client#${nextClientId++}`; |
|
|
|
function handleCaughtError(message, error) { |
|
const errorMessage = { |
|
id: message.id, |
|
method: message.method, |
|
target: message.target, |
|
error: message.error === undefined ? 'undefined' : 'defined', |
|
params: message.params === undefined ? 'undefined' : 'defined', |
|
result: message.result === undefined ? 'undefined' : 'defined', |
|
}; |
|
|
|
if (message.id === undefined) { |
|
console.error( |
|
`Handling message from ${clientId} failed with:\n${error}\n` + |
|
`message:\n${JSON.stringify(errorMessage)}`); |
|
} else { |
|
try { |
|
clientWs.send(JSON.stringify({ |
|
version: PROTOCOL_VERSION, |
|
error: error, |
|
id: message.id, |
|
})); |
|
} catch (e) { |
|
console.error(`Failed to reply to ${clientId} with error:\n${error}` + |
|
`\nmessage:\n${JSON.stringify(errorMessage)}` + |
|
`\ndue to error: ${e.toString()}`); |
|
} |
|
} |
|
} |
|
|
|
function handleServerRequest(message) { |
|
let result = null; |
|
switch (message.method) { |
|
case 'getid': |
|
result = clientId; |
|
break; |
|
case 'getpeers': |
|
result = {}; |
|
clients.forEach((otherWs, otherId) => { |
|
if (clientId !== otherId) { |
|
result[otherId] = url.parse(otherWs.upgradeReq.url, true).query; |
|
} |
|
}); |
|
break; |
|
default: |
|
throw `unknown method: ${message.method}`; |
|
} |
|
|
|
clientWs.send(JSON.stringify({ |
|
version: PROTOCOL_VERSION, |
|
result: result, |
|
id: message.id |
|
})); |
|
} |
|
|
|
function forwardRequest(message) { |
|
getClientWs(message.target).send(JSON.stringify({ |
|
version: PROTOCOL_VERSION, |
|
method: message.method, |
|
params: message.params, |
|
id: (message.id === undefined |
|
? undefined |
|
: {requestId: message.id, clientId: clientId}), |
|
})); |
|
} |
|
|
|
function forwardResponse(message) { |
|
getClientWs(message.id.clientId).send(JSON.stringify({ |
|
version: PROTOCOL_VERSION, |
|
result: message.result, |
|
error: message.error, |
|
id: message.id.requestId, |
|
})); |
|
} |
|
|
|
clients.set(clientId, clientWs); |
|
clientWs.onclose = |
|
clientWs.onerror = () => { |
|
clientWs.onmessage = null; |
|
clients.delete(clientId); |
|
}; |
|
clientWs.onmessage = (event) => { |
|
const message = parseMessage(event.data, event.binary); |
|
if (message === undefined) { |
|
console.error('Received message not matching protocol'); |
|
return; |
|
} |
|
|
|
try { |
|
if (isBroadcast(message)) { |
|
handleSendBroadcast(clientId, message); |
|
} else if (isRequest(message)) { |
|
if (message.target === 'server') { |
|
handleServerRequest(message); |
|
} else { |
|
forwardRequest(message); |
|
} |
|
} else if (isResponse(message)) { |
|
forwardResponse(message); |
|
} else { |
|
throw 'Invalid message, did not match the protocol'; |
|
} |
|
} catch (e) { |
|
handleCaughtError(message, e.toString()); |
|
} |
|
}; |
|
}); |
|
|
|
return { |
|
broadcast: (method, params) => { |
|
handleSendBroadcast(null, {method: method, params: params}); |
|
} |
|
}; |
|
} |
|
|
|
module.exports = { |
|
attachToServer: attachToServer, |
|
parseMessage: parseMessage, |
|
};
|
|
|