168 lines
No EOL
6.3 KiB
JavaScript
168 lines
No EOL
6.3 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const uaParser = require('ua-parser-js');
|
|
const {geoip} = require('./geoIP');
|
|
const {logger} = require('./logger');
|
|
|
|
let PROJECT_KEY_LENGTH = parseInt(process.env.PROJECT_KEY_LENGTH) || 20;
|
|
|
|
const IDENTITIES = {agent: 'agent', session: 'session'};
|
|
const EVENTS_DEFINITION = {
|
|
listen: {
|
|
UPDATE_EVENT: "UPDATE_SESSION", // tab become active/inactive, page title change, changed session object (rare case), call start/end
|
|
CONNECT_ERROR: "connect_error",
|
|
CONNECT_FAILED: "connect_failed",
|
|
ERROR: "error"
|
|
},
|
|
//The following list of events will be only emitted by the server
|
|
server: {
|
|
UPDATE_SESSION: "SERVER_UPDATE_SESSION"
|
|
}
|
|
};
|
|
EVENTS_DEFINITION.emit = {
|
|
NEW_AGENT: "NEW_AGENT",
|
|
NO_AGENTS: "NO_AGENT",
|
|
AGENT_DISCONNECT: "AGENT_DISCONNECTED",
|
|
AGENTS_CONNECTED: "AGENTS_CONNECTED",
|
|
NO_SESSIONS: "SESSION_DISCONNECTED",
|
|
SESSION_ALREADY_CONNECTED: "SESSION_ALREADY_CONNECTED",
|
|
SESSION_RECONNECTED: "SESSION_RECONNECTED",
|
|
UPDATE_EVENT: EVENTS_DEFINITION.listen.UPDATE_EVENT
|
|
};
|
|
|
|
const BASE_sessionInfo = {
|
|
"pageTitle": "Page",
|
|
"active": false,
|
|
"live": true,
|
|
"sessionID": "0",
|
|
"metadata": {},
|
|
"userID": "",
|
|
"userUUID": "",
|
|
"projectKey": "",
|
|
"revID": "",
|
|
"timestamp": 0,
|
|
"trackerVersion": "",
|
|
"isSnippet": true,
|
|
"userOs": "",
|
|
"userBrowser": "",
|
|
"userBrowserVersion": "",
|
|
"userDevice": "",
|
|
"userDeviceType": "",
|
|
"userCountry": "",
|
|
"userState": "",
|
|
"userCity": "",
|
|
"projectId": 0
|
|
};
|
|
|
|
const extractPeerId = (peerId) => {
|
|
const parts = peerId.split("-");
|
|
if (parts.length < 2 || parts.length > 3) {
|
|
logger.debug(`Invalid peerId format: ${peerId}`);
|
|
return {};
|
|
}
|
|
if (PROJECT_KEY_LENGTH > 0 && parts[0].length !== PROJECT_KEY_LENGTH) {
|
|
logger.debug(`Invalid project key length in peerId: ${peerId}`);
|
|
return {};
|
|
}
|
|
const [projectKey, sessionId, tabId = generateRandomTabId()] = parts;
|
|
return { projectKey, sessionId, tabId };
|
|
};
|
|
|
|
const generateRandomTabId = () => (Math.random() + 1).toString(36).substring(2);
|
|
|
|
function processPeerInfo(socket) {
|
|
socket._connectedAt = new Date();
|
|
const { projectKey, sessionId, tabId } = extractPeerId(socket.handshake.query.peerId || "");
|
|
Object.assign(socket.handshake.query, {
|
|
roomId: projectKey && sessionId ? `${projectKey}-${sessionId}` : null,
|
|
projectKey,
|
|
sessId: sessionId,
|
|
tabId
|
|
});
|
|
logger.debug(`Connection details: projectKey:${projectKey}, sessionId:${sessionId}, tabId:${tabId}, roomId:${socket.handshake.query.roomId}`);
|
|
}
|
|
|
|
/**
|
|
* extracts and populate socket with information
|
|
* @Param {socket} used socket
|
|
* */
|
|
const extractSessionInfo = function (socket) {
|
|
if (socket.handshake.query.sessionInfo !== undefined) {
|
|
logger.debug(`received headers: ${socket.handshake.headers}`);
|
|
|
|
socket.handshake.query.sessionInfo = JSON.parse(socket.handshake.query.sessionInfo);
|
|
socket.handshake.query.sessionInfo = {...BASE_sessionInfo, ...socket.handshake.query.sessionInfo};
|
|
|
|
let ua = uaParser(socket.handshake.headers['user-agent']);
|
|
socket.handshake.query.sessionInfo.userOs = ua.os.name || null;
|
|
socket.handshake.query.sessionInfo.userBrowser = ua.browser.name || null;
|
|
socket.handshake.query.sessionInfo.userBrowserVersion = ua.browser.version || null;
|
|
socket.handshake.query.sessionInfo.userDevice = ua.device.model || null;
|
|
socket.handshake.query.sessionInfo.userDeviceType = ua.device.type || 'desktop';
|
|
socket.handshake.query.sessionInfo.userCountry = null;
|
|
socket.handshake.query.sessionInfo.userState = null;
|
|
socket.handshake.query.sessionInfo.userCity = null;
|
|
if (geoip() !== null) {
|
|
logger.debug(`looking for location of ${socket.handshake.headers['x-forwarded-for'] || socket.handshake.address}`);
|
|
try {
|
|
let ip = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address;
|
|
ip = ip.split(",")[0];
|
|
let info = geoip().city(ip);
|
|
socket.handshake.query.sessionInfo.userCountry = info.country.isoCode;
|
|
socket.handshake.query.sessionInfo.userCity = info.city.names.en;
|
|
socket.handshake.query.sessionInfo.userState = info.subdivisions.length > 0 ? info.subdivisions[0].names.en : null;
|
|
} catch (e) {
|
|
logger.debug(`geoip-country failed: ${e}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function errorHandler(listenerName, error) {
|
|
logger.error(`Error detected from ${listenerName}\n${error}`);
|
|
}
|
|
|
|
const JWT_TOKEN_PREFIX = "Bearer ";
|
|
|
|
function check(socket, next) {
|
|
if (socket.handshake.query.identity === IDENTITIES.session) {
|
|
return next();
|
|
}
|
|
if (socket.handshake.query.peerId && socket.handshake.auth && socket.handshake.auth.token) {
|
|
let token = socket.handshake.auth.token;
|
|
if (token.startsWith(JWT_TOKEN_PREFIX)) {
|
|
token = token.substring(JWT_TOKEN_PREFIX.length);
|
|
}
|
|
jwt.verify(token, process.env.ASSIST_JWT_SECRET, (err, decoded) => {
|
|
logger.debug(`JWT payload: ${decoded}`);
|
|
if (err) {
|
|
logger.debug(err);
|
|
return next(new Error('Authentication error'));
|
|
}
|
|
const {projectKey, sessionId} = extractPeerId(socket.handshake.query.peerId);
|
|
if (!projectKey || !sessionId) {
|
|
logger.debug(`Missing attribute: projectKey:${projectKey}, sessionId:${sessionId}`);
|
|
return next(new Error('Authentication error'));
|
|
}
|
|
if (String(projectKey) !== String(decoded.projectKey) || String(sessionId) !== String(decoded.sessionId)) {
|
|
logger.debug(`Trying to access projectKey:${projectKey} instead of ${decoded.projectKey} or
|
|
to sessionId:${sessionId} instead of ${decoded.sessionId}`);
|
|
return next(new Error('Authorization error'));
|
|
}
|
|
socket.decoded = decoded;
|
|
return next();
|
|
});
|
|
} else {
|
|
logger.debug(`something missing in handshake: ${socket.handshake}`);
|
|
return next(new Error('Authentication error'));
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
processPeerInfo,
|
|
extractPeerId,
|
|
extractSessionInfo,
|
|
EVENTS_DEFINITION,
|
|
IDENTITIES,
|
|
errorHandler,
|
|
authorizer: {check}
|
|
}; |