unreal engine 5.1 update, level load fixed
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
logs/*.log
|
||||
node_modules
|
||||
platform_scripts
|
||||
tps
|
||||
@@ -0,0 +1,25 @@
|
||||
# Use the current Long Term Support (LTS) version of Node.js
|
||||
FROM node:lts
|
||||
|
||||
# Copy the signalling server source code from the build context
|
||||
COPY . /opt/SignallingWebServer
|
||||
|
||||
# Install the dependencies for the signalling server
|
||||
WORKDIR /opt/SignallingWebServer
|
||||
RUN npm install .
|
||||
|
||||
# Expose TCP ports 80 and 443 for player WebSocket connections and web server HTTP(S) access
|
||||
EXPOSE 80
|
||||
EXPOSE 443
|
||||
|
||||
# Expose TCP port 8888 for streamer WebSocket connections
|
||||
EXPOSE 8888
|
||||
|
||||
# Expose TCP port 8889 for connections from the SFU
|
||||
EXPOSE 8889
|
||||
|
||||
# Expose TCP port 9999 for connections from the Matchmaker
|
||||
EXPOSE 9999
|
||||
|
||||
# Set the signalling server as the container's entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/node", "/opt/SignallingWebServer/cirrus.js"]
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Required - the Login style sheet -->
|
||||
<link rel="stylesheet" type="text/css" href="css/login.css">
|
||||
|
||||
<!-- Optional: set some favicons -->
|
||||
<link id="favPng" rel="icon" type="image/png" href="images/favicon.png">
|
||||
|
||||
<!-- Optional: set a title for your page -->
|
||||
<title>Pixel Streaming Login</title>
|
||||
<script defer src="login.js"></script></head>
|
||||
|
||||
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
|
||||
<form action="/login" method="post">
|
||||
<div class="entry">
|
||||
<input type="text" id="username" name="username" placeholder="Username"
|
||||
autocomplete="username">
|
||||
</div>
|
||||
<div class="entry">
|
||||
<input type="password" id="password" name="password" placeholder="Password"
|
||||
autocomplete="current-password">
|
||||
</div>
|
||||
<div class="entry button">
|
||||
<button type="submit">LOGIN</button>
|
||||
</div>
|
||||
</form>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
|
||||
<!DOCTYPE HTML>
|
||||
<html style="width: 100%; height: 100%">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link href="https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@600&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Required - the stress tester style sheet -->
|
||||
<link rel="stylesheet" type="text/css" href="css/stresstest.css">
|
||||
|
||||
<!-- Optional: set some favicons -->
|
||||
<link id="favPng" rel="icon" type="image/png" href="images/favicon.png">
|
||||
|
||||
<!-- Optional: set a title for your page -->
|
||||
<title>Pixel Streaming Stress Tester</title>
|
||||
<script defer src="stresstest.js"></script></head>
|
||||
|
||||
<body style="width: 100vw; height: 100vh; min-height: -webkit-fill-available; font-family: 'Montserrat'; margin: 0px">
|
||||
<div id="control">
|
||||
<button id="playPause">Pause</button>
|
||||
<div>Total streams: <span id="nStreamsLabel">0</span></div>
|
||||
<div>
|
||||
<span>Max peers: </span>
|
||||
<span id="nPeerLabel">5</span>
|
||||
<input type="range" min="1" max="8" value="5" class="slider" id="nPeersSlider">
|
||||
</div>
|
||||
<div>
|
||||
<span>Peer creation interval (seconds): </span>
|
||||
<input type="number" id="creationIntervalInput" min="0" max="100" step="1" value="1">
|
||||
</div>
|
||||
<div>
|
||||
<span>Peer deletion interval (seconds): </span>
|
||||
<input type="number" id="deletionIntervalInput" min="0" max="100" step="1" value="2">
|
||||
</div>
|
||||
</div>
|
||||
<div id="streamsContainer"></div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,932 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
//-- Server side logic. Serves pixel streaming WebRTC-based page, proxies data back to Streamer --//
|
||||
|
||||
var express = require('express');
|
||||
var app = express();
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const querystring = require('querystring');
|
||||
const bodyParser = require('body-parser');
|
||||
const logging = require('./modules/logging.js');
|
||||
logging.RegisterConsoleLogger();
|
||||
|
||||
// Command line argument --configFile needs to be checked before loading the config, all other command line arguments are dealt with through the config object
|
||||
|
||||
const defaultConfig = {
|
||||
UseFrontend: false,
|
||||
UseMatchmaker: false,
|
||||
UseHTTPS: false,
|
||||
UseAuthentication: false,
|
||||
LogToFile: true,
|
||||
LogVerbose: true,
|
||||
HomepageFile: 'player.html',
|
||||
AdditionalRoutes: new Map(),
|
||||
EnableWebserver: true,
|
||||
MatchmakerAddress: "",
|
||||
MatchmakerPort: 9999,
|
||||
PublicIp: "localhost",
|
||||
HttpPort: 80,
|
||||
HttpsPort: 443,
|
||||
StreamerPort: 8888,
|
||||
SFUPort: 8889,
|
||||
MaxPlayerCount: -1
|
||||
};
|
||||
|
||||
const argv = require('yargs').argv;
|
||||
var configFile = (typeof argv.configFile != 'undefined') ? argv.configFile.toString() : path.join(__dirname, 'config.json');
|
||||
console.log(`configFile ${configFile}`);
|
||||
const config = require('./modules/config.js').init(configFile, defaultConfig);
|
||||
|
||||
if (config.LogToFile) {
|
||||
logging.RegisterFileLogger('./logs/');
|
||||
}
|
||||
|
||||
console.log("Config: " + JSON.stringify(config, null, '\t'));
|
||||
|
||||
var http = require('http').Server(app);
|
||||
|
||||
if (config.UseHTTPS) {
|
||||
//HTTPS certificate details
|
||||
const options = {
|
||||
key: fs.readFileSync(path.join(__dirname, './certificates/client-key.pem')),
|
||||
cert: fs.readFileSync(path.join(__dirname, './certificates/client-cert.pem'))
|
||||
};
|
||||
|
||||
var https = require('https').Server(options, app);
|
||||
}
|
||||
|
||||
//If not using authetication then just move on to the next function/middleware
|
||||
var isAuthenticated = redirectUrl => function (req, res, next) { return next(); }
|
||||
|
||||
if (config.UseAuthentication && config.UseHTTPS) {
|
||||
var passport = require('passport');
|
||||
require('./modules/authentication').init(app);
|
||||
// Replace the isAuthenticated with the one setup on passport module
|
||||
isAuthenticated = passport.authenticationMiddleware ? passport.authenticationMiddleware : isAuthenticated
|
||||
} else if (config.UseAuthentication && !config.UseHTTPS) {
|
||||
console.error('Trying to use authentication without using HTTPS, this is not allowed and so authentication will NOT be turned on, please turn on HTTPS to turn on authentication');
|
||||
}
|
||||
|
||||
const helmet = require('helmet');
|
||||
var hsts = require('hsts');
|
||||
var net = require('net');
|
||||
|
||||
var FRONTEND_WEBSERVER = 'https://localhost';
|
||||
if (config.UseFrontend) {
|
||||
var httpPort = 3000;
|
||||
var httpsPort = 8000;
|
||||
|
||||
//Required for self signed certs otherwise just get an error back when sending request to frontend see https://stackoverflow.com/a/35633993
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
|
||||
|
||||
const httpsClient = require('./modules/httpsClient.js');
|
||||
var webRequest = new httpsClient();
|
||||
} else {
|
||||
var httpPort = config.HttpPort;
|
||||
var httpsPort = config.HttpsPort;
|
||||
}
|
||||
|
||||
var streamerPort = config.StreamerPort; // port to listen to Streamer connections
|
||||
var sfuPort = config.SFUPort;
|
||||
|
||||
var matchmakerAddress = '127.0.0.1';
|
||||
var matchmakerPort = 9999;
|
||||
var matchmakerRetryInterval = 5;
|
||||
var matchmakerKeepAliveInterval = 30;
|
||||
var maxPlayerCount = -1;
|
||||
|
||||
var gameSessionId;
|
||||
var userSessionId;
|
||||
var serverPublicIp;
|
||||
|
||||
// `clientConfig` is send to Streamer and Players
|
||||
// Example of STUN server setting
|
||||
// let clientConfig = {peerConnectionOptions: { 'iceServers': [{'urls': ['stun:34.250.222.95:19302']}] }};
|
||||
var clientConfig = { type: 'config', peerConnectionOptions: {} };
|
||||
|
||||
// Parse public server address from command line
|
||||
// --publicIp <public address>
|
||||
try {
|
||||
if (typeof config.PublicIp != 'undefined') {
|
||||
serverPublicIp = config.PublicIp.toString();
|
||||
}
|
||||
|
||||
if (typeof config.HttpPort != 'undefined') {
|
||||
httpPort = config.HttpPort;
|
||||
}
|
||||
|
||||
if (typeof config.HttpsPort != 'undefined') {
|
||||
httpsPort = config.HttpsPort;
|
||||
}
|
||||
|
||||
if (typeof config.StreamerPort != 'undefined') {
|
||||
streamerPort = config.StreamerPort;
|
||||
}
|
||||
|
||||
if (typeof config.SFUPort != 'undefined') {
|
||||
sfuPort = config.SFUPort;
|
||||
}
|
||||
|
||||
if (typeof config.FrontendUrl != 'undefined') {
|
||||
FRONTEND_WEBSERVER = config.FrontendUrl;
|
||||
}
|
||||
|
||||
if (typeof config.peerConnectionOptions != 'undefined') {
|
||||
clientConfig.peerConnectionOptions = JSON.parse(config.peerConnectionOptions);
|
||||
console.log(`peerConnectionOptions = ${JSON.stringify(clientConfig.peerConnectionOptions)}`);
|
||||
} else {
|
||||
console.log("No peerConnectionConfig")
|
||||
}
|
||||
|
||||
if (typeof config.MatchmakerAddress != 'undefined') {
|
||||
matchmakerAddress = config.MatchmakerAddress;
|
||||
}
|
||||
|
||||
if (typeof config.MatchmakerPort != 'undefined') {
|
||||
matchmakerPort = config.MatchmakerPort;
|
||||
}
|
||||
|
||||
if (typeof config.MatchmakerRetryInterval != 'undefined') {
|
||||
matchmakerRetryInterval = config.MatchmakerRetryInterval;
|
||||
}
|
||||
|
||||
if (typeof config.MaxPlayerCount != 'undefined') {
|
||||
maxPlayerCount = config.MaxPlayerCount;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
if (config.UseHTTPS) {
|
||||
app.use(helmet());
|
||||
|
||||
app.use(hsts({
|
||||
maxAge: 15552000 // 180 days in seconds
|
||||
}));
|
||||
|
||||
//Setup http -> https redirect
|
||||
console.log('Redirecting http->https');
|
||||
app.use(function (req, res, next) {
|
||||
if (!req.secure) {
|
||||
if (req.get('Host')) {
|
||||
var hostAddressParts = req.get('Host').split(':');
|
||||
var hostAddress = hostAddressParts[0];
|
||||
if (httpsPort != 443) {
|
||||
hostAddress = `${hostAddress}:${httpsPort}`;
|
||||
}
|
||||
return res.redirect(['https://', hostAddress, req.originalUrl].join(''));
|
||||
} else {
|
||||
console.error(`unable to get host name from header. Requestor ${req.ip}, url path: '${req.originalUrl}', available headers ${JSON.stringify(req.headers)}`);
|
||||
return res.status(400).send('Bad Request');
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
sendGameSessionData();
|
||||
|
||||
//Setup the login page if we are using authentication
|
||||
if(config.UseAuthentication){
|
||||
if(config.EnableWebserver) {
|
||||
app.get('/login', function(req, res){
|
||||
res.sendFile(path.join(__dirname, '/Public', '/login.html'));
|
||||
});
|
||||
}
|
||||
|
||||
// create application/x-www-form-urlencoded parser
|
||||
var urlencodedParser = bodyParser.urlencoded({ extended: false })
|
||||
|
||||
//login page form data is posted here
|
||||
app.post('/login',
|
||||
urlencodedParser,
|
||||
passport.authenticate('local', { failureRedirect: '/login' }),
|
||||
function(req, res){
|
||||
//On success try to redirect to the page that they originally tired to get to, default to '/' if no redirect was found
|
||||
var redirectTo = req.session.redirectTo ? req.session.redirectTo : '/';
|
||||
delete req.session.redirectTo;
|
||||
console.log(`Redirecting to: '${redirectTo}'`);
|
||||
res.redirect(redirectTo);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(config.EnableWebserver) {
|
||||
//Setup folders
|
||||
app.use(express.static(path.join(__dirname, '/Public')))
|
||||
app.use('/images', express.static(path.join(__dirname, './images')))
|
||||
app.use('/scripts', [isAuthenticated('/login'),express.static(path.join(__dirname, '/scripts'))]);
|
||||
app.use('/', [isAuthenticated('/login'), express.static(path.join(__dirname, '/custom_html'))])
|
||||
}
|
||||
|
||||
try {
|
||||
for (var property in config.AdditionalRoutes) {
|
||||
if (config.AdditionalRoutes.hasOwnProperty(property)) {
|
||||
console.log(`Adding additional routes "${property}" -> "${config.AdditionalRoutes[property]}"`)
|
||||
app.use(property, [isAuthenticated('/login'), express.static(path.join(__dirname, config.AdditionalRoutes[property]))]);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`reading config.AdditionalRoutes: ${err}`)
|
||||
}
|
||||
|
||||
if(config.EnableWebserver) {
|
||||
|
||||
// Request has been sent to site root, send the homepage file
|
||||
app.get('/', isAuthenticated('/login'), function (req, res) {
|
||||
homepageFile = (typeof config.HomepageFile != 'undefined' && config.HomepageFile != '') ? config.HomepageFile.toString() : defaultConfig.HomepageFile;
|
||||
|
||||
let pathsToTry = [ path.join(__dirname, homepageFile), path.join(__dirname, '/Public', homepageFile), path.join(__dirname, '/custom_html', homepageFile), homepageFile ];
|
||||
|
||||
// Try a few paths, see if any resolve to a homepage file the user has set
|
||||
for(let pathToTry of pathsToTry){
|
||||
if(fs.existsSync(pathToTry)){
|
||||
// Send the file for browser to display it
|
||||
res.sendFile(pathToTry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Catch file doesn't exist, and send back 404 if not
|
||||
console.error('Unable to locate file ' + homepageFile)
|
||||
res.status(404).send('Unable to locate file ' + homepageFile);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
//Setup http and https servers
|
||||
http.listen(httpPort, function () {
|
||||
console.logColor(logging.Green, 'Http listening on *: ' + httpPort);
|
||||
});
|
||||
|
||||
if (config.UseHTTPS) {
|
||||
https.listen(httpsPort, function () {
|
||||
console.logColor(logging.Green, 'Https listening on *: ' + httpsPort);
|
||||
});
|
||||
}
|
||||
|
||||
console.logColor(logging.Cyan, `Running Cirrus - The Pixel Streaming reference implementation signalling server for Unreal Engine 5.1.`);
|
||||
|
||||
let nextPlayerId = 100; // reserve some player ids
|
||||
const SFUPlayerId = "1"; // sfu is a special kind of player
|
||||
|
||||
let streamer = null; // WebSocket connected to Streamer
|
||||
let sfu = null; // WebSocket connected to SFU
|
||||
let players = new Map(); // playerId <-> player, where player is either a web-browser or a native webrtc player
|
||||
|
||||
function sfuIsConnected() {
|
||||
return sfu && sfu.readyState == 1;
|
||||
}
|
||||
|
||||
function logIncoming(sourceName, msgType, msg) {
|
||||
if (config.LogVerbose)
|
||||
console.logColor(logging.Blue, "\x1b[37m-> %s\x1b[34m: %s", sourceName, msg);
|
||||
else
|
||||
console.logColor(logging.Blue, "\x1b[37m-> %s\x1b[34m: %s", sourceName, msgType);
|
||||
}
|
||||
|
||||
function logOutgoing(destName, msgType, msg) {
|
||||
if (config.LogVerbose)
|
||||
console.logColor(logging.Green, "\x1b[37m<- %s\x1b[32m: %s", destName, msg);
|
||||
else
|
||||
console.logColor(logging.Green, "\x1b[37m<- %s\x1b[32m: %s", destName, msgType);
|
||||
}
|
||||
|
||||
// normal peer to peer signalling goes to streamer. SFU streaming signalling goes to the sfu
|
||||
function sendMessageToController(msg, skipSFU, skipStreamer = false) {
|
||||
const rawMsg = JSON.stringify(msg);
|
||||
if (sfu && sfu.readyState == 1 && !skipSFU) {
|
||||
logOutgoing("SFU", msg.type, rawMsg);
|
||||
sfu.send(rawMsg);
|
||||
}
|
||||
if (streamer && streamer.readyState == 1 && !skipStreamer) {
|
||||
logOutgoing("Streamer", msg.type, rawMsg);
|
||||
streamer.send(rawMsg);
|
||||
}
|
||||
|
||||
if (!sfu && !streamer) {
|
||||
console.error("sendMessageToController: No streamer or SFU connected!\nMSG: %s", rawMsg);
|
||||
}
|
||||
}
|
||||
|
||||
function sendMessageToPlayer(playerId, msg) {
|
||||
let player = players.get(playerId);
|
||||
if (!player) {
|
||||
console.log(`dropped message ${msg.type} as the player ${playerId} is not found`);
|
||||
return;
|
||||
}
|
||||
const playerName = playerId == SFUPlayerId ? "SFU" : `player ${playerId}`;
|
||||
const rawMsg = JSON.stringify(msg);
|
||||
logOutgoing(playerName, msg.type, rawMsg);
|
||||
player.ws.send(rawMsg);
|
||||
}
|
||||
|
||||
let WebSocket = require('ws');
|
||||
|
||||
console.logColor(logging.Green, `WebSocket listening for Streamer connections on :${streamerPort}`)
|
||||
let streamerServer = new WebSocket.Server({ port: streamerPort, backlog: 1 });
|
||||
streamerServer.on('connection', function (ws, req) {
|
||||
|
||||
// Check if we have an already existing connection to a streamer, if so, deny a new streamer connecting.
|
||||
if(streamer != null){
|
||||
/* We send a 1008 because that a "policy violation", which similar enough to what is happening here. */
|
||||
ws.close(1008, 'Cirrus supports only 1 streamer being connected, already one connected, so dropping this new connection.');
|
||||
console.logColor(logging.Yellow, `Dropping new streamer connection, we already have a connected streamer`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.logColor(logging.Green, `Streamer connected: ${req.connection.remoteAddress}`);
|
||||
sendStreamerConnectedToMatchmaker();
|
||||
|
||||
ws.on('message', (msgRaw) => {
|
||||
|
||||
var msg;
|
||||
try {
|
||||
msg = JSON.parse(msgRaw);
|
||||
} catch(err) {
|
||||
console.error(`cannot parse Streamer message: ${msgRaw}\nError: ${err}`);
|
||||
streamer.close(1008, 'Cannot parse');
|
||||
return;
|
||||
}
|
||||
|
||||
logIncoming("Streamer", msg.type, msgRaw);
|
||||
|
||||
try {
|
||||
// just send pings back to sender
|
||||
if (msg.type == 'ping') {
|
||||
const rawMsg = JSON.stringify({ type: "pong", time: msg.time});
|
||||
logOutgoing("Streamer", msg.type, rawMsg);
|
||||
ws.send(rawMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert incoming playerId to a string if it is an integer, if needed. (We support receiving it as an int or string).
|
||||
let playerId = msg.playerId;
|
||||
if (playerId && typeof playerId === 'number')
|
||||
{
|
||||
playerId = playerId.toString();
|
||||
}
|
||||
delete msg.playerId; // no need to send it to the player
|
||||
|
||||
if (msg.type == 'offer') {
|
||||
sendMessageToPlayer(playerId, msg);
|
||||
} else if (msg.type == 'answer') {
|
||||
sendMessageToPlayer(playerId, msg);
|
||||
} else if (msg.type == 'iceCandidate') {
|
||||
sendMessageToPlayer(playerId, msg);
|
||||
} else if (msg.type == 'disconnectPlayer') {
|
||||
let player = players.get(playerId);
|
||||
if (player) {
|
||||
player.ws.close(1011 /* internal error */, msg.reason);
|
||||
}
|
||||
} else {
|
||||
console.error(`unsupported Streamer message type: ${msg.type}`);
|
||||
streamer.close(1008, 'Unsupported message type');
|
||||
}
|
||||
} catch(err) {
|
||||
console.error(`ERROR: ws.on message error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
function onStreamerDisconnected() {
|
||||
sendStreamerDisconnectedToMatchmaker();
|
||||
disconnectAllPlayers();
|
||||
if (sfuIsConnected()) {
|
||||
const msg = { type: "streamerDisconnected" };
|
||||
sfu.send(JSON.stringify(msg));
|
||||
}
|
||||
streamer = null;
|
||||
}
|
||||
|
||||
ws.on('close', function(code, reason) {
|
||||
console.error(`streamer disconnected: ${code} - ${reason}`);
|
||||
onStreamerDisconnected();
|
||||
});
|
||||
|
||||
ws.on('error', function(error) {
|
||||
console.error(`streamer connection error: ${error}`);
|
||||
onStreamerDisconnected();
|
||||
try {
|
||||
ws.close(1006 /* abnormal closure */, error);
|
||||
} catch(err) {
|
||||
console.error(`ERROR: ws.on error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
streamer = ws;
|
||||
|
||||
streamer.send(JSON.stringify(clientConfig));
|
||||
|
||||
if (sfuIsConnected()) {
|
||||
const msg = { type: "playerConnected", playerId: SFUPlayerId, dataChannel: true, sfu: true };
|
||||
streamer.send(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
|
||||
console.logColor(logging.Green, `WebSocket listening for SFU connections on :${sfuPort}`);
|
||||
let sfuServer = new WebSocket.Server({ port: sfuPort});
|
||||
sfuServer.on('connection', function (ws, req) {
|
||||
// reject if we already have an sfu
|
||||
if (sfuIsConnected()) {
|
||||
ws.close(1013, 'Already have SFU');
|
||||
return;
|
||||
}
|
||||
|
||||
players.set(SFUPlayerId, { ws: ws, id: SFUPlayerId });
|
||||
|
||||
ws.on('message', (msgRaw) => {
|
||||
var msg;
|
||||
try {
|
||||
msg = JSON.parse(msgRaw);
|
||||
} catch (err) {
|
||||
console.error(`cannot parse SFU message: ${msgRaw}\nError: ${err}`);
|
||||
ws.close(1008, 'Cannot parse');
|
||||
return;
|
||||
}
|
||||
|
||||
logIncoming("SFU", msg.type, msgRaw);
|
||||
|
||||
if (msg.type == 'offer') {
|
||||
// offers from the sfu are for players
|
||||
const playerId = msg.playerId;
|
||||
delete msg.playerId;
|
||||
sendMessageToPlayer(playerId, msg);
|
||||
}
|
||||
else if (msg.type == 'answer') {
|
||||
// answers from the sfu are for the streamer
|
||||
msg.playerId = SFUPlayerId;
|
||||
const rawMsg = JSON.stringify(msg);
|
||||
logOutgoing("Streamer", msg.type, rawMsg);
|
||||
streamer.send(rawMsg);
|
||||
}
|
||||
else if (msg.type == 'streamerDataChannels') {
|
||||
// sfu is asking streamer to open a data channel for a connected peer
|
||||
msg.sfuId = SFUPlayerId;
|
||||
const rawMsg = JSON.stringify(msg);
|
||||
logOutgoing("Streamer", msg.type, rawMsg);
|
||||
streamer.send(rawMsg);
|
||||
}
|
||||
else if (msg.type == 'peerDataChannels') {
|
||||
// sfu is telling a peer what stream id to use for a data channel
|
||||
const playerId = msg.playerId;
|
||||
delete msg.playerId;
|
||||
sendMessageToPlayer(playerId, msg);
|
||||
// remember the player has a data channel
|
||||
const player = players.get(playerId);
|
||||
player.datachannel = true;
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', function(code, reason) {
|
||||
console.error(`SFU disconnected: ${code} - ${reason}`);
|
||||
sfu = null;
|
||||
disconnectSFUPlayer();
|
||||
});
|
||||
|
||||
ws.on('error', function(error) {
|
||||
console.error(`SFU connection error: ${error}`);
|
||||
sfu = null;
|
||||
disconnectSFUPlayer();
|
||||
try {
|
||||
ws.close(1006 /* abnormal closure */, error);
|
||||
} catch(err) {
|
||||
console.error(`ERROR: ws.on error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
sfu = ws;
|
||||
console.logColor(logging.Green, `SFU (${req.connection.remoteAddress}) connected `);
|
||||
|
||||
if (streamer && streamer.readyState == 1) {
|
||||
const msg = { type: "playerConnected", playerId: SFUPlayerId, dataChannel: true, sfu: true };
|
||||
streamer.send(JSON.stringify(msg));
|
||||
}
|
||||
});
|
||||
|
||||
let playerCount = 0;
|
||||
|
||||
console.logColor(logging.Green, `WebSocket listening for Players connections on :${httpPort}`);
|
||||
let playerServer = new WebSocket.Server({ server: config.UseHTTPS ? https : http});
|
||||
playerServer.on('connection', function (ws, req) {
|
||||
// Reject connection if streamer is not connected
|
||||
if (!streamer || streamer.readyState != 1 /* OPEN */) {
|
||||
ws.close(1013 /* Try again later */, 'Streamer is not connected');
|
||||
return;
|
||||
}
|
||||
|
||||
var url = require('url');
|
||||
const parsedUrl = url.parse(req.url);
|
||||
const urlParams = new URLSearchParams(parsedUrl.search);
|
||||
const preferSFU = urlParams.has('preferSFU') && urlParams.get('preferSFU') !== 'false';
|
||||
const skipSFU = !preferSFU;
|
||||
const skipStreamer = preferSFU && sfu;
|
||||
|
||||
if(preferSFU && !sfu) {
|
||||
ws.send(JSON.stringify({ type: "warning", warning: "Even though ?preferSFU was specified, there is currently no SFU connected." }));
|
||||
}
|
||||
|
||||
if(playerCount + 1 > maxPlayerCount && maxPlayerCount !== -1)
|
||||
{
|
||||
console.logColor(logging.Red, `new connection would exceed number of allowed concurrent connections. Max: ${maxPlayerCount}, Current ${playerCount}`);
|
||||
ws.close(1013, `too many connections. max: ${maxPlayerCount}, current: ${playerCount}`);
|
||||
return;
|
||||
}
|
||||
|
||||
++playerCount;
|
||||
let playerId = (++nextPlayerId).toString();
|
||||
console.logColor(logging.Green, `player ${playerId} (${req.connection.remoteAddress}) connected`);
|
||||
players.set(playerId, { ws: ws, id: playerId });
|
||||
|
||||
function sendPlayersCount() {
|
||||
let playerCountMsg = JSON.stringify({ type: 'playerCount', count: players.size });
|
||||
for (let p of players.values()) {
|
||||
p.ws.send(playerCountMsg);
|
||||
}
|
||||
}
|
||||
|
||||
ws.on('message', (msgRaw) =>{
|
||||
|
||||
var msg;
|
||||
try {
|
||||
msg = JSON.parse(msgRaw);
|
||||
} catch (err) {
|
||||
console.error(`cannot parse player ${playerId} message: ${msgRaw}\nError: ${err}`);
|
||||
ws.close(1008, 'Cannot parse');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!msg || !msg.type)
|
||||
{
|
||||
console.error(`Cannot parse message ${msgRaw}`);
|
||||
return;
|
||||
}
|
||||
|
||||
logIncoming(`player ${playerId}`, msg.type, msgRaw);
|
||||
|
||||
if (msg.type == 'offer') {
|
||||
msg.playerId = playerId;
|
||||
sendMessageToController(msg, skipSFU);
|
||||
} else if (msg.type == 'answer') {
|
||||
msg.playerId = playerId;
|
||||
sendMessageToController(msg, skipSFU, skipStreamer);
|
||||
} else if (msg.type == 'iceCandidate') {
|
||||
msg.playerId = playerId;
|
||||
sendMessageToController(msg, skipSFU, skipStreamer);
|
||||
} else if (msg.type == 'stats') {
|
||||
console.log(`player ${playerId}: stats\n${msg.data}`);
|
||||
} else if (msg.type == "dataChannelRequest") {
|
||||
msg.playerId = playerId;
|
||||
sendMessageToController(msg, skipSFU, true);
|
||||
} else if (msg.type == "peerDataChannelsReady") {
|
||||
msg.playerId = playerId;
|
||||
sendMessageToController(msg, skipSFU, true);
|
||||
}
|
||||
else {
|
||||
console.error(`player ${playerId}: unsupported message type: ${msg.type}`);
|
||||
ws.close(1008, 'Unsupported message type');
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
function onPlayerDisconnected() {
|
||||
try {
|
||||
--playerCount;
|
||||
const player = players.get(playerId);
|
||||
if (player.datachannel) {
|
||||
// have to notify the streamer that the datachannel can be closed
|
||||
sendMessageToController({ type: 'playerDisconnected', playerId: playerId }, true, false);
|
||||
}
|
||||
players.delete(playerId);
|
||||
sendMessageToController({ type: 'playerDisconnected', playerId: playerId }, skipSFU);
|
||||
sendPlayerDisconnectedToFrontend();
|
||||
sendPlayerDisconnectedToMatchmaker();
|
||||
sendPlayersCount();
|
||||
} catch(err) {
|
||||
console.logColor(logging.Red, `ERROR:: onPlayerDisconnected error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
ws.on('close', function(code, reason) {
|
||||
console.logColor(logging.Yellow, `player ${playerId} connection closed: ${code} - ${reason}`);
|
||||
onPlayerDisconnected();
|
||||
});
|
||||
|
||||
ws.on('error', function(error) {
|
||||
console.error(`player ${playerId} connection error: ${error}`);
|
||||
ws.close(1006 /* abnormal closure */, error);
|
||||
onPlayerDisconnected();
|
||||
|
||||
console.logColor(logging.Red, `Trying to reconnect...`);
|
||||
reconnect();
|
||||
});
|
||||
|
||||
sendPlayerConnectedToFrontend();
|
||||
sendPlayerConnectedToMatchmaker();
|
||||
|
||||
ws.send(JSON.stringify(clientConfig));
|
||||
|
||||
sendMessageToController({ type: "playerConnected", playerId: playerId, dataChannel: true, sfu: false }, skipSFU, skipStreamer);
|
||||
sendPlayersCount();
|
||||
});
|
||||
|
||||
function disconnectAllPlayers(code, reason) {
|
||||
console.log("killing all players");
|
||||
let clone = new Map(players);
|
||||
for (let player of clone.values()) {
|
||||
if (player.id != SFUPlayerId) { // dont dc the sfu
|
||||
player.ws.close(code, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectSFUPlayer() {
|
||||
console.log("disconnecting SFU from streamer");
|
||||
if(players.has(SFUPlayerId)) {
|
||||
players.get(SFUPlayerId).ws.close(4000, "SFU Disconnected");
|
||||
players.delete(SFUPlayerId);
|
||||
}
|
||||
sendMessageToController({ type: 'playerDisconnected', playerId: SFUPlayerId }, true, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles the connection to the matchmaker.
|
||||
*/
|
||||
|
||||
if (config.UseMatchmaker) {
|
||||
var matchmaker = new net.Socket();
|
||||
|
||||
matchmaker.on('connect', function() {
|
||||
console.log(`Cirrus connected to Matchmaker ${matchmakerAddress}:${matchmakerPort}`);
|
||||
|
||||
// message.playerConnected is a new variable sent from the SS to help track whether or not a player
|
||||
// is already connected when a 'connect' message is sent (i.e., reconnect). This happens when the MM
|
||||
// and the SS get disconnected unexpectedly (was happening often at scale for some reason).
|
||||
var playerConnected = false;
|
||||
|
||||
// Set the playerConnected flag to tell the MM if there is already a player active (i.e., don't send a new one here)
|
||||
if( players && players.size > 0) {
|
||||
playerConnected = true;
|
||||
}
|
||||
|
||||
// Add the new playerConnected flag to the message body to the MM
|
||||
message = {
|
||||
type: 'connect',
|
||||
address: typeof serverPublicIp === 'undefined' ? '127.0.0.1' : serverPublicIp,
|
||||
port: httpPort,
|
||||
ready: streamer && streamer.readyState === 1,
|
||||
playerConnected: playerConnected
|
||||
};
|
||||
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
});
|
||||
|
||||
matchmaker.on('error', (err) => {
|
||||
console.log(`Matchmaker connection error ${JSON.stringify(err)}`);
|
||||
});
|
||||
|
||||
matchmaker.on('end', () => {
|
||||
console.log('Matchmaker connection ended');
|
||||
});
|
||||
|
||||
matchmaker.on('close', (hadError) => {
|
||||
console.logColor(logging.Blue, 'Setting Keep Alive to true');
|
||||
matchmaker.setKeepAlive(true, 60000); // Keeps it alive for 60 seconds
|
||||
|
||||
console.log(`Matchmaker connection closed (hadError=${hadError})`);
|
||||
|
||||
reconnect();
|
||||
});
|
||||
|
||||
// Attempt to connect to the Matchmaker
|
||||
function connect() {
|
||||
matchmaker.connect(matchmakerPort, matchmakerAddress);
|
||||
}
|
||||
|
||||
// Try to reconnect to the Matchmaker after a given period of time
|
||||
function reconnect() {
|
||||
console.log(`Try reconnect to Matchmaker in ${matchmakerRetryInterval} seconds`)
|
||||
setTimeout(function() {
|
||||
connect();
|
||||
}, matchmakerRetryInterval * 1000);
|
||||
}
|
||||
|
||||
function registerMMKeepAlive() {
|
||||
setInterval(function() {
|
||||
message = {
|
||||
type: 'ping'
|
||||
};
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
}, matchmakerKeepAliveInterval * 1000);
|
||||
}
|
||||
|
||||
connect();
|
||||
registerMMKeepAlive();
|
||||
}
|
||||
|
||||
//Keep trying to send gameSessionId in case the server isn't ready yet
|
||||
function sendGameSessionData() {
|
||||
//If we are not using the frontend web server don't try and make requests to it
|
||||
if (!config.UseFrontend)
|
||||
return;
|
||||
webRequest.get(`${FRONTEND_WEBSERVER}/server/requestSessionId`,
|
||||
function (response, body) {
|
||||
if (response.statusCode === 200) {
|
||||
gameSessionId = body;
|
||||
console.log('SessionId: ' + gameSessionId);
|
||||
}
|
||||
else {
|
||||
console.error('Status code: ' + response.statusCode);
|
||||
console.error(body);
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
//Repeatedly try in cases where the connection timed out or never connected
|
||||
if (err.code === "ECONNRESET") {
|
||||
//timeout
|
||||
sendGameSessionData();
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Frontend server not running, unable to setup game session');
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendUserSessionData(serverPort) {
|
||||
//If we are not using the frontend web server don't try and make requests to it
|
||||
if (!config.UseFrontend)
|
||||
return;
|
||||
webRequest.get(`${FRONTEND_WEBSERVER}/server/requestUserSessionId?gameSessionId=${gameSessionId}&serverPort=${serverPort}&appName=${querystring.escape(clientConfig.AppName)}&appDescription=${querystring.escape(clientConfig.AppDescription)}${(typeof serverPublicIp === 'undefined' ? '' : '&serverHost=' + serverPublicIp)}`,
|
||||
function (response, body) {
|
||||
if (response.statusCode === 410) {
|
||||
sendUserSessionData(serverPort);
|
||||
} else if (response.statusCode === 200) {
|
||||
userSessionId = body;
|
||||
console.log('UserSessionId: ' + userSessionId);
|
||||
} else {
|
||||
console.error('Status code: ' + response.statusCode);
|
||||
console.error(body);
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
//Repeatedly try in cases where the connection timed out or never connected
|
||||
if (err.code === "ECONNRESET") {
|
||||
//timeout
|
||||
sendUserSessionData(serverPort);
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Frontend server not running, unable to setup user session');
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendServerDisconnect() {
|
||||
//If we are not using the frontend web server don't try and make requests to it
|
||||
if (!config.UseFrontend)
|
||||
return;
|
||||
try {
|
||||
webRequest.get(`${FRONTEND_WEBSERVER}/server/serverDisconnected?gameSessionId=${gameSessionId}&appName=${querystring.escape(clientConfig.AppName)}`,
|
||||
function (response, body) {
|
||||
if (response.statusCode === 200) {
|
||||
console.log('serverDisconnected acknowledged by Frontend');
|
||||
} else {
|
||||
console.error('Status code: ' + response.statusCode);
|
||||
console.error(body);
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
//Repeatedly try in cases where the connection timed out or never connected
|
||||
if (err.code === "ECONNRESET") {
|
||||
//timeout
|
||||
sendServerDisconnect();
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Frontend server not running, unable to setup user session');
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
console.logColor(logging.Red, `ERROR::: sendServerDisconnect error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendPlayerConnectedToFrontend() {
|
||||
//If we are not using the frontend web server don't try and make requests to it
|
||||
if (!config.UseFrontend)
|
||||
return;
|
||||
try {
|
||||
webRequest.get(`${FRONTEND_WEBSERVER}/server/clientConnected?gameSessionId=${gameSessionId}&appName=${querystring.escape(clientConfig.AppName)}`,
|
||||
function (response, body) {
|
||||
if (response.statusCode === 200) {
|
||||
console.log('clientConnected acknowledged by Frontend');
|
||||
} else {
|
||||
console.error('Status code: ' + response.statusCode);
|
||||
console.error(body);
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
//Repeatedly try in cases where the connection timed out or never connected
|
||||
if (err.code === "ECONNRESET") {
|
||||
//timeout
|
||||
sendPlayerConnectedToFrontend();
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Frontend server not running, unable to setup game session');
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
console.logColor(logging.Red, `ERROR::: sendPlayerConnectedToFrontend error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendPlayerDisconnectedToFrontend() {
|
||||
//If we are not using the frontend web server don't try and make requests to it
|
||||
if (!config.UseFrontend)
|
||||
return;
|
||||
try {
|
||||
webRequest.get(`${FRONTEND_WEBSERVER}/server/clientDisconnected?gameSessionId=${gameSessionId}&appName=${querystring.escape(clientConfig.AppName)}`,
|
||||
function (response, body) {
|
||||
if (response.statusCode === 200) {
|
||||
console.log('clientDisconnected acknowledged by Frontend');
|
||||
}
|
||||
else {
|
||||
console.error('Status code: ' + response.statusCode);
|
||||
console.error(body);
|
||||
}
|
||||
},
|
||||
function (err) {
|
||||
//Repeatedly try in cases where the connection timed out or never connected
|
||||
if (err.code === "ECONNRESET") {
|
||||
//timeout
|
||||
sendPlayerDisconnectedToFrontend();
|
||||
} else if (err.code === 'ECONNREFUSED') {
|
||||
console.error('Frontend server not running, unable to setup game session');
|
||||
} else {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
} catch(err) {
|
||||
console.logColor(logging.Red, `ERROR::: sendPlayerDisconnectedToFrontend error: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendStreamerConnectedToMatchmaker() {
|
||||
if (!config.UseMatchmaker)
|
||||
return;
|
||||
try {
|
||||
message = {
|
||||
type: 'streamerConnected'
|
||||
};
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
} catch (err) {
|
||||
console.logColor(logging.Red, `ERROR sending streamerConnected: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function sendStreamerDisconnectedToMatchmaker() {
|
||||
if (!config.UseMatchmaker)
|
||||
return;
|
||||
try {
|
||||
message = {
|
||||
type: 'streamerDisconnected'
|
||||
};
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
} catch (err) {
|
||||
console.logColor(logging.Red, `ERROR sending streamerDisconnected: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// The Matchmaker will not re-direct clients to this Cirrus server if any client
|
||||
// is connected.
|
||||
function sendPlayerConnectedToMatchmaker() {
|
||||
if (!config.UseMatchmaker)
|
||||
return;
|
||||
try {
|
||||
message = {
|
||||
type: 'clientConnected'
|
||||
};
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
} catch (err) {
|
||||
console.logColor(logging.Red, `ERROR sending clientConnected: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// The Matchmaker is interested when nobody is connected to a Cirrus server
|
||||
// because then it can re-direct clients to this re-cycled Cirrus server.
|
||||
function sendPlayerDisconnectedToMatchmaker() {
|
||||
if (!config.UseMatchmaker)
|
||||
return;
|
||||
try {
|
||||
message = {
|
||||
type: 'clientDisconnected'
|
||||
};
|
||||
matchmaker.write(JSON.stringify(message));
|
||||
} catch (err) {
|
||||
console.logColor(logging.Red, `ERROR sending clientDisconnected: ${err.message}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"UseFrontend": false,
|
||||
"UseMatchmaker": false,
|
||||
"UseHTTPS": false,
|
||||
"UseAuthentication": false,
|
||||
"LogToFile": true,
|
||||
"LogVerbose": true,
|
||||
"HomepageFile": "player.html",
|
||||
"AdditionalRoutes": {},
|
||||
"EnableWebserver": true,
|
||||
"MatchmakerAddress": "",
|
||||
"MatchmakerPort": 9999,
|
||||
"PublicIp": "localhost",
|
||||
"HttpPort": 80,
|
||||
"HttpsPort": 443,
|
||||
"StreamerPort": 8888,
|
||||
"SFUPort": 8889,
|
||||
"MaxPlayerCount": -1
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
exports.users = require('./users');
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
//
|
||||
// Usage: npm run store_password -- --username <USERNAME> --password <PASSWORD>
|
||||
// or from ./modules/authentication/db dir: node store_password.js --username <USERNAME> --password <PASSWORD>
|
||||
//
|
||||
// --usersFile is an optional parameter that can be used to specify a different location for the users database file
|
||||
// use this if running the command from a different working dir. The default location is './users.json'
|
||||
// e.g. If running from the SignallingWebServer dir use: --usersFile ./modules/authentication/db/users.json
|
||||
|
||||
const argv = require('yargs').argv;
|
||||
const fs = require('fs');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
var username, password;
|
||||
var usersFile = './users.json'
|
||||
|
||||
const STORE_PLAINTEXT_PASSWORD = false;
|
||||
|
||||
try {
|
||||
if(typeof argv.username != 'undefined'){
|
||||
username = argv.username.toString();
|
||||
}
|
||||
|
||||
if(typeof argv.password != 'undefined'){
|
||||
password = argv.password;
|
||||
}
|
||||
|
||||
if(typeof argv.usersFile != 'undefined'){
|
||||
usersFile = argv.usersFile;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
if(username && password){
|
||||
let existingAccounts = [];
|
||||
if (fs.existsSync(usersFile)) {
|
||||
console.log(`File '${usersFile}' exists, reading file`)
|
||||
var content = fs.readFileSync(usersFile, 'utf8');
|
||||
try{
|
||||
existingAccounts = JSON.parse(content);
|
||||
}
|
||||
catch(e){
|
||||
console.error(`Existing file '${usersFile}', has invalid JSON: ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
var existingUser = existingAccounts.find( u => u.username == username)
|
||||
if(existingUser){
|
||||
console.log(`User '${username}', already exists, updating password`)
|
||||
existingUser.passwordHash = generatePasswordHash(password)
|
||||
if(STORE_PLAINTEXT_PASSWORD)
|
||||
existingUser.password = password;
|
||||
else if (existingUser.password)
|
||||
delete existingUser.password;
|
||||
|
||||
} else {
|
||||
console.log(`Adding new user '${username}'`)
|
||||
let newUser = {
|
||||
id: existingAccounts.length + 1,
|
||||
username: username,
|
||||
passwordHash: generatePasswordHash(password)
|
||||
}
|
||||
if(STORE_PLAINTEXT_PASSWORD)
|
||||
newUser.password = password;
|
||||
|
||||
existingAccounts.push(newUser);
|
||||
}
|
||||
|
||||
console.log(`Writing updated users to '${usersFile}'`);
|
||||
var newContent = JSON.stringify(existingAccounts);
|
||||
fs.writeFileSync(usersFile, newContent);
|
||||
} else {
|
||||
console.log(`Please pass in both username (${username}) and password (${password}) please`);
|
||||
}
|
||||
|
||||
function generatePasswordHash(pass){
|
||||
return bcrypt.hashSync(pass, 12)
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Read in users from file
|
||||
let records = [];
|
||||
let usersFile = path.join(__dirname, './users.json');
|
||||
if (fs.existsSync(usersFile)) {
|
||||
console.log(`Reading users from '${usersFile}'`)
|
||||
var content = fs.readFileSync(usersFile, 'utf8');
|
||||
try {
|
||||
records = JSON.parse(content);
|
||||
} catch(e) {
|
||||
console.log(`ERROR: Failed to parse users from file '${usersFile}'`)
|
||||
}
|
||||
}
|
||||
|
||||
exports.findById = function(id, cb) {
|
||||
var idx = id - 1;
|
||||
if (records[idx]) {
|
||||
cb(null, records[idx]);
|
||||
} else {
|
||||
cb(new Error('User ' + id + ' does not exist'));
|
||||
}
|
||||
}
|
||||
|
||||
exports.findByUsername = function(username, cb) {
|
||||
for (var i = 0, len = records.length; i < len; i++) {
|
||||
var record = records[i];
|
||||
if (record.username === username) {
|
||||
return cb(null, record);
|
||||
}
|
||||
}
|
||||
return cb(null, null);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
module.exports = {
|
||||
init: require('./init')
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
// Adapted from
|
||||
// * https://blog.risingstack.com/node-hero-node-js-authentication-passport-js/
|
||||
// * https://github.com/RisingStack/nodehero-authentication/tree/master/app
|
||||
// * https://github.com/passport/express-4.x-local-example
|
||||
|
||||
|
||||
const passport = require('passport');
|
||||
const session = require('express-session');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const LocalStrategy = require('passport-local').Strategy;
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
var db = require('./db');
|
||||
|
||||
function initPassport (app) {
|
||||
|
||||
// Generate session secret if it doesn't already exist and save it to file for use next time
|
||||
let config = {};
|
||||
let configPath = path.join(__dirname, './config.json');
|
||||
if (fs.existsSync(configPath)) {
|
||||
let content = fs.readFileSync(configPath, 'utf8');
|
||||
try {
|
||||
config = JSON.parse(content);
|
||||
} catch (e) {
|
||||
console.log(`Error with config file '${configPath}': ${e}`);
|
||||
}
|
||||
}
|
||||
|
||||
if(!config.sessionSecret){
|
||||
config.sessionSecret = bcrypt.genSaltSync(12);
|
||||
let content = JSON.stringify(config);
|
||||
fs.writeFileSync(configPath, content);
|
||||
}
|
||||
|
||||
// Setup session id settings
|
||||
app.use(session({
|
||||
secret: config.sessionSecret,
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
cookie: {
|
||||
secure: true,
|
||||
maxAge: 24 * 60 * 60 * 1000 /* 1 day */
|
||||
//maxAge: 5 * 1000 /* 5 seconds */
|
||||
}
|
||||
}));
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.serializeUser(function(user, cb) {
|
||||
cb(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, cb) {
|
||||
db.users.findById(id, function (err, user) {
|
||||
if (err) { return cb(err); }
|
||||
cb(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('Setting up auth');
|
||||
passport.use(new LocalStrategy(
|
||||
(username, password, callback) => {
|
||||
db.users.findByUsername(username, (err, user) => {
|
||||
if (err) {
|
||||
console.log(`Unable to login '${username}', error ${err}`);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// User not found
|
||||
if (!user) {
|
||||
console.log(`User '${username}' not found`);
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
// Always use hashed passwords and fixed time comparison
|
||||
bcrypt.compare(password, user.passwordHash, (err, isValid) => {
|
||||
if (err) {
|
||||
console.log(`Error comparing password for user '${username}': ${err}`);
|
||||
return callback(err);
|
||||
}
|
||||
if (!isValid) {
|
||||
console.log(`Password incorrect for user '${username}'`)
|
||||
return callback(null, false);
|
||||
}
|
||||
|
||||
console.log(`User '${username}' logged in`);
|
||||
return callback(null, user);
|
||||
});
|
||||
})
|
||||
}
|
||||
));
|
||||
|
||||
passport.authenticationMiddleware = function authenticationMiddleware (redirectUrl) {
|
||||
return function (req, res, next) {
|
||||
if (req.isAuthenticated()) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// Set redirectTo property so that user can be redirected back there after logging in
|
||||
//console.log(`Original request path '${req.originalUrl}'`);
|
||||
req.session.redirectTo = req.originalUrl;
|
||||
res.redirect(redirectUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = initPassport;
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
//-- Provides configuration information from file and combines it with default values and command line arguments --//
|
||||
//-- Hierachy of values: Default Values < Config File < Command Line arguments --//
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const argv = require('yargs').argv;
|
||||
|
||||
function initConfig(configFile, defaultConfig){
|
||||
defaultConfig = defaultConfig || {};
|
||||
|
||||
// Using object spread syntax: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals
|
||||
let config = {...defaultConfig};
|
||||
try {
|
||||
let configData = fs.readFileSync(configFile, 'UTF8');
|
||||
fileConfig = JSON.parse(configData);
|
||||
config = {...config, ...fileConfig}
|
||||
|
||||
try {
|
||||
accessSync('configFile', constants.W_OK);
|
||||
// Update config file with any additional defaults (does not override existing values if default has changed)
|
||||
fs.writeFileSync(configFile, JSON.stringify(config, null, '\t'), 'UTF8');
|
||||
} catch (err) {
|
||||
console.log("Config file is readonly, skipping writing config...");
|
||||
}
|
||||
|
||||
} catch(err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
console.log("No config file found, writing defaults to log file " + configFile);
|
||||
fs.writeFileSync(configFile, JSON.stringify(config, null, '\t'), 'UTF8');
|
||||
} else if (err instanceof SyntaxError) {
|
||||
console.log(`ERROR: Invalid JSON in ${configFile}, ignoring file config, ${err}`)
|
||||
} else {
|
||||
console.log(`ERROR: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
//Make a copy of the command line args and remove the unneccessary ones
|
||||
//The _ value is an array of any elements without a key
|
||||
let commandLineConfig = {...argv}
|
||||
delete commandLineConfig._;
|
||||
delete commandLineConfig.help;
|
||||
delete commandLineConfig.version;
|
||||
delete commandLineConfig['$0'];
|
||||
config = {...config, ...commandLineConfig}
|
||||
} catch(err) {
|
||||
console.log(`ERROR: ${err}`);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
init: initConfig
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
var querystring = require('querystring')
|
||||
const https = require('https');
|
||||
const assert = require('assert');
|
||||
|
||||
function cleanUrl(aUrl){
|
||||
let url = aUrl;
|
||||
if(url.startsWith("https://"))
|
||||
url = url.substring("https://".length);
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
function createOptions(requestType, url){
|
||||
let index = url.indexOf('/');
|
||||
|
||||
let urlParts = url.split('/', 2)
|
||||
|
||||
return {
|
||||
hostname: (index === -1) ? url.substring(0) : url.substring(0, index),
|
||||
port: 443,
|
||||
path: (index === -1) ? '' : url.substring(index),
|
||||
method: requestType,
|
||||
timeout: 30000,
|
||||
};
|
||||
}
|
||||
|
||||
function makeHttpsCall(options, aCallback, aError){
|
||||
//console.log(JSON.stringify(options));
|
||||
const req = https.request(options, function(response){
|
||||
let data = '';
|
||||
|
||||
//console.log('statusCode:', response.statusCode);
|
||||
//console.log('headers:', response.headers);
|
||||
|
||||
// A chunk of data has been received.
|
||||
response.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
// The whole response has been received. Print out the result.
|
||||
response.on('end', () => {
|
||||
if(typeof aCallback != "undefined")
|
||||
aCallback(response, data);
|
||||
});
|
||||
});
|
||||
|
||||
req.on('timeout', function () {
|
||||
console.log("Request timed out. " + (options.timeout / 1000) + " seconds expired");
|
||||
|
||||
// Source: https://github.com/nodejs/node/blob/master/test/parallel/test-http-client-timeout-option.js#L27
|
||||
req.destroy();
|
||||
});
|
||||
|
||||
req.on("error", (err) => {
|
||||
if(typeof aError != "undefined") {
|
||||
aError(err);
|
||||
} else {
|
||||
console.log("Error: " + err.message);
|
||||
}
|
||||
});
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
module.exports = class HttpClient {
|
||||
get(aUrl, aCallback, aError) {
|
||||
let url = cleanUrl(aUrl);
|
||||
|
||||
let options = createOptions('GET', url);
|
||||
|
||||
const req = makeHttpsCall(options, aCallback, aError);
|
||||
|
||||
req.end();
|
||||
}
|
||||
|
||||
post(aUrl, body, aCallback, aError) {
|
||||
let url = cleanUrl(aUrl);
|
||||
|
||||
let options = createOptions('POST', url);
|
||||
|
||||
let postBody = querystring.stringify(body);
|
||||
|
||||
//Add extra options for POST request type
|
||||
options.headers = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': postBody.length
|
||||
};
|
||||
|
||||
const req = makeHttpsCall(options, aCallback, aError);
|
||||
|
||||
req.write(postBody);
|
||||
req.end();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
const fs = require('fs');
|
||||
const { Console } = require('console');
|
||||
|
||||
var loggers=[];
|
||||
var logFunctions=[];
|
||||
var logColorFunctions=[];
|
||||
|
||||
console.log = function(msg, ...args) {
|
||||
logFunctions.forEach((logFunction) => {
|
||||
logFunction(msg, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
console.logColor = function(color, msg, ...args) {
|
||||
logColorFunctions.forEach((logColorFunction) => {
|
||||
logColorFunction(color, msg, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
const AllAttributesOff = '\x1b[0m';
|
||||
const BoldOn = '\x1b[1m';
|
||||
const Black = '\x1b[30m';
|
||||
const Red = '\x1b[31m';
|
||||
const Green = '\x1b[32m';
|
||||
const Yellow = '\x1b[33m';
|
||||
const Blue = '\x1b[34m';
|
||||
const Magenta = '\x1b[35m';
|
||||
const Cyan = '\x1b[36m';
|
||||
const White = '\x1b[37m';
|
||||
|
||||
/**
|
||||
* Pad the start of the given number with zeros so it takes up the number of digits.
|
||||
* e.g. zeroPad(5, 3) = '005' and zeroPad(23, 2) = '23'.
|
||||
*/
|
||||
function zeroPad(number, digits) {
|
||||
let string = number.toString();
|
||||
while (string.length < digits) {
|
||||
string = '0' + string;
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string of the form 'YEAR.MONTH.DATE.HOURS.MINUTES.SECONDS'.
|
||||
*/
|
||||
function dateTimeToString() {
|
||||
let date = new Date();
|
||||
return `${date.getFullYear()}.${zeroPad(date.getMonth(), 2)}.${zeroPad(date.getDate(), 2)}.${zeroPad(date.getHours(), 2)}.${zeroPad(date.getMinutes(), 2)}.${zeroPad(date.getSeconds(), 2)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a string of the form 'HOURS.MINUTES.SECONDS.MILLISECONDS'.
|
||||
*/
|
||||
function timeToString() {
|
||||
let date = new Date();
|
||||
return `${zeroPad(date.getHours(), 2)}:${zeroPad(date.getMinutes(), 2)}:${zeroPad(date.getSeconds(), 2)}.${zeroPad(date.getMilliseconds(), 3)}`;
|
||||
}
|
||||
|
||||
function RegisterFileLogger(path) {
|
||||
if(path == null)
|
||||
path = './logs/';
|
||||
|
||||
if (!fs.existsSync(path))
|
||||
fs.mkdirSync(path);
|
||||
|
||||
var output = fs.createWriteStream(`${path}${dateTimeToString()}.log`);
|
||||
var fileLogger = new Console(output);
|
||||
logFunctions.push(function(msg, ...args) {
|
||||
fileLogger.log(`${timeToString()} ${msg}`, ...args);
|
||||
});
|
||||
|
||||
logColorFunctions.push(function(color, msg, ...args) {
|
||||
fileLogger.log(`${timeToString()} ${msg}`, ...args);
|
||||
});
|
||||
loggers.push(fileLogger);
|
||||
}
|
||||
|
||||
function RegisterConsoleLogger() {
|
||||
var consoleLogger = new Console(process.stdout, process.stderr)
|
||||
logFunctions.push(function(msg, ...args) {
|
||||
consoleLogger.log(`${timeToString()} ${msg}`, ...args);
|
||||
});
|
||||
|
||||
logColorFunctions.push(function(color, msg, ...args) {
|
||||
consoleLogger.log(`${BoldOn}${color}${timeToString()} ${msg}${AllAttributesOff}`, ...args);
|
||||
});
|
||||
loggers.push(consoleLogger);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
//Functions
|
||||
RegisterFileLogger,
|
||||
RegisterConsoleLogger,
|
||||
|
||||
//Variables
|
||||
AllAttributesOff,
|
||||
BoldOn,
|
||||
Black,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "cirrus-webserver",
|
||||
"version": "0.0.1",
|
||||
"description": "cirrus web server",
|
||||
"scripts": {
|
||||
"store_password": "run-script-os",
|
||||
"store_password:default": "./platform_scripts/bash/node/bin/node ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json",
|
||||
"store_password:windows": "platform_scripts\\cmd\\node\\node.exe ./modules/authentication/db/store_password.js --usersFile=./modules/authentication/db/users.json",
|
||||
"start-local": "run-script-os --",
|
||||
"start-local:default": "./platform_scripts/bash/Start_Local.sh",
|
||||
"start-local:windows": ".\\platform_scripts\\cmd\\Start_Local.bat",
|
||||
"start-signalling-server": "run-script-os --",
|
||||
"start-signalling-server:default": "./platform_scripts/bash/Start_SignallingServer.sh",
|
||||
"start-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_SignallingServer.bat",
|
||||
"start-with-turn-signalling-server": "run-script-os --",
|
||||
"start-with-turn-signalling-server:default": "./platform_scripts/bash/Start_WithTurn_SignallingServer.sh",
|
||||
"start-wiht-turn-signalling-server:windows": ".\\platform_scripts\\cmd\\Start_WithTurn_SignallingServer.bat",
|
||||
"start": "run-script-os",
|
||||
"start:default": "if [ `id -u` -eq 0 ] || [ ! -z $NO_SUDO ]\nthen\n export process=\"./platform_scripts/bash/node/bin/node cirrus.js\"\nelse\n export process=\"sudo ./platform_scripts/bash/node/bin/node cirrus.js\"\nfi\n$process ",
|
||||
"start:windows": "platform_scripts\\cmd\\node\\node.exe cirrus.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"bcryptjs": "^2.4.3",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.15.6",
|
||||
"helmet": "^3.21.3",
|
||||
"passport": "^0.6.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"run-script-os": "^1.1.6",
|
||||
"ws": "^7.1.2",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs": "^15.3.0"
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
FROM node:latest
|
||||
|
||||
# Copy the signalling server source code to the Docker build context
|
||||
COPY . /opt/SignallingWebServer
|
||||
|
||||
# Install the dependencies for the signalling server
|
||||
WORKDIR /opt/SignallingWebServer
|
||||
RUN npm install .
|
||||
|
||||
# Expose TCP port 80 for player WebSocket connections and web server HTTP access
|
||||
EXPOSE 80
|
||||
|
||||
# Expose TCP port 8888 for streamer WebSocket connections
|
||||
EXPOSE 8888
|
||||
EXPOSE 8888/udp
|
||||
|
||||
# Expose port for SFU connections
|
||||
EXPOSE 8889
|
||||
|
||||
# Google stun
|
||||
EXPOSE 19302
|
||||
|
||||
# Matchmaker
|
||||
EXPOSE 9999
|
||||
|
||||
# Turn coturn
|
||||
EXPOSE 3478
|
||||
EXPOSE 3479
|
||||
|
||||
# Set the signalling server as the container's entrypoint
|
||||
ENTRYPOINT ["/usr/local/bin/node", "/opt/SignallingWebServer/cirrus.js"]
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
How to use files in this directory:
|
||||
- Make sure that all of your dependencies are installed. Use ./setup.sh what will install whatever is missing as long as you are on a supported operating system. Please note that setup.sh is called from every script designed to run
|
||||
|
||||
- Run a local instance of the Cirrus server by using the ./run_local.sh script
|
||||
|
||||
- Use the following scripts to run locally or in your cloud instance:
|
||||
- Start_SignallingServer.sh - Start only the Signalling (STUN) server
|
||||
- Start_TURNServer.sh - Start only the TURN server
|
||||
- Start_WithTURN_SignallingServer.sh - Start a TURN server and the Cirrus server together
|
||||
|
||||
- Please note that scripts intended to run need to be executable: $ chmod +x *.sh will do that job.
|
||||
- The local/cloud Start_*.sh shell scripts can be invoked with the --help command line option to see how those can be configured. The following options can be supplied: --publicip, --turn, --stun. Please read the --help
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
pushd "${BASH_LOCATION}" > /dev/null
|
||||
|
||||
source common_utils.sh
|
||||
|
||||
set_start_default_values "n" "y" # Set STUN server defaults only
|
||||
use_args "$@"
|
||||
call_setup_sh
|
||||
print_parameters
|
||||
|
||||
peerconnectionoptions='{\"iceServers\":[{\"urls\":[\"stun:${stunserver}\"]}]}'
|
||||
|
||||
process="${BASH_LOCATION}/node/lib/node_modules/npm/bin/npm-cli.js run start:default --"
|
||||
arguments=""
|
||||
|
||||
if [ ! -z $IS_DEBUG ]; then
|
||||
arguments+=" --inspect"
|
||||
fi
|
||||
|
||||
arguments+=" --peerConnectionOptions=\"${peerconnectionoptions}\" --PublicIp=${publicip}"
|
||||
# Add arguments passed to script to arguments for executable
|
||||
arguments+=" ${cirruscmd}"
|
||||
|
||||
pushd ../..
|
||||
echo "Running: $process $arguments"
|
||||
PATH="${BASH_LOCATION}/node/bin:$PATH"
|
||||
start_process $process $arguments
|
||||
popd
|
||||
|
||||
popd
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
pushd "${BASH_LOCATION}" > /dev/null
|
||||
|
||||
source turn_user_pwd.sh
|
||||
source common_utils.sh
|
||||
|
||||
set_start_default_values "y" "n" # TURN server defaults only
|
||||
use_args "$@"
|
||||
call_setup_sh
|
||||
print_parameters
|
||||
|
||||
localip=$(hostname -I | awk '{print $1}')
|
||||
echo "Private IP: $localip"
|
||||
|
||||
turnport="${turnserver##*:}"
|
||||
if [ -z "${turnport}" ]; then
|
||||
turnport=3478
|
||||
fi
|
||||
echo "TURN port: ${turnport}"
|
||||
echo ""
|
||||
|
||||
|
||||
# Hmm, plain text
|
||||
realm="PixelStreaming"
|
||||
process="turnserver"
|
||||
arguments="-p ${turnport} -r $realm -X $publicip -E $localip -L $localip --no-cli --no-tls --no-dtls --pidfile /var/run/turnserver.pid -f -a -v -n -u ${turnusername}:${turnpassword}"
|
||||
|
||||
# Add arguments passed to script to arguments for executable
|
||||
arguments+=" ${cirruscmd}"
|
||||
|
||||
pushd ../.. >/dev/null
|
||||
echo "Running: $process $arguments"
|
||||
# pause
|
||||
start_process $process $arguments &
|
||||
popd >/dev/null # ../..
|
||||
|
||||
popd >/dev/null # BASH_SOURCE
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
pushd "${BASH_LOCATION}" > /dev/null
|
||||
|
||||
source common_utils.sh
|
||||
|
||||
set_start_default_values "y" "y" # Set both TURN and STUN server defaults
|
||||
use_args "$@"
|
||||
call_setup_sh
|
||||
print_parameters
|
||||
|
||||
bash Start_TURNServer.sh --turn "${turnserver}"
|
||||
|
||||
peerconnectionoptions='{\"iceServers\":[{\"urls\":[\"stun:$stunserver\",\"turn:$turnserver\"],\"username\":\"PixelStreamingUser\",\"credential\":\"AnotherTURNintheroad\"}]}'
|
||||
|
||||
process="${BASH_LOCATION}/node/lib/node_modules/npm/bin/npm-cli.js run start:default --"
|
||||
arguments=""
|
||||
|
||||
if [ ! -z $IS_DEBUG ]; then
|
||||
arguments+=" --inspect"
|
||||
fi
|
||||
|
||||
arguments+=" --peerConnectionOptions=\"$peerconnectionoptions\" --PublicIp=$publicip"
|
||||
# Add arguments passed to script to arguments for executable
|
||||
arguments+=" ${cirruscmd}"
|
||||
|
||||
pushd ../..
|
||||
echo "Running: $process $arguments"
|
||||
PATH="${BASH_LOCATION}/node/bin:$PATH"
|
||||
start_process $process $arguments
|
||||
popd
|
||||
|
||||
popd
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
function log_msg() { #message
|
||||
if [ ! -z $VERBOSE ]; then
|
||||
echo $1
|
||||
fi
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
echo "
|
||||
Usage:
|
||||
${0} [--help] [--publicip <IP Address>] [--turn <turn server>] [--stun <stun server>] [cirrus options...]
|
||||
Where:
|
||||
--help will print this message and stop this script.
|
||||
--debug will run all scripts with --inspect
|
||||
--nosudo will run all scripts without \`sudo\` command useful for when run in containers.
|
||||
--verbose will enable additional logging
|
||||
--package-manager <package manager name> specify an alternative package manager to apt-get
|
||||
--publicip is used to define public ip address (using default port) for turn server, syntax: --publicip ; it is used for
|
||||
default value: Retrieved from 'curl https://api.ipify.org' or if unsuccessful then set to 127.0.0.1. It is the IP address of the Cirrus server and the default IP address of the TURN server
|
||||
--turn defines what TURN server to be used, syntax: --turn 127.0.0.1:19303
|
||||
default value: as above, IP address downloaded from https://api.ipify.org; in case if download failure it is set to 127.0.0.1
|
||||
--stun defined what STUN server to be used, syntax: --stun stun.l.google.com:19302
|
||||
default value as above
|
||||
--build will force a rebuild of the typescript frontend even if it already exists
|
||||
Other options: stored and passed to the Cirrus server. All parameters printed once the script values are set.
|
||||
Command line options might be omitted to run with defaults and it is a good practice to omit specific ones when just starting the TURN or the STUN server alone, not the whole set of scripts.
|
||||
"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function print_parameters() {
|
||||
echo ""
|
||||
echo "${0} is running with the following parameters:"
|
||||
echo "--------------------------------------"
|
||||
if [[ -n "${stunserver}" ]]; then echo "STUN server : ${stunserver}" ; fi
|
||||
if [[ -n "${turnserver}" ]]; then echo "TURN server : ${turnserver}" ; fi
|
||||
echo "Public IP address : ${publicip}"
|
||||
echo "Cirrus server command line arguments: ${cirruscmd}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
function set_start_default_values() {
|
||||
# publicip and cirruscmd are always needed
|
||||
publicip=$(curl -s https://api.ipify.org)
|
||||
if [[ -z $publicip ]]; then
|
||||
publicip="127.0.0.1"
|
||||
fi
|
||||
cirruscmd=""
|
||||
|
||||
if [ "$1" = "y" ]; then
|
||||
turnserver="${publicip}:19303"
|
||||
fi
|
||||
|
||||
if [ "$2" = "y" ]; then
|
||||
stunserver="stun.l.google.com:19302"
|
||||
fi
|
||||
}
|
||||
|
||||
function use_args() {
|
||||
while(($#)) ; do
|
||||
case "$1" in
|
||||
--debug ) IS_DEBUG=1; shift;;
|
||||
--nosudo ) NO_SUDO=1; shift;;
|
||||
--verbose ) VERBOSE=1; shift;;
|
||||
--build ) FORCE_BUILD=1; shift;;
|
||||
--stun ) stunserver="$2"; shift 2;;
|
||||
--turn ) turnserver="$2"; shift 2;;
|
||||
--publicip ) publicip="$2"; turnserver="${publicip}:19303"; shift 2;;
|
||||
--help ) print_usage;;
|
||||
* ) echo "Unknown command, adding to cirrus command line: $1"; cirruscmd+=" $1"; shift;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
function call_setup_sh() {
|
||||
bash "setup.sh"
|
||||
}
|
||||
|
||||
function start_process() {
|
||||
if [ ! -z $NO_SUDO ]; then
|
||||
log_msg "running with sudo removed"
|
||||
eval $(echo "$@" | sed 's/sudo//g')
|
||||
else
|
||||
eval $@
|
||||
fi
|
||||
}
|
||||
|
||||
function get_version() {
|
||||
local version=$1
|
||||
|
||||
if command -v $version; then
|
||||
version=$($@)
|
||||
fi
|
||||
|
||||
echo $version | sed -E 's/[^0-9.]//g'
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# When run from SignallingWebServer/platform_scripts/bash, this uses the SignallingWebServer directory
|
||||
# as the build context so the Cirrus files can be successfully copied into the container image
|
||||
docker build --network=host -t 'cirrus-webserver:latest' -f ./Dockerfile ../..
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Start docker container by name using host networking
|
||||
docker run --name cirrus_latest --network host --rm cirrus-webserver
|
||||
|
||||
# Interactive start example
|
||||
#docker run --name cirrus_latest --network host --rm -it --entrypoint /bin/bash cirrus-webserver
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Suppress printing of directory stack
|
||||
pushd () {
|
||||
command pushd "$@" > /dev/null
|
||||
}
|
||||
popd () {
|
||||
command popd "$@" > /dev/null
|
||||
}
|
||||
|
||||
# Stop both stun and turn
|
||||
pushd "$(dirname ${BASH_SOURCE[0]})"
|
||||
./docker-start-cirrus.sh --with-turn &
|
||||
popd
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
source turn_user_pwd.sh
|
||||
|
||||
USETURN="false"
|
||||
|
||||
for arg do
|
||||
shift
|
||||
[ "${arg}" = "--with-turn" ] && USETURN="true" && continue
|
||||
set -- "$@" "${arg}"
|
||||
done
|
||||
|
||||
# Get stun server data for passing to the container
|
||||
source common_utils.sh
|
||||
if [ "${USETURN}" = "true" ]; then
|
||||
set_start_default_values "y" "y" # Both TURN and STUN server defaults
|
||||
else
|
||||
set_start_default_values "n" "y" # Only STUN server defaults
|
||||
fi
|
||||
use_args "$@"
|
||||
|
||||
# Start docker container by name using host networking
|
||||
if [ "${USETURN}" = "true" ]; then
|
||||
peerConnectionOptions="{\""iceServers\"":[{\""urls\"":[\""stun:"${stunserver}"\"",\""turn":"${turnserver}\""],\""username\"":\""${turnusername}\"",\""credential\"":\""${turnpassword}\""}]}"
|
||||
else
|
||||
peerConnectionOptions="{\""iceServers\"":[{\""urls\"":[\""stun:"${stunserver}"\""]}]}"
|
||||
fi
|
||||
|
||||
docker run --name cirrus_latest --network host --rm --entrypoint /usr/local/bin/node cirrus-webserver /opt/SignallingWebServer/cirrus.js --peerConnectionOptions="${peerConnectionOptions}" --publicIp="${publicip}"
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Get stun server data for passing to the container
|
||||
source common_utils.sh
|
||||
set_start_default_values "n" "y" # Only STUN server defaults
|
||||
use_args "$@"
|
||||
|
||||
localip=$(hostname -I | awk '{print $1}')
|
||||
echo "Private IP: $localip"
|
||||
|
||||
turnport="${turnserver##*:}"
|
||||
if [ -z "${turnport}" ]; then
|
||||
turnport=3478
|
||||
fi
|
||||
echo "TURN port: ${turnport}"
|
||||
echo ""
|
||||
|
||||
turnusername="PixelStreamingUser"
|
||||
turnpassword="AnotherTURNintheroad"
|
||||
realm="PixelStreaming"
|
||||
process="turnserver"
|
||||
arguments="-p ${turnport} -r $realm -X $publicip -E $localip -L $localip --no-cli --no-tls --no-dtls --pidfile /var/run/turnserver.pid -f -a -v -n -u ${turnusername}:${turnpassword}"
|
||||
|
||||
# Add arguments passed to script to arguments for executable
|
||||
arguments+=" ${cirruscmd}"
|
||||
|
||||
# Start docker container by name using host networking
|
||||
echo "Running: ${process} ${arguments}"
|
||||
|
||||
# Get the docker image
|
||||
docker pull coturn/coturn
|
||||
|
||||
# Start the TURN server
|
||||
#docker run --name coturn_latest --network host -it --entrypoint /bin/bash coturn/coturn
|
||||
#docker run --name coturn_latest --network host --rm -a stdin -a stdout -a stderr --entrypoint "sudo mkdir -p /var/run" coturn/coturn ""
|
||||
#docker run --name coturn_latest --network host --rm -a stdin -a stdout -a stderr --entrypoint "/bin/ls" coturn/coturn "/var/"
|
||||
|
||||
docker run --name coturn_latest --network host --rm -a stdin -a stdout -a stderr --entrypoint "${process}" coturn/coturn "${arguments}"
|
||||
|
||||
#docker run --name coturn_latest --network host --rm -a stdin -a stdout -a stderr --entrypoint "/bin/bash" coturn/coturn "ls -latr /var/run/"
|
||||
#docker run --name coturn_latest --network host --rm -a stdin -a stdout -a stderr --entrypoint "sudo chown ubuntu:ubuntu /var/run/turnserver.pid | sudo chmod +x /var/run/turnserver.pid | ${process}" coturn/coturn "${arguments}"
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Suppress printing of directory stack
|
||||
pushd () {
|
||||
command pushd "$@" > /dev/null
|
||||
}
|
||||
popd () {
|
||||
command popd "$@" > /dev/null
|
||||
}
|
||||
|
||||
# Stop both stun and turn
|
||||
pushd "$(dirname ${BASH_SOURCE[0]})"
|
||||
./docker-stop-cirrus.sh
|
||||
./docker-stop-turn.sh
|
||||
popd
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Stop the docker container
|
||||
PSID=$(docker ps -a -q --filter="name=cirrus_latest")
|
||||
if [ -z "$PSID" ]; then
|
||||
echo "Docker stun is not running, no stopping will be done"
|
||||
exit 1;
|
||||
fi
|
||||
echo "Stopping stun server ..."
|
||||
docker stop cirrus_latest
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Stop the docker container
|
||||
PSID=$(docker ps -a -q --filter="name=coturn_latest")
|
||||
if [ -z "$PSID" ]; then
|
||||
echo "Docker turn is not running, no stopping will be done"
|
||||
exit 1;
|
||||
fi
|
||||
echo "Stopping turn server..."
|
||||
docker stop coturn_latest
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
pushd "${BASH_LOCATION}" > /dev/null
|
||||
|
||||
source common_utils.sh
|
||||
|
||||
set_start_default_values "n" "n" # No server specific defaults
|
||||
use_args "$@"
|
||||
call_setup_sh
|
||||
print_parameters
|
||||
|
||||
process="${BASH_LOCATION}/node/lib/node_modules/npm/bin/npm-cli.js run start:default --"
|
||||
arguments=""
|
||||
|
||||
if [ ! -z $IS_DEBUG ]; then
|
||||
arguments+=" --inspect"
|
||||
fi
|
||||
|
||||
arguments+=" --publicIp=${publicip}"
|
||||
arguments+=" ${cirruscmd}"
|
||||
|
||||
pushd ../.. > /dev/null
|
||||
|
||||
echo ""
|
||||
echo "Starting Cirrus server use ctrl-c to exit"
|
||||
echo "-----------------------------------------"
|
||||
echo ""
|
||||
|
||||
PATH="${BASH_LOCATION}/node/bin:$PATH"
|
||||
start_process $process $arguments
|
||||
|
||||
popd > /dev/null # ../..
|
||||
|
||||
popd > /dev/null # BASH_SOURCE
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
BASH_LOCATION=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||
|
||||
pushd "${BASH_LOCATION}" > /dev/null
|
||||
|
||||
source common_utils.sh
|
||||
|
||||
use_args $@
|
||||
# Azure specific fix to allow installing NodeJS from NodeSource
|
||||
if test -f "/etc/apt/sources.list.d/azure-cli.list"; then
|
||||
sudo touch /etc/apt/sources.list.d/nodesource.list
|
||||
sudo touch /usr/share/keyrings/nodesource.gpg
|
||||
sudo chmod 644 /etc/apt/sources.list.d/nodesource.list
|
||||
sudo chmod 644 /usr/share/keyrings/nodesource.gpg
|
||||
sudo chmod 644 /etc/apt/sources.list.d/azure-cli.list
|
||||
fi
|
||||
|
||||
function check_version() { #current_version #min_version
|
||||
#check if same string
|
||||
if [ -z "$2" ] || [ "$1" = "$2" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local i current minimum
|
||||
|
||||
IFS="." read -r -a current <<< $1
|
||||
IFS="." read -r -a minimum <<< $2
|
||||
|
||||
# fill empty fields in current with zeros
|
||||
for ((i=${#current[@]}; i<${#minimum[@]}; i++))
|
||||
do
|
||||
current[i]=0
|
||||
done
|
||||
|
||||
for ((i=0; i<${#current[@]}; i++))
|
||||
do
|
||||
if [[ -z ${minimum[i]} ]]; then
|
||||
# fill empty fields in minimum with zeros
|
||||
minimum[i]=0
|
||||
fi
|
||||
|
||||
if ((10#${current[i]} > 10#${minimum[i]})); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ((10#${current[i]} < 10#${minimum[i]})); then
|
||||
return 2
|
||||
fi
|
||||
done
|
||||
|
||||
# if got this far string is the same once we added missing 0
|
||||
return 0
|
||||
}
|
||||
|
||||
function check_and_install() { #dep_name #get_version_string #version_min #install_command
|
||||
local is_installed=0
|
||||
|
||||
log_msg "Checking for required $1 install"
|
||||
|
||||
local current=$(echo $2 | sed -E 's/[^0-9.]//g')
|
||||
local minimum=$(echo $3 | sed -E 's/[^0-9.]//g')
|
||||
|
||||
if [ $# -ne 4 ]; then
|
||||
log_msg "check_and_install expects 4 args (dep_name get_version_string version_min install_command) got $#"
|
||||
return -1
|
||||
fi
|
||||
|
||||
if [ ! -z $current ]; then
|
||||
log_msg "Current version: $current checking >= $minimum"
|
||||
check_version "$current" "$minimum"
|
||||
if [ "$?" -lt 2 ]; then
|
||||
log_msg "$1 is installed."
|
||||
return 0
|
||||
else
|
||||
log_msg "Required install of $1 not found installing"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ $is_installed -ne 1 ]; then
|
||||
echo "$1 installation not found installing..."
|
||||
|
||||
start_process $4
|
||||
|
||||
if [ $? -ge 1 ]; then
|
||||
echo "Installation of $1 failed try running `export VERBOSE=1` then run this script again for more details"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
function setup_frontend() {
|
||||
# navigate to root
|
||||
pushd ${BASH_LOCATION}/../../.. > /dev/null
|
||||
export PATH="../../SignallingWebServer/platform_scripts/bash/node/bin:$PATH"
|
||||
# If player.html doesn't exist, or --build passed as arg, rebuild the frontend
|
||||
if [ ! -f SignallingWebServer/Public/player.html ] || [ ! -z "$FORCE_BUILD" ] ; then
|
||||
echo "Building Typescript Frontend."
|
||||
# Using our bundled NodeJS, build the web frontend files
|
||||
pushd ${BASH_LOCATION}/../../../Frontend/library > /dev/null
|
||||
../../SignallingWebServer/platform_scripts/bash/node/bin/npm install
|
||||
../../SignallingWebServer/platform_scripts/bash/node/bin/npx webpack
|
||||
popd
|
||||
|
||||
pushd ${BASH_LOCATION}/../../../Frontend/implementations/EpicGames > /dev/null
|
||||
../../../SignallingWebServer/platform_scripts/bash/node/bin/npm install
|
||||
../../../SignallingWebServer/platform_scripts/bash/node/bin/npm link ../../library
|
||||
../../../SignallingWebServer/platform_scripts/bash/node/bin/npx webpack
|
||||
popd
|
||||
else
|
||||
echo 'Skipping building Frontend because files already exist. Please run with "--build" to force a rebuild'
|
||||
fi
|
||||
|
||||
popd > /dev/null # root
|
||||
}
|
||||
|
||||
|
||||
echo "Checking Pixel Streaming Server dependencies."
|
||||
|
||||
# navigate to SignallingWebServer root
|
||||
pushd ${BASH_LOCATION}/../.. > /dev/null
|
||||
|
||||
node_version=""
|
||||
if [[ -f "${BASH_LOCATION}/node/bin/node" ]]; then
|
||||
node_version=$("${BASH_LOCATION}/node/bin/node" --version)
|
||||
fi
|
||||
check_and_install "node" "$node_version" "v16.4.2" "curl https://nodejs.org/dist/v16.14.2/node-v16.14.2-linux-x64.tar.gz --output node.tar.xz
|
||||
&& tar -xf node.tar.xz
|
||||
&& rm node.tar.xz
|
||||
&& mv node-v*-linux-x64 \"${BASH_LOCATION}/node\""
|
||||
|
||||
PATH="${BASH_LOCATION}/node/bin:$PATH"
|
||||
"${BASH_LOCATION}/node/lib/node_modules/npm/bin/npm-cli.js" install
|
||||
|
||||
popd > /dev/null # SignallingWebServer
|
||||
|
||||
# Trigger Frontend Build if needed or requested
|
||||
# This has to be done after check_and_install "node"
|
||||
setup_frontend
|
||||
|
||||
popd > /dev/null # BASH_SOURCE
|
||||
|
||||
#command #dep_name #get_version_string #version_min #install command
|
||||
coturn_version=$(if command -v turnserver &> /dev/null; then echo 1; else echo 0; fi)
|
||||
if [ $coturn_version -eq 0 ]; then
|
||||
if ! command -v apt-get &> /dev/null; then
|
||||
echo "Setup for the scripts is designed for use with distros that use the apt-get package manager" \
|
||||
"if you are seeing this message you will have to update \"${BASH_LOCATION}/setup.sh\" with\n" \
|
||||
"a package manger and the equivalent packages for your distribution. Please follow the\n" \
|
||||
"instructions found at https://pkgs.org/search/?q=coturn to install Coturn for your specific distribution"
|
||||
exit 1
|
||||
else
|
||||
if [ `id -u` -eq 0 ]; then
|
||||
check_and_install "coturn" "$coturn_version" "1" "apt-get install -y coturn"
|
||||
else
|
||||
check_and_install "coturn" "$coturn_version" "1" "sudo apt-get install -y coturn"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Plain text TURN setup
|
||||
turnusername="PixelStreamingUser"
|
||||
turnpassword="AnotherTURNintheroad"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
How to use files in this directory:
|
||||
- Files with .ps1 extension can be run with PowerShell[.exe] in Windows. Powershell needs to be started as Administrator to run setup.ps1 so it can run installation / installation check steps
|
||||
- Make sure that all of your dependencies are installed. Use .\setup.ps1 what will install whatever is missing as long as you are on a supported operating system
|
||||
|
||||
- Run a local instance of the Cirrus server by using the .\run_local.ps1 script
|
||||
|
||||
- Use the following scripts to run locally or in your cloud instance:
|
||||
- Start_SignallingServer.ps1 - Start only the Signalling (STUN) server
|
||||
- Start_TURNServer.ps1 - Start only the TURN server
|
||||
- Start_WithTURN_SignallingServer.ps1 - Start a TURN server and the Cirrus server together
|
||||
- The Start_Common.ps1 file contains shared functions for other Start_*.ps1 scripts and it is not supposed to run alone
|
||||
|
||||
- The local/cloud Start_*.ps1 powershell scripts can be invoked with the --help command line option to see how those can be configured. The following options can be supplied: --publicip, --turn, --stun. Please read the --help
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
# Parse $args into a string
|
||||
$params = $args[0]
|
||||
if ( $args.Count -gt 1 ) {
|
||||
$params = $args[1..$($args.Count - 1)]
|
||||
# Do setup as a common task, it is smart and will not reinstall if not required.
|
||||
Start-Process -FilePath "$PSScriptRoot\setup.bat" -Wait -NoNewWindow -ArgumentList $params
|
||||
}
|
||||
else {
|
||||
Start-Process -FilePath "$PSScriptRoot\setup.bat" -Wait -NoNewWindow
|
||||
}
|
||||
echo $params
|
||||
|
||||
$global:ScriptName = $MyInvocation.MyCommand.Name
|
||||
$global:PublicIP = $null
|
||||
$global:StunServer = $null
|
||||
$global:TurnServer = $null
|
||||
$global:CirrusCmd = $null
|
||||
$global:BuildFrontend = $null
|
||||
|
||||
function print_usage {
|
||||
echo "
|
||||
Usage (in MS Windows Power Shell):
|
||||
$global:ScriptName [--help] [--publicip <IP Address>] [--turn <turn server>] [--stun <stun server>] [cirrus options...]
|
||||
Where:
|
||||
--help will print this message and stop this script.
|
||||
--publicip is used to define public ip address (using default port) for turn server, syntax: --publicip ; it is used for
|
||||
default value: Retrieved from 'curl https://api.ipify.org' or if unsuccessful then set to 127.0.0.1. It is the IP address of the Cirrus server and the default IP address of the TURN server
|
||||
--turn defines what TURN server to be used, syntax: --turn 127.0.0.1:19303
|
||||
default value: as above, IP address downloaded from https://api.ipify.org; in case if download failure it is set to 127.0.0.1
|
||||
--stun defined what STUN server to be used, syntax: --stun stun.l.google.com:19302
|
||||
default value as above
|
||||
Other options: stored and passed to the Cirrus server. All parameters printed once the script values are set.
|
||||
Command line options might be omitted to run with defaults and it is a good practice to omit specific ones when just starting the TURN or the STUN server alone, not the whole set of scripts.
|
||||
"
|
||||
exit 1
|
||||
}
|
||||
|
||||
function print_parameters {
|
||||
echo ""
|
||||
echo "$scriptname is running with the following parameters:"
|
||||
echo "--------------------------------------"
|
||||
if ($global:StunServer -ne $null) { echo "STUN server : $global:StunServer" }
|
||||
if ($global:TurnServer -ne $null) { echo "TURN server : $global:TurnServer" }
|
||||
echo "Public IP address : $global:PublicIP"
|
||||
echo "Cirrus server command line arguments: $global:CirrusCmd"
|
||||
echo ""
|
||||
}
|
||||
|
||||
function set_start_default_values($SetTurnServerVar, $SetStunServerVar) {
|
||||
# publicip and cirruscmd are always needed
|
||||
$global:PublicIP = Invoke-WebRequest -Uri "https://api.ipify.org" -UseBasicParsing
|
||||
if ($global:PublicIP -eq $null -Or $global:PublicIP.length -eq 0) {
|
||||
$global:PublicIP = "127.0.0.1"
|
||||
} else {
|
||||
$global:PublicIP = ($global:PublicIP).Content
|
||||
}
|
||||
$global:cirruscmd = ""
|
||||
|
||||
if ($SetTurnServerVar -eq "y") {
|
||||
$global:TurnServer = $global:PublicIP + ":19303"
|
||||
}
|
||||
if ($SetStunServerVar -eq "y") {
|
||||
$global:StunServer = "stun.l.google.com:19302"
|
||||
}
|
||||
}
|
||||
|
||||
function use_args($arg) {
|
||||
$CmdArgs = $arg -split (" ")
|
||||
while($CmdArgs.count -gt 0) {
|
||||
$Cmd, $CmdArgs = $CmdArgs
|
||||
if ($Cmd -eq "--stun") {
|
||||
$global:StunServer, $CmdArgs = $CmdArgs
|
||||
} elseif ($Cmd -eq "--turn") {
|
||||
$global:TurnServer, $CmdArgs = $CmdArgs
|
||||
} elseif ($Cmd -eq "--publicip") {
|
||||
$global:PublicIP, $CmdArgs = $CmdArgs
|
||||
$global:TurnServer = $global:publicip + ":19303"
|
||||
} elseif ($Cmd -eq "--build") {
|
||||
$global:BuildFrontend, $CmdArgs = $CmdArgs
|
||||
} elseif ($Cmd -eq "--help") {
|
||||
print_usage
|
||||
} else {
|
||||
echo "Unknown command, adding to cirrus command line: $Cmd"
|
||||
$global:CirrusCmd += " $Cmd"
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
. "$PSScriptRoot\Start_Common.ps1" $args
|
||||
|
||||
set_start_default_values "n" "y" # Set both TURN and STUN server defaults
|
||||
use_args($args)
|
||||
print_parameters
|
||||
|
||||
$peerConnectionOptions = "{ \""iceServers\"": [{\""urls\"": [\""stun:" + $global:StunServer + "\""]}] }"
|
||||
|
||||
$ProcessExe = "platform_scripts\cmd\node\node.exe"
|
||||
$Arguments = @("cirrus", "--peerConnectionOptions=""$peerConnectionOptions""", "--PublicIp=$global:PublicIp")
|
||||
# Add arguments passed to script to Arguments for executable
|
||||
$Arguments += $global:CirrusCmd
|
||||
|
||||
Push-Location $PSScriptRoot\..\..\
|
||||
Write-Output "Running: $ProcessExe $Arguments"
|
||||
Start-Process -FilePath $ProcessExe -ArgumentList "$Arguments" -Wait -NoNewWindow
|
||||
Pop-Location
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
. "$PSScriptRoot\Start_Common.ps1" $args
|
||||
|
||||
set_start_default_values "y" "n" # Set both TURN and STUN server defaults
|
||||
use_args($args)
|
||||
print_parameters
|
||||
#$LocalIp = Invoke-WebRequest -Uri "http://169.254.169.254/latest/meta-data/local-ipv4"
|
||||
$LocalIP = (Test-Connection -ComputerName (hostname) -Count 1 | Select IPV4Address).IPV4Address.IPAddressToString
|
||||
|
||||
Write-Output "Private IP: $LocalIp"
|
||||
|
||||
$TurnPort="19303"
|
||||
$Pos = $global:TurnServer.LastIndexOf(":")
|
||||
if ($Pos -ne -1) {
|
||||
$TurnPort = $global:TurnServer.Substring($Pos+ 1)
|
||||
}
|
||||
echo "TURN port: ${turnport}"
|
||||
echo ""
|
||||
|
||||
Push-Location $PSScriptRoot
|
||||
|
||||
$TurnUsername = "PixelStreamingUser"
|
||||
$TurnPassword = "AnotherTURNintheroad"
|
||||
$Realm = "PixelStreaming"
|
||||
$ProcessExe = ".\turnserver.exe"
|
||||
$Arguments = "-p $TurnPort -r $Realm -X $PublicIP -E $LocalIP -L $LocalIP --no-cli --no-tls --no-dtls --pidfile `"C:\coturn.pid`" -f -a -v -n -u $TurnUsername`:$TurnPassword"
|
||||
|
||||
# Add arguments passed to script to Arguments for executable
|
||||
$Arguments += $args
|
||||
|
||||
Push-Location $PSScriptRoot\coturn\
|
||||
Write-Output "Running: $ProcessExe $Arguments"
|
||||
# pause
|
||||
Start-Process -FilePath $ProcessExe -ArgumentList $Arguments -NoNewWindow
|
||||
Pop-Location
|
||||
|
||||
Pop-Location
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
# Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
. "$PSScriptRoot\Start_Common.ps1" $args
|
||||
|
||||
set_start_default_values "y" "y" # Set both TURN and STUN server defaults
|
||||
use_args($args)
|
||||
print_parameters
|
||||
|
||||
Push-Location $PSScriptRoot
|
||||
|
||||
Start-Process -FilePath "PowerShell" -ArgumentList ".\Start_TURNServer.ps1" -WorkingDirectory "$PSScriptRoot"
|
||||
|
||||
$peerConnectionOptions = "{ \""iceServers\"": [{\""urls\"": [\""stun:" + $global:StunServer + "\"",\""turn:" + $global:TurnServer + "\""], \""username\"": \""PixelStreamingUser\"", \""credential\"": \""AnotherTURNintheroad\""}] }"
|
||||
|
||||
$ProcessExe = "platform_scripts\cmd\node\node.exe"
|
||||
$Arguments = @("cirrus", "--peerConnectionOptions=""$peerConnectionOptions""", "--PublicIp=$global:PublicIp")
|
||||
# Add arguments passed to script to Arguments for executable
|
||||
$Arguments += $args
|
||||
|
||||
Push-Location $PSScriptRoot\..\..\
|
||||
Write-Output "Running: $ProcessExe $Arguments"
|
||||
Start-Process -FilePath $ProcessExe -ArgumentList $Arguments -Wait -NoNewWindow
|
||||
Pop-Location
|
||||
|
||||
Pop-Location
|
||||
@@ -0,0 +1,39 @@
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
@echo off
|
||||
|
||||
@Rem Set script directory as working directory.
|
||||
pushd "%~dp0"
|
||||
|
||||
title Building Cirrus.exe
|
||||
|
||||
@Rem Run setup to ensure we have node and cirrus installed.
|
||||
call setup.bat %*
|
||||
|
||||
@Rem Look for a `nexe` directory next to this script
|
||||
if exist nexe\ (
|
||||
echo nexe directory found...skipping install.
|
||||
) else (
|
||||
echo nexe directory not found...beginning nexe install.
|
||||
|
||||
@Rem Make `nexe directory`
|
||||
mkdir nexe
|
||||
|
||||
@Rem npm init and install nexe
|
||||
pushd nexe
|
||||
call ..\node\npm init -y
|
||||
call ..\node\npm install nexe --save
|
||||
popd
|
||||
)
|
||||
|
||||
@Rem Move to cirrus directory.
|
||||
pushd ..\..
|
||||
|
||||
@Rem Build cirrus.exe using `nexe` using node 14.5.0 (as that is one of the latest prebuilts node versions in the nexe repo)
|
||||
call platform_scripts\cmd\node\npx nexe cirrus.js --target "x64-14.15.3" -r "Public/*" -r "scripts/*" -r "images/*" -r "config.json"
|
||||
|
||||
@Rem Pop cirrus directory.
|
||||
popd ..\..
|
||||
|
||||
@Rem Pop working directory
|
||||
popd
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
::
|
||||
:: RefreshEnv.cmd
|
||||
::
|
||||
:: Batch file to read environment variables from registry and
|
||||
:: set session variables to these values.
|
||||
::
|
||||
:: With this batch file, there should be no need to reload command
|
||||
:: environment every time you want environment changes to propagate
|
||||
|
||||
::echo "RefreshEnv.cmd only works from cmd.exe, please install the Chocolatey Profile to take advantage of refreshenv from PowerShell"
|
||||
echo | set /p dummy="Refreshing environment variables from registry for cmd.exe. Please wait..."
|
||||
|
||||
goto main
|
||||
|
||||
:: Set one environment variable from registry key
|
||||
:SetFromReg
|
||||
"%WinDir%\System32\Reg" QUERY "%~1" /v "%~2" > "%TEMP%\_envset.tmp" 2>NUL
|
||||
for /f "usebackq skip=2 tokens=2,*" %%A IN ("%TEMP%\_envset.tmp") do (
|
||||
echo/set "%~3=%%B"
|
||||
)
|
||||
goto :EOF
|
||||
|
||||
:: Get a list of environment variables from registry
|
||||
:GetRegEnv
|
||||
"%WinDir%\System32\Reg" QUERY "%~1" > "%TEMP%\_envget.tmp"
|
||||
for /f "usebackq skip=2" %%A IN ("%TEMP%\_envget.tmp") do (
|
||||
if /I not "%%~A"=="Path" (
|
||||
call :SetFromReg "%~1" "%%~A" "%%~A"
|
||||
)
|
||||
)
|
||||
goto :EOF
|
||||
|
||||
:main
|
||||
echo/@echo off >"%TEMP%\_env.cmd"
|
||||
|
||||
:: Slowly generating final file
|
||||
call :GetRegEnv "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" >> "%TEMP%\_env.cmd"
|
||||
call :GetRegEnv "HKCU\Environment">>"%TEMP%\_env.cmd" >> "%TEMP%\_env.cmd"
|
||||
|
||||
:: Special handling for PATH - mix both User and System
|
||||
call :SetFromReg "HKLM\System\CurrentControlSet\Control\Session Manager\Environment" Path Path_HKLM >> "%TEMP%\_env.cmd"
|
||||
call :SetFromReg "HKCU\Environment" Path Path_HKCU >> "%TEMP%\_env.cmd"
|
||||
|
||||
:: Caution: do not insert space-chars before >> redirection sign
|
||||
echo/set "Path=%%Path_HKLM%%;%%Path_HKCU%%" >> "%TEMP%\_env.cmd"
|
||||
|
||||
:: Cleanup
|
||||
del /f /q "%TEMP%\_envset.tmp" 2>nul
|
||||
del /f /q "%TEMP%\_envget.tmp" 2>nul
|
||||
|
||||
:: capture user / architecture
|
||||
SET "OriginalUserName=%USERNAME%"
|
||||
SET "OriginalArchitecture=%PROCESSOR_ARCHITECTURE%"
|
||||
|
||||
:: Set these variables
|
||||
call "%TEMP%\_env.cmd"
|
||||
|
||||
:: Cleanup
|
||||
del /f /q "%TEMP%\_env.cmd" 2>nul
|
||||
|
||||
:: reset user / architecture
|
||||
SET "USERNAME=%OriginalUserName%"
|
||||
SET "PROCESSOR_ARCHITECTURE=%OriginalArchitecture%"
|
||||
|
||||
echo | set /p dummy="Finished."
|
||||
echo ...
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
@echo off
|
||||
|
||||
@Rem Set script directory as working directory.
|
||||
pushd "%~dp0"
|
||||
|
||||
title Cirrus
|
||||
|
||||
@Rem Run setup to ensure we have node and cirrus installed.
|
||||
call setup.bat
|
||||
|
||||
@Rem Move to cirrus directory.
|
||||
pushd ..\..
|
||||
|
||||
@Rem Run node server and pass any argument along.
|
||||
platform_scripts\cmd\node\node.exe cirrus %*
|
||||
|
||||
@Rem Pop cirrus directory.
|
||||
popd
|
||||
|
||||
@Rem Pop script directory.
|
||||
popd
|
||||
|
||||
pause
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright (C) 1999-2008 - Jonathan Wilkes
|
||||
http://www.xanya.net
|
||||
|
||||
Installing and using this software (or source code) signifies acceptance of these terms and the conditions of the license.
|
||||
This license applies to everything in this package (Including any supplied Source Code), except where otherwise noted.
|
||||
|
||||
License Agreement
|
||||
-----------------
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the author be held liable for any damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software/source code.
|
||||
(If you use the supplied source code (if any) in a product, then an acknowledgment in the product documentation would be appreciated but is not required.)
|
||||
|
||||
2. If you have downloaded the Source Code for this application (where available) then altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any distribution of the software.
|
||||
(If you use the supplied source code (if any) in a product, including commercial applications, then you do NOT need to distribute this license with your product.)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
|
||||
SetEnv
|
||||
Version 1.09 - ( For Windows 9x/NT/2000/XP/S2K3/Vista )
|
||||
|
||||
Copyright (C) 2005-2008 - Jonathan Wilkes - All Rights Reserved.
|
||||
http://www.xanya.net
|
||||
|
||||
================================================================================
|
||||
|
||||
1. Installation
|
||||
|
||||
Simply download and run the Setup_SetEnv.exe application to install SetEnv.
|
||||
|
||||
2. Using SetEnv
|
||||
|
||||
The SetEnv is a free tool for setting/updating/deleting System Environment Variables.
|
||||
Type the following at a command prompt (assumes SetEnv.exe is in current path), for command line usage information.
|
||||
|
||||
setenv -?
|
||||
|
||||
See our website for full usage details, http://www.xanya.net/site/utils/setenv.php
|
||||
|
||||
3. Version History
|
||||
|
||||
1.09 [Fix] - (Feb 9, 2008) - Fixed a problem on Windows 98 where it sometimes failed to open the Autoexec.bat file.
|
||||
1.08 [New] - (May 31, 2007) - Added how to delete a USER environment variable to the usage information.
|
||||
1.07 [Fix] - (Jan 25, 2007) - Fixed a bug found by depaolim.
|
||||
1.06 [New] - (Jan 14, 2007) - Added dynamic expansion support (same as using ~ with setx)
|
||||
- Originally requested by Andre Amaral, further Request by Synetech
|
||||
1.05 [New] - (Sep 06, 2006) - Added support to prepend (rather than append) a value to an expanded string
|
||||
- Requested by Masuia
|
||||
1.04 [New] - (May 30, 2006) - Added support for User environment variables.
|
||||
1.03 [Fix] - (Apr 20, 2006) - Bug fix in ProcessWinXP() discovered by attiasr
|
||||
1.01 [Fix] - (Nov 15, 2005) - Bug fix in IsWinME() discovered by frankd
|
||||
1.00 [New] - (Oct 29, 2005) - Initial Public Release.
|
||||
|
||||
4. License and Terms of Use
|
||||
|
||||
Please see the License.txt file for licensing information.
|
||||
|
||||
5. Reporting Problems
|
||||
|
||||
If you encounter any problems whilst using SetEnv, please try downloading the latest version from http://www.xanya.net to see if the problem has already been resolved.
|
||||
If this does not help, then please send an e-mail to darka@xanya.net with details describing the problem.
|
||||
|
||||
================================================================================
|
||||
@@ -0,0 +1,23 @@
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
@echo off
|
||||
|
||||
@Rem Set script location as working directory for commands.
|
||||
pushd "%~dp0"
|
||||
|
||||
@Rem Ensure we have NodeJs available for calling.
|
||||
call setup_node.bat
|
||||
|
||||
@Rem Ensure we have frontend built.
|
||||
call setup_frontend.bat %*
|
||||
|
||||
@Rem Ensure we have CoTURN available for calling.
|
||||
call setup_coturn.bat
|
||||
|
||||
@Rem Move to cirrus.js directory and install its package.json
|
||||
pushd %~dp0\..\..\
|
||||
call platform_scripts\cmd\node\npm install --no-save
|
||||
popd
|
||||
|
||||
@Rem Pop working directory
|
||||
popd
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
@echo off
|
||||
|
||||
@Rem Set script location as working directory for commands.
|
||||
pushd "%~dp0"
|
||||
|
||||
@Rem Look for CoTURN directory next to this script
|
||||
if exist coturn\ (
|
||||
echo CoTURN directory found...skipping install.
|
||||
) else (
|
||||
echo CoTURN directory not found...beginning CoTURN download for Windows.
|
||||
|
||||
@Rem Download nodejs and follow redirects.
|
||||
curl -L -o ./turnserver.zip "https://github.com/mcottontensor/coturn/releases/download/v4.5.2-windows/turnserver.zip"
|
||||
|
||||
@Rem Unarchive the .zip to a directory called "turnserver"
|
||||
mkdir coturn & tar -xf turnserver.zip -C coturn
|
||||
|
||||
@Rem Delete the downloaded turnserver.zip
|
||||
del turnserver.zip
|
||||
)
|
||||
|
||||
@Rem Pop working directory
|
||||
popd
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
:main
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
@echo off
|
||||
|
||||
@Rem Set root directory as working directory for commands.
|
||||
pushd %~dp0\..\..\..\
|
||||
|
||||
@Rem By default don't build the frontend files
|
||||
set "shouldbuild=false"
|
||||
|
||||
@Rem Check if --build is passed as argument and we will always build frontend files.
|
||||
:parse
|
||||
IF "%~1"=="" GOTO endparse
|
||||
IF "%~1"=="--build" set "shouldbuild=true"
|
||||
SHIFT
|
||||
GOTO parse
|
||||
:endparse
|
||||
|
||||
@Rem Look under /Public directory for player.html
|
||||
if exist SignallingWebServer\Public\player.html (
|
||||
@Rem If --build is passed then we should build
|
||||
if "%shouldbuild%" == "true" (
|
||||
call :buildFrontend
|
||||
) else (
|
||||
echo Skipping rebuilding frontend... SignallingWebServer/Public has content already, use --build to force a frontend rebuild.
|
||||
)
|
||||
) else (
|
||||
call :buildFrontend
|
||||
)
|
||||
|
||||
@Rem Pop working directory
|
||||
popd
|
||||
|
||||
goto :eof
|
||||
|
||||
:buildFrontend
|
||||
echo Building frontend files...
|
||||
|
||||
@Rem Look for a node directory next to this script
|
||||
if not exist node call SignallingWebServer\platform_scripts\cmd\setup_node.bat
|
||||
|
||||
@Rem NOTE: We want to use our NodeJS (not system NodeJS!) to build the web frontend files.
|
||||
@Rem Save our current directory (the NodeJS dir) in a variable
|
||||
set "NodeDir=%CD%\SignallingWebServer\platform_scripts\cmd\node"
|
||||
|
||||
@Rem Prepend NodeDir to PATH temporarily using a custom tool called SetEnv
|
||||
call SignallingWebServer\platform_scripts\cmd\setenv\SetEnv.exe -uap PATH %%%%"%NodeDir%"
|
||||
@Rem Refresh the cmd session with new PATH
|
||||
call %~dp0\refreshenv.cmd
|
||||
|
||||
@Rem Do npm install in the Frontend\lib directory (note we use start because that loads PATH)
|
||||
echo ----------------------------
|
||||
echo Building frontend library...
|
||||
pushd %CD%\Frontend\library
|
||||
call ..\..\SignallingWebServer\platform_scripts\cmd\node\npm install
|
||||
call ..\..\SignallingWebServer\platform_scripts\cmd\node\npx webpack
|
||||
popd
|
||||
echo End of build PS frontend lib step.
|
||||
|
||||
@Rem Do npm install in the Frontend\implementations\EpicGames directory (note we use start because that loads PATH)
|
||||
echo ----------------------------
|
||||
echo Building Epic Games reference frontend...
|
||||
pushd %CD%\Frontend\implementations\EpicGames
|
||||
call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm install
|
||||
call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npm link ../../library
|
||||
call ..\..\..\SignallingWebServer\platform_scripts\cmd\node\npx webpack
|
||||
popd
|
||||
echo End of build reference frontend step.
|
||||
echo ----------------------------
|
||||
|
||||
@Rem Remove our NodeJS from the PATH
|
||||
call SignallingWebServer\platform_scripts\cmd\setenv\SetEnv.exe -ud PATH %%%%"%NodeDir%"
|
||||
|
||||
goto :eof
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
@Rem Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
@echo off
|
||||
|
||||
@Rem Set script location as working directory for commands.
|
||||
pushd "%~dp0"
|
||||
|
||||
@Rem Name and version of node that we are downloading
|
||||
SET NodeVersion=v16.4.2
|
||||
SET NodeName=node-%NodeVersion%-win-x64
|
||||
|
||||
@Rem Look for a node directory next to this script
|
||||
if exist node\ (
|
||||
echo Node directory found...skipping install.
|
||||
) else (
|
||||
echo Node directory not found...beginning NodeJS download for Windows.
|
||||
|
||||
@Rem Download nodejs and follow redirects.
|
||||
curl -L -o ./node.zip "https://nodejs.org/dist/%NodeVersion%/%NodeName%.zip"
|
||||
|
||||
@Rem Unarchive the .zip
|
||||
tar -xf node.zip
|
||||
|
||||
@Rem Rename the extracted, versioned, directory that contains the NodeJS binaries to simply "node".
|
||||
ren "%NodeName%\" "node"
|
||||
|
||||
@Rem Delete the downloaded node.zip
|
||||
del node.zip
|
||||
)
|
||||
|
||||
@Rem Print node version
|
||||
echo Node version: & node\node.exe -v
|
||||
|
||||
@Rem Pop working directory
|
||||
popd
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>bootstrap v3.x, v4.x</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>This is a requirement to using Bootstrap, providing better UI elements for the client web pages created for demoing pixelstreaming</Function>
|
||||
<Eula>https://github.com/twitter/bootstrap/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/Bootstrap_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Express v4.16.2</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Express is a web framework for Node.js.</Function>
|
||||
<Eula>https://github.com/expressjs/express/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Plugins/Experimental/PixelStreaming/Source/Express_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>FontAwesome Free v5.1</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Provides a consistent icon style to use in the sites for demoing pixelstreaming.</Function>
|
||||
<Eula>https://github.com/FortAwesome/Font-Awesome/blob/master/LICENSE.txt</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>None</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Helmet v.3.21.3</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/SignallingWebServer</Location>
|
||||
<Function>Helmet helps you secure your Express apps by setting various HTTP headers.</Function>
|
||||
<Eula>https://github.com/helmetjs/helmet/blob/v3.21.3/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>NONE (but keep license with code)</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>JQuery</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>This is a requirement to using Bootstrap, providing access to the DOM in the browser for easier and more advanced client side interactions and UI. Used for Project Cirrus.</Function>
|
||||
<Eula>https://github.com/jquery/jquery/blob/master/LICENSE.txt; https://js.foundation/pdf/ip-policy.pdf</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/JQuery_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Popper.js v1.14.3</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>A requirement to using Bootstrap.</Function>
|
||||
<Eula>https://github.com/FezVrasta/popper.js/blob/master/LICENSE.md</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/Popper.js_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Socket.io v2.0.4</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Enables real-time bidirectional event-based communication.</Function>
|
||||
<Eula>https://github.com/socketio/socket.io/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Plugins/Experimental/PixelStreaming/Source/Socket.io_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>WS</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/SignallingWebServer/</Location>
|
||||
<Function>It's used by SignallingWebServer (based on Node.js web-server) that is part of our PixelStreaming project.We add a dependency to WS library to Node.js configuration and it's downloaded automatically.</Function>
|
||||
<Eula>https://github.com/websockets/ws/blob/HEAD/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>//depot/UE4/Engine/Source/ThirdParty/Licenses/WS_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>WebRTC adapter (adapter.js) v6.3.2</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Used as a cross browser interface for WebRTC.</Function>
|
||||
<Eula>https://github.com/webrtc/adapter/blob/master/LICENSE.md</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/WebRTCadapter_License.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Yargs v15.3.0</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/SignallingWebServer/</Location>
|
||||
<Function>A module for Node.js, used to parse command line arguments, which is downloaded automatically by Node Package Manager.</Function>
|
||||
<Eula>https://github.com/yargs/yargs/blob/v15.3.0/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>None</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>Bcrypt.js</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/SignallingWebServer/</Location>
|
||||
<Function>This is used to verify passwords match the ones stored using the bcrypt algorithm. The passwords are always stored using bcrypt and so we never know the unencrypted password. This allows us to implement a authentication system on the web server so that only people we give accounts to can access the web server. This use is only for prototype stage, production will use the Epic unreal account system</Function>
|
||||
<Eula>https://github.com/dcodeIO/bcrypt.js/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/UE4/Main/Engine/Source/ThirdParty/Licenses</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>express-session v1.15.6</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Used to create session id's used to remember a person who has logged into a server across page loads so that they don't have to log in every time the reload or navigate to a different page hosted on a webserver</Function>
|
||||
<Eula>https://github.com/expressjs/session/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/express-session_license.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>passport-local v1.0.0</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>This is a implementation for the Passport middleware that allows you to store user credentials locally on the machine (passwords are stored with bcrypt and not reversible) to be used to authenticate users on a node.js webserver. This use is only for prototype stage, production will use the Epic unreal account system.</Function>
|
||||
<Eula>https://github.com/jaredhanson/passport-local/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/passport-local_license.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<Name>passport v0.4.0</Name>
|
||||
<Location>/Samples/PixelStreaming/WebServers/</Location>
|
||||
<Function>Is the authentication middleware that adds the ability to securely log in a user to the webserver. This is a generic framework that you add specific implementation frameworks (separate TPS's will be provided for these) to to provide authentication on a node.js webserver.</Function>
|
||||
<Eula>https://github.com/jaredhanson/passport/blob/master/LICENSE</Eula>
|
||||
<RedistributeTo>
|
||||
<EndUserGroup>Licensees</EndUserGroup>
|
||||
<EndUserGroup>Git</EndUserGroup>
|
||||
<EndUserGroup>P4</EndUserGroup>
|
||||
</RedistributeTo>
|
||||
<LicenseFolder>/Engine/Source/ThirdParty/Licenses/passport_license.txt</LicenseFolder>
|
||||
</TpsData>
|
||||
Reference in New Issue
Block a user