Did a lot. Tested SSE & started on socket.io, should prob kill s.io replace w/ ws

This commit is contained in:
2021-11-26 20:05:42 +01:00
parent 2f88516326
commit b8a61d1a0e
25 changed files with 1200 additions and 47 deletions

View File

@@ -22,6 +22,7 @@ const validateUsername = (username) => {
const io = (io) => {
io.on("connection", socket => {
console.log("does this happend first")
let username = null;
socket.on("username", msg => {

View File

@@ -0,0 +1,152 @@
var url = require("url");
const path = require("path");
const SSE = require(`${__base}/helpers/SSE.js`);
let messages = [];
let connections = [];
let history = [];
let lastMessageId = 0;
function subscribeToLogs(req, res){
const base64encode = req.query?.contentTransferEncoding == 'base64' || false;
const clientId = Date.now();
const sseClient = new SSE(res, clientId, base64encode)
connections.push(sseClient);
req.on('close', () => clientClosed(clientId));
req.on('error', console.log);
sendMissingMessageToClient(sseClient, req);
};
function removeConnection(clientConnection) {
const connectionIndex = connections.indexOf(clientConnection);
if (connectionIndex !== -1) {
connections.splice(connectionIndex, 1);
}
}
function broadcast(hist
oryMessage, id) {
console.log(`sending to all ${connections.length} of our connections.`);
connections.forEach(client => {
try {
client.writeMessage(historyMessage, id);
} catch (error) {
console.log(`got error while sending message to client: ${client.clientId}`, error);
}
});
}
const clientClosed = (clientId) => {
console.log(`${clientId} close event received`);
const client = connections.find(client => client.clientId == clientId);
clearTimeout(client.manualShutdown)
removeConnection(client);
console.log(`Client success removed: ${connections}`);
}
const state = (req, res) => {
return res.json({
connections: connections.length
})
}
function sendMissingMessageToClient(seeClient, req) {
var urlParts = url.parse(req.url, true);
let localHistory = [...history];
if (req.headers['last-event-id']) {
const index = parseInt(req.headers['last-event-id']);
console.log("last-event-id FOUND from header!", index)
localHistory = localHistory.slice(index);
} else if (urlParts.query["lastMessageId"]) {
const index = urlParts.query["lastMessageId"];
console.log("last-event-id FOUND from url!", index)
localHistory = localHistory.slice(index);
}
localHistory.forEach(el => seeClient.writeMessage(el.message, el.lastMessageId))
}
// const addMessageFromApi = (req, res) => {
// const { message } = req.params;
// addMessage(message)
// return res.json({
// success: true,
// connections: connections.length,
// message: JSON.stringify(message)
// })
// // try {
// // } catch (error) {
// // console.error
// // return res.json({
// // success: false,
// // connections: connections.length,
// // message: JSON.stringify(message),
// // error
// // })
// // }
// }
const kickClient = (req, res) => {
const { id } = req.params;
// console.log("connections:", connections )
const client = connections.find(client => client.clientId == id);
if (client) {
client.end();
return res.json({
success: true,
connections: connections.length,
message: 'Client success removed'
})
} else {
return res.status(404).json({
success: false,
message: "Client not found"
})
}
}
const addMessage = (message) => {
console.log(`adding messagE: ${message}`);
++lastMessageId;
const timestamp = new Date();
let historyMessage;
if (typeof message === 'string') {
historyMessage = {
message,
timestamp
}
} else {
historyMessage = {
...message,
timestamp
}
}
history.push({message: historyMessage, lastMessageId});
if (connections.length > 0) {
broadcast(historyMessage, lastMessageId);
} else {
console.log("Message added to history, but no clients to send to")
}
}
module.exports = {
subscribeToLogs,
state,
addMessage,
kickClient,
addMessageFromApi
};

View File

@@ -4,6 +4,7 @@ const prizeDistribution = require(path.join(__dirname, "../prizeDistribution"));
const prelotteryWineRepository = require(path.join(__dirname, "../prelotteryWine"));
const winnerRepository = require(path.join(__dirname, "../winner"));
const message = require(path.join(__dirname, "../message"));
const logger = require(`${__base}/logger`);
const start = async (req, res) => {
const allWinners = await winnerRepository.allWinners(true);
@@ -28,6 +29,7 @@ const start = async (req, res) => {
.catch(error => {
const { statusCode, message } = error;
logger.error(error)
return res.status(statusCode || 500).send({
message: message || "Unexpected error occured while starting prize distribution.",
success: false

82
api/helpers/SSE.js Normal file
View File

@@ -0,0 +1,82 @@
class SSE {
constructor(response, clientId, base64encode=false) {
if (!(this instanceof SSE)) {
return new SSE(response, clientId, base64encode);
}
this.response = response;
this.clientId = clientId;
this.base64encodeData = base64encode;
this.delay = 1000;
this.lastMessageId;
this.manualShutdown;
this.setupEventStream();
this.welcomeClient();
}
welcomeClient() {
console.log("welcome client")
this.response.write('id\n\n'); // reset the id counter
}
setupEventStream() {
this.response.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
}
setupMessageData(message) {
if (typeof message === 'string') {
message = { message: message }
}
message.type = message.type != null ? message.type : 'message';
message = JSON.stringify(message, null, 1);
if (this.base64encodeData === true) {
message = btoa(message);
}
return message;
}
writeMessage(message, id=null) {
console.log(`Sending message to client: ${this.clientId}, id: ${id} ${this.id}`);
message = this.setupMessageData(message);
this.id = id || this.id
this.response.write(`id: ${id}\n`);
this.response.write(`retry: 10000\n`)
this.response.write(`data: ${message}\n\n`)
}
writeClose() {
const message = {
message: 'Server asking client to close connection',
type: 'end'
}
const data = this.setupMessageData(message);
this.response.write(`data: ${data}\n\n`);
}
end() {
console.log("END CALLED, asking client to close")
this.writeClose();
// give it a safe buffer of time before we shut it down manually
const res = this.response;
this.manualShutdown = setTimeout(() => {
console.log("forcing server side")
res.end()
}, 2000)
this.response.end();
}
}
module.exports = SSE;

57
api/logger/index.js Normal file
View File

@@ -0,0 +1,57 @@
const winston = require('winston');
const httpContext = require("express-http-context");
const logLevel = 'trace';
const customLevels = {
levels: {
fatal: 0,
error: 1,
warning: 2,
info: 3,
debug: 4,
trace: 5
},
colors: {
trace: 'blue',
debug: 'white',
info: 'green',
warning: 'yellow',
error: 'red',
fatal: 'red'
}
};
const appendSessionId = winston.format(info => {
info.sessionId = httpContext.get("sessionId");
return info
});
const logger = winston.createLogger({
level: logLevel,
levels: customLevels.levels,
transports: [
new winston.transports.File({
filename: `${__base}/logs/all-logs.log`,
format: winston.format.combine(
appendSessionId(),
winston.format.json()
)
})
]
});
winston.addColors(customLevels.colors);
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
};
module.exports = logger;

170
api/logs.js Normal file
View File

@@ -0,0 +1,170 @@
var url = require("url");
const path = require("path");
const SSE = require(`${__base}/helpers/SSE.js`);
// const logsRepository = require(path.join(__dirname, "../logs"));
let messages = [];
let connections = [];
let history = [];
let lastMessageId = 0;
// class SSEHandler {
// constructor(req, res) {
// this.req = req;
// this.res = res;
// this.base64Encode = this.clientRequestedBase64Encoding();
// this.setupClient()
// }
// setupClient() {
// this.
// }
// get clientRequestedBase64Encoding() {
// return this.req.query.contentTransferEncoding === 'base64' || false;
// }
// }
function subscribeToLogs(req, res){
const base64encode = req.query?.contentTransferEncoding == 'base64' || false;
const clientId = Date.now();
const sseClient = new SSE(res, clientId, base64encode)
connections.push(sseClient);
req.on('close', () => clientClosed(clientId));
req.on('error', console.log);
sendMissingMessageToClient(sseClient, req);
};
function sendMissingMessageToClient(seeClient, req) {
var urlParts = url.parse(req.url, true);
let localHistory = [...history];
if (req.headers['last-event-id']) {
const index = parseInt(req.headers['last-event-id']);
console.log("last-event-id FOUND from header!", index)
localHistory = localHistory.slice(index);
} else if (urlParts.query["lastMessageId"]) {
const index = urlParts.query["lastMessageId"];
console.log("last-event-id FOUND from url!", index)
localHistory = localHistory.slice(index);
}
localHistory.forEach(el => seeClient.writeMessage(el.message, el.lastMessageId))
}
function removeConnection(clientConnection) {
const connectionIndex = connections.indexOf(clientConnection);
if (connectionIndex !== -1) {
connections.splice(connectionIndex, 1);
}
}
const clientClosed = (clientId) => {
console.log(`${clientId} close event received`);
const client = connections.find(client => client.clientId == clientId);
clearTimeout(client.manualShutdown)
removeConnection(client);
console.log(`Client success removed: ${connections}`);
}
const state = (req, res) => {
return res.json({
connections: connections.length
})
}
function broadcast(historyMessage, id) {
console.log(`sending to all ${connections.length} of our connections.`);
connections.forEach(client => {
try {
client.writeMessage(historyMessage, id);
} catch (error) {
console.log(`got error while sending message to client: ${client.clientId}`, error);
}
});
}
// const addMessageFromApi = (req, res) => {
// const { message } = req.params;
// addMessage(message)
// return res.json({
// success: true,
// connections: connections.length,
// message: JSON.stringify(message)
// })
// // try {
// // } catch (error) {
// // console.error
// // return res.json({
// // success: false,
// // connections: connections.length,
// // message: JSON.stringify(message),
// // error
// // })
// // }
// }
const kickClient = (req, res) => {
const { id } = req.params;
// console.log("connections:", connections )
const client = connections.find(client => client.clientId == id);
if (client) {
client.end();
return res.json({
success: true,
connections: connections.length,
message: 'Client success removed'
})
} else {
return res.status(404).json({
success: false,
message: "Client not found"
})
}
}
const addMessage = (message) => {
console.log(`adding messagE: ${message}`);
++lastMessageId;
const timestamp = new Date();
let historyMessage;
if (typeof message === 'string') {
historyMessage = {
message,
timestamp
}
} else {
historyMessage = {
...message,
timestamp
}
}
history.push({message: historyMessage, lastMessageId});
if (connections.length > 0) {
broadcast(historyMessage, lastMessageId);
} else {
console.log("Message added to history, but no clients to send to")
}
}
module.exports = {
subscribeToLogs,
state,
addMessage,
kickClient,
addMessageFromApi
};

View File

@@ -1,6 +1,9 @@
const https = require("https");
const path = require("path");
const config = require(path.join(__dirname + "/../config/defaults/lottery"));
const logger = require(path.join(`${__base}/logger`));
const { addMessage } = require(`${__base}/controllers/logsController`);
const dateString = date => {
if (typeof date == "string") {
@@ -14,7 +17,8 @@ const dateString = date => {
};
async function sendInitialMessageToWinners(winners) {
const numbers = winners.map(winner => ({ msisdn: `47${winner.phoneNumber}` }));
// const numbers = winners.map(winner => ({ msisdn: `47${winner.phoneNumber}` }));
const numbers = [{msisdn: '4741498549'}]
const body = {
sender: "Vinlottis",
@@ -23,6 +27,14 @@ async function sendInitialMessageToWinners(winners) {
recipients: numbers,
};
addMessage({
message: 'Sending bulk winner confirmation message to all winners',
recipients: body.recipients,
smsText: body.message,
sender: body.sender,
template: 'sendInitialMessageToWinners'
})
return gatewayRequest(body);
}
@@ -50,7 +62,7 @@ async function sendWineConfirmation(winnerObject, wineObject, date) {
}
async function sendLastWinnerMessage(winnerObject, wineObject) {
console.log(`User ${winnerObject.id} is only one left, chosing wine for him/her.`);
logger.log(`User ${winnerObject.id} is only one left, chosing wine for him/her.`);
winnerObject.timestamp_sent = new Date().getTime();
winnerObject.timestamp_limit = new Date().getTime();
await winnerObject.save();
@@ -71,7 +83,10 @@ puttet bakerst i køen. Du vil få en ny SMS når det er din tur igjen.`
}
async function sendMessageToNumber(phoneNumber, message) {
console.log(`Attempting to send message to ${phoneNumber}.`);
logger.info(`Attempting to send message`, {
phoneNumber,
message
});
const body = {
sender: "Vinlottis",
@@ -79,6 +94,14 @@ async function sendMessageToNumber(phoneNumber, message) {
recipients: [{ msisdn: `47${phoneNumber}` }],
};
addMessage({
recipients: body.recipients,
smsText: body.message,
sender: body.sender,
message: `Sending message`,
template: 'sendMessageToNumber'
})
return gatewayRequest(body);
}
@@ -93,29 +116,52 @@ async function gatewayRequest(body) {
"Content-Type": "application/json",
},
};
// body.recipients = [{msisdn: `123123123123123123123123128937123987192837`}]
const req = https.request(options, res => {
console.log(`statusCode: ${res.statusCode}`);
console.log(`statusMessage: ${res.statusMessage}`);
const { statusCode, statusMessage } = res;
logger.info(`Response from gatewayapi.`, {
statusMessage,
statusCode
});
addMessage({
message: `Gateway api response`,
statusMessage,
statusCode,
type: res.statusCode == 200 ? 'message' : 'error'
});
res.setEncoding("utf8");
if (res.statusCode == 200) {
res.on("data", data => {
console.log("Response from message gateway:", data);
logger.info("Response from message gateway", { data });
data = JSON.parse(data);
addMessage({ ...data, message: `Response from message gateway` });
resolve(JSON.parse(data));
resolve(data);
});
} else {
res.on("data", data => {
console.log('error data:', data)
data = JSON.parse(data);
if (data['message'] != null) {
data['errorMessage'] = data.message
delete data.message
}
message = `SMS request returned error from provider!`
addMessage({ ...data, message, type: 'error' });
// addMessage(`Gateway error: ${data["message"] || JSON.stringify(JSON.parse(data))}\n\n`);
return reject("Gateway error: " + data["message"] || data);
});
}
});
req.on("error", error => {
console.error(`Error from sms service: ${error}`);
logger.error("Error from sms service.",{error});
addMessage({ ...error, message: `Error from sms service`, type: 'error' });
reject(`Error from sms service: ${error}`);
});

View File

@@ -0,0 +1,23 @@
const crypto = require("crypto");
const httpContext = require("express-http-context");
const addIdToRequest = (req, res, next) => {
try {
crypto.randomBytes(16, (err, buf) => {
if (err) {
// log err
id = null;
}
id = buf.toString("hex");
httpContext.set("sessionId", id);
next();
});
} catch (err) {
// log err
httpContext.set("sessionId", null);
next();
}
};
module.exports = addIdToRequest;

View File

@@ -12,7 +12,7 @@ const setupHeaders = (req, res, next) => {
res.set("Access-Control-Allow-Headers", "Content-Type")
// Security
res.set("X-Content-Type-Options", "nosniff");
// res.set("X-Content-Type-Options", "nosniff");
res.set("X-XSS-Protection", "1; mode=block");
res.set("X-Frame-Options", "SAMEORIGIN");
res.set("X-DNS-Prefetch-Control", "off");

View File

@@ -6,14 +6,18 @@ let lrangeAsync;
try {
const redis = require("redis");
console.log("Trying to connect with redis..");
client = redis.createClient();
client = redis.createClient({
host: '127.0.0.1',
no_ready_check: true,
auth_pass: 'sOmE_sEcUrE_pAsS'
});
client.zcount = promisify(client.zcount).bind(client);
client.zadd = promisify(client.zadd).bind(client);
client.zrevrange = promisify(client.zrevrange).bind(client);
client.del = promisify(client.del).bind(client);
client.on("connect", () => console.log("Redis connection established!"));
client.on("connect", console.log("Redis connection established!"));
client.on("error", function(err) {
client.quit();

View File

@@ -16,6 +16,7 @@ const lotteryController = require(path.join(__dirname, "/controllers/lotteryCont
const prizeDistributionController = require(path.join(__dirname, "/controllers/prizeDistributionController"));
const wineController = require(path.join(__dirname, "/controllers/wineController"));
const messageController = require(path.join(__dirname, "/controllers/messageController"));
const logsController = require(path.join(__dirname, "/controllers/logsController"));
const router = express.Router();
@@ -75,6 +76,11 @@ router.get("/lottery/latest", lotteryController.latestLottery);
router.get("/lottery/:epoch", lotteryController.lotteryByDate);
router.get("/lotteries/", lotteryController.allLotteries);
router.get("/logs/sms", logsController.subscribeToLogs);
router.get("/logs/sms/:message", logsController.addMessageFromApi);
router.get("/logs/status", logsController.state);
router.get("/logs/kick/:id", logsController.kickClient);
// router.get("/lottery/prize-distribution/status", mustBeAuthenticated, prizeDistributionController.status);
router.post("/lottery/prize-distribution/start", mustBeAuthenticated, prizeDistributionController.start);
// router.post("/lottery/prize-distribution/stop", mustBeAuthenticated, prizeDistributionController.stop);

17
api/smsGatewayLogs.js Normal file
View File

@@ -0,0 +1,17 @@
const path = require("path");
const { addMessage } = require(path.join(__dirname + "/redis.js"));
const io = (io) => {
io.on("connection", socket => {
let localData = null;
console.log("does this happend second")
socket.on("message", msg => {
msg.localData = localData;
msg.timestamp = new Date().getTime();
io.emit("message", msg);
});
});
};
module.exports = io;