const path = require('path');
const fs = require('fs-extra');
const Utils = require('../Utils.js');
/** Settings that can be set in the settings.json file */
class Settings {
constructor(jt) {
this.jt = jt;
// Admin access, see AdminAuth: without a password, only from the computer running jtree.
this.admins = {}; // extra admin logins, {id: {pwd: '...'}}
this.allowClientsToCreateParticipants = true; // default for new sessions
this.askBeforeLeaving = true; // participants' pages ask before they are closed or reloaded
this.autoSaveFreq = 5000; // how often to save the time (internal/serverState.json), in ms; after a crash, restored stage timers are off by up to this much.
this.logToConsole = false;
this.openAdminOnStart = true; // whether or not the admin page should be opened when the server starts.
this.multipleUsers = false;
this.reloadApps = true; // load apps every time they are accessed.
// Whose rules apply where z-Tree, oTree and jtree differ (division by zero, an array index
// out of range, ...): 'jtree' (they are errors, reported), 'ztree' or 'otree' (as that
// engine does them). A session may have its own (see Session#engineMode).
this.mode = 'jtree';
this.clientJSFile = 'internal/clients/shared/shared.js';
this.clientJSTemplateFile = 'internal/sharedTemplate.js';
this.defaultAdminUI = 'v2'; // folder in adminUIsPath served at /admin; others at /admin/<folder>
this.participantUI = 'internal/clients/participant';
this.clientUI = 'internal/clients/participant';
this.adminUIsPath = 'internal/clients/admin';
this.adminUIsSharedPath = 'internal/clients/admin/shared';
this.appFolders = ['apps']; // the folders apps are in: relative to jtree's folder (client/), or absolute
this.roomsPath = 'rooms';
this.usersPath = 'users';
this.helpPath = 'internal/docs'; // location of help documents.
this.logPath = 'internal/logs';
this.sessionsFolder = 'sessions';
this.sharedUI = 'internal/clients/shared/';
this.serverTimeInfoFilename = 'internal/serverState.json'; // location of file that stores last time server was active.
this.autoplayDelay = 'randomInt(4,8)*1000';
this.outputDelimiter = ';';
this.useHTTPS = false;
this.basePath = '/jtree'; // route everything is served under; '' for the root. JAS sets it from the app's route.
this.httpsCertificateFile = 'certificate.pem';
this.defaultAdminPwd = undefined; // admin password; set it to use the admin from other computers
this.sessionShowFullLinks = false;
this.loadSettings = false;
this.session = {};
this.session.suggestedNumParticipants = 4;
this.valsToSave = {};
this.server = {};
this.logMessage = null;
/**
* Pre-specified participant IDs.
*/
this.participantIds = [];
for (let i=1; i<100; i++) {
this.participantIds.push('P' + i);
}
// On Linux / Mac, ports below 1024 require sudo access.
var isWin = process.platform === "win32";
if (isWin) {
this.port = 80;
} else {
this.port = 3000;
}
var settings = this; // so that settings can be modified without using 'jt.' prefix.
try {
let settingsJSONPath = path.join(this.jt.path, 'settings.json');
if (fs.existsSync(settingsJSONPath)) {
var json = fs.readJSONSync(settingsJSONPath);
for (var i in json) {
console.log('loading custom setting: ' + i + ' = ' + (/pwd|password/i.test(i) ? '********' : JSON.stringify(json[i])));
this[i] = json[i];
}
}
} catch (err) {
console.log('Error loading settings JSON file: ' + err);
this.logMessage = err;
}
try {
let settingsJSPath = path.join(this.jt.path, 'settings.js');
if (fs.existsSync(settingsJSPath)) {
var settingsFile = fs.readFileSync(settingsJSPath, 'utf8');
console.log('loading custom settings from settings.js');
eval(settingsFile);
}
} catch (err) {
console.log('Error reading settings JS file: ' + err);
this.logMessage = err;
}
// PORT, as JAS and the launchers use, wins over settings.json; JTREE_MODE over its mode.
if (process.env.PORT) {
this.port = parseInt(process.env.PORT);
}
if (process.env.JTREE_MODE) {
this.mode = process.env.JTREE_MODE;
}
if (!Settings.MODES.includes(this.mode)) {
console.log('Unknown mode ' + JSON.stringify(this.mode) + ' (one of ' + Settings.MODES.join(', ') + '): using jtree');
this.mode = 'jtree';
}
}
/**
* Filename of log file. By default, returns YYYYMMDD-HHmmss-SSS log.txt
*/
getLogFilename() {
return Utils.getDate(new Date()) + '.txt';
}
getConsoleTimeStamp() {
return Utils.getDate(new Date());
}
/**
* Return the i-th participant ID.
*
* @param {type} num description
* @return {type} description
*/
getParticipantId(num) {
return this.participantIds[num];
}
/** Writes valsToSave into settings.json, keeping the settings already in it. */
save() {
let file = path.join(this.jt.path, 'settings.json');
let json = {};
try {
if (fs.existsSync(file)) {
json = fs.readJSONSync(file);
}
} catch (err) {
console.log('Error reading settings JSON file: ' + err);
}
Utils.writeJSON(file, Object.assign(json, this.valsToSave));
}
setDefaultAdminPwd(curPwd, newPwd) {
if (this.defaultAdminPwd === undefined || curPwd === this.defaultAdminPwd) {
this.defaultAdminPwd = newPwd;
this.valsToSave.defaultAdminPwd = newPwd;
this.save();
}
}
setMultipleUsers(b) {
this.multipleUsers = b;
this.valsToSave.multipleUsers = b;
this.save();
}
outputFields() {
var fields = [];
for (var prop in this) {
if (
!Utils.isFunction(this[prop]) &&
prop !== 'jt'
)
fields.push(prop);
}
return fields;
}
/** The settings sent to admin pages, without passwords. */
shell() {
var out = {};
var fields = this.outputFields();
for (var i in fields) {
var field = fields[i];
out[field] = this[field];
}
out.defaultAdminPwd = this.defaultAdminPwd ? '********' : undefined;
out.admins = {};
for (var id in this.admins) {
out.admins[id] = { pwd: '********' };
}
out.valsToSave = Object.assign({}, this.valsToSave);
delete out.valsToSave.defaultAdminPwd;
return out;
}
}
/** The modes the server and its sessions can run in (see Settings#mode). */
Settings.MODES = ['jtree', 'ztree', 'otree'];
var exports = module.exports = {};
exports.new = Settings;
exports.MODES = Settings.MODES;