1295 lines
36 KiB
JavaScript
1295 lines
36 KiB
JavaScript
/**
|
|
* This is the core of ANSItex
|
|
*
|
|
* When called as login.js, it will handle the un-authenticated user.
|
|
* When called as main.js, it's assumed that the user has been authenticated.
|
|
* (In fact the functionality between the two is no different.)
|
|
*/
|
|
|
|
log(LOG_DEBUG,'* INIT: ANSItex');
|
|
var debug_mode = ''; // eg: 'user/password/10010010001';
|
|
|
|
// SBBS Key definitions
|
|
require('key_defs.js','KEY_ESC');
|
|
|
|
// ANSItex specific includes
|
|
load('ansitex/load/funcs.js');
|
|
|
|
// Our page handler
|
|
load('ansitex/load/page.js');
|
|
|
|
// @todo Returning from chat should refresh the frame
|
|
// @todo Suppress displays of telegrams (but trigger when they arrive)
|
|
/**
|
|
* SBBS terminal settings init
|
|
*/
|
|
// Dont always ask for a password.
|
|
bbs.node_settings &= ~(NM_LOGON_P);
|
|
// We'll take care of inactivity
|
|
bbs.node_settings &= NM_NO_INACT;
|
|
// No spinning cursors
|
|
bbs.node_settings &= NM_NOPAUSESPIN;
|
|
// Dont allow users to login with a number
|
|
bbs.node_settings &= NM_NO_NUM;
|
|
// @todo Suppress "Read your mail now" at login
|
|
// @todo Update message loading to process mystic color codes - this todo shouldnt be here, but placed here so I would see it and move it
|
|
// @todo Add "time ago" for messages, as an easier visual of how old it is.
|
|
|
|
// Suppress some SBBS message prompts, as we handle them
|
|
bbs.replace_text(390,''); // Unknown User
|
|
bbs.replace_text(391,''); // Invalid Login
|
|
bbs.replace_text(826,''); // LoggingOn
|
|
|
|
switch (client.socket.local_port) {
|
|
case 516:
|
|
var SESSION_EXT = 'vtx';
|
|
require(ANSITEX_HOME+'/load/session/viewdata.js','SESSION_VIEWDATA');
|
|
break;
|
|
|
|
// Assume ANSItex
|
|
default:
|
|
var SESSION_EXT = 'tex';
|
|
require(ANSITEX_HOME+'/load/session/ansitex.js','SESSION_ANSITEX');
|
|
}
|
|
|
|
/**
|
|
* This is our main event loop - where we interact with the user.
|
|
* This loop takes care of first connect (unauthenticated users), or after authentication (main shell).
|
|
*/
|
|
while (bbs.online) {
|
|
/**
|
|
* Next action to take
|
|
* - NULL means no action
|
|
* - ACTION_* is the action (as defined in defs.js)
|
|
* @type {number|null}
|
|
*/
|
|
var action = ACTION_GOTO; // Initial action
|
|
|
|
/**
|
|
* State of the current action
|
|
* - NULL means we are not doing anything
|
|
* - MODE_* is the mode (as defined in defs.js)
|
|
* @type {number|null}
|
|
*/
|
|
var mode = null; // Initial mode
|
|
|
|
/**
|
|
* The next page to display
|
|
* @type {PageObject}
|
|
*/
|
|
var next_page = new PageObject(user.name ? FRAME_HOME_AUTH : FRAME_HOME_CONNECT); // Start Frame
|
|
|
|
/**
|
|
* Variable holding our current key timeout value
|
|
* @type {number}
|
|
*/
|
|
var inkey_timeout = INACTIVE_TIMEOUT;
|
|
|
|
/**
|
|
* Current Session Object that describe the terminal that the user has connected on
|
|
* - SessionViewdata - for ViewData sessions
|
|
* - SessionAnsitex - for ANSItex sessions
|
|
* @type {SessionProtocol}
|
|
*/
|
|
var so = new SessionProtocol();
|
|
|
|
/**
|
|
* History of PageObjects that the user has seen this session
|
|
* @type {array}
|
|
*/
|
|
var history = [];
|
|
|
|
/**
|
|
* Current input field being edited when a frame has input fields
|
|
* - NULL means we are not inputting on a field
|
|
* @type {number|null}
|
|
*/
|
|
var fn = null;
|
|
|
|
/**
|
|
* Current Input Field.
|
|
* @type {object}
|
|
*/
|
|
var cf = null;
|
|
|
|
/**
|
|
* User has hit the inactivity timeout without any input
|
|
* @type {boolean}
|
|
*/
|
|
var timeout = false;
|
|
|
|
/**
|
|
* Time the user hit the inactivity timeout
|
|
* @type {number}
|
|
*/
|
|
var timer = time();
|
|
|
|
/**
|
|
* Current Control Method
|
|
* @type {null}
|
|
*/
|
|
var cc = null;
|
|
|
|
/**
|
|
* Which command control methods are in play and process input
|
|
* @type {array}
|
|
*/
|
|
var control = [];
|
|
|
|
/**
|
|
* The command being entered on the bottom line
|
|
* @type {string}
|
|
*/
|
|
var cmd = '';
|
|
|
|
/**
|
|
* We are receiving an extended key sequence (like a function key)
|
|
* @type {string}
|
|
*/
|
|
var extendedkey = '';
|
|
|
|
/**
|
|
* The current input character
|
|
* @type {string}
|
|
*/
|
|
var read = '';
|
|
|
|
/**
|
|
* ESC key sequence received
|
|
*/
|
|
var esc = false;
|
|
|
|
while (action !== ACTION_TERMINATE && action !== ACTION_EXIT && bbs.online) {
|
|
try {
|
|
log(LOG_DEBUG,'- Start ACTION is ['+action+']');
|
|
|
|
bbs.nodesync();
|
|
|
|
read = '';
|
|
esc = false;
|
|
|
|
// If we have no action, read from the terminal
|
|
if (action === null) {
|
|
// If a special key sequence is coming...
|
|
while ((esc || ! read) && (action !== ACTION_TERMINATE)) {
|
|
log(LOG_DEBUG,'================================================');
|
|
log(LOG_DEBUG,'- READ START : control.length='+control.length);
|
|
log(LOG_DEBUG,'- READ START : inkey_timeout='+inkey_timeout);
|
|
|
|
// Wait for a key from the user
|
|
read = console.inkey(K_NONE,inkey_timeout);
|
|
|
|
// We are entering a special keyboard char.
|
|
if (read === KEY_ESC) {
|
|
log(LOG_DEBUG,'- READ SPECIAL KEY COMING');
|
|
esc = true;
|
|
// We reduce our timeout, and assume the key is a function key. If the user pressed ESC we'll process that later
|
|
inkey_timeout = 200;
|
|
|
|
// If we got the ESC, but no [ then re-put the ESC in the read, we loose the current key
|
|
// @todo We loose the current pressed key
|
|
} else if (esc && ! extendedkey && read !== '[') {
|
|
log(LOG_DEBUG,'- READ SPECIAL KEY ABANDONED: ['+read+'] ('+read.charCodeAt(0)+')');
|
|
esc = false;
|
|
inkey_timeout = INACTIVE_TIMEOUT;
|
|
read = KEY_ESC;
|
|
|
|
// Recognise when the ESC sequence has ended (with a ~ or ;)
|
|
} else if (esc && extendedkey && (read === '~' || read === ';' || ! read)) {
|
|
switch (extendedkey) {
|
|
case '[15': read = false; break; // F5
|
|
case '[17': read = false; break; // F6
|
|
case '[18': read = false; break; // F7
|
|
case '[19': read = false; break; // F8
|
|
case '[20': read = false; break; // F9
|
|
case '[21': read = ascii(26); break; // F10
|
|
case '[23': read = false; break; // F11
|
|
case '[24': read = false; break; // F12
|
|
default:
|
|
log(LOG_DEBUG,'- READ UNKNOWN KEY: ['+extendedkey+']');
|
|
read = '';
|
|
}
|
|
|
|
esc = false;
|
|
extendedkey = '';
|
|
inkey_timeout = INACTIVE_TIMEOUT;
|
|
|
|
// Record the character as an extended key
|
|
} else if (esc) {
|
|
log(LOG_DEBUG,'- READ SPECIAL KEY ['+read+'] ('+read.charCodeAt(0)+')');
|
|
|
|
extendedkey += read;
|
|
read = false;
|
|
}
|
|
|
|
// Calculate idle timeouts
|
|
// If the user has exemption H we dont worry about timeout
|
|
if (! read && ! (user.security.exemptions&UFLAG_H) ) {
|
|
log(LOG_DEBUG,'- READ empty, evaluating timeouts...');
|
|
|
|
// Terminate the user if they have been inactive too long.
|
|
if (time() > timer+((user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)+INACTIVE_TIMEOUT)/1000) {
|
|
so.baselineSend('INACTIVE',false);
|
|
action = ACTION_TERMINATE;
|
|
mode = null;
|
|
|
|
log(LOG_INFO,'User INACTIVE - terminating...');
|
|
|
|
// Idle warning - due to inactivity.
|
|
} else if (time() > timer+(user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)/1000) {
|
|
timeout = true;
|
|
so.baselineSend('INACTIVITY',false);
|
|
|
|
if (cf) {
|
|
so.gotoxy(cf.x+cf.value.length,cf.y);
|
|
so.attr(cf.attribute);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// If the user become active during inactivity, clear the baseline message
|
|
if (timeout) {
|
|
so.baselineClear(false);
|
|
|
|
if (cf) {
|
|
so.gotoxy(cf.x+cf.value.length,cf.y);
|
|
so.attr(cf.attribute);
|
|
}
|
|
}
|
|
|
|
timer = time();
|
|
timeout = false;
|
|
}
|
|
|
|
// If we are in a control, we need to break here so that the control takes the input
|
|
if (control.length)
|
|
break;
|
|
}
|
|
}
|
|
|
|
log(LOG_DEBUG,'READ: ['+read+'] ('+read.charCodeAt(0)+')');
|
|
|
|
// to ensure our node status is updated correctly
|
|
system.node_list[bbs.node_num-1].action = 0xff;
|
|
|
|
// After reading from the terminal, see if we need to pass that input to a control module,
|
|
// except if input is on the bottom line
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
log(LOG_DEBUG,'CONTROL: mode ['+mode+'] ('+control.length+')');
|
|
if ((mode !== MODE_BL) && control.length) {
|
|
//log(LOG_DEBUG,'CONTROL DEBUG: ['+control.length+'] ('+JSON.stringify(control)+')');
|
|
cc = control[control.length-1];
|
|
log(LOG_DEBUG,'CONTROL IS: ['+typeof cc+']');
|
|
log(LOG_DEBUG,'CONTROL START: ['+read+'] ('+cc.getName+')');
|
|
// We pass the read to the control and see if it consumes it.
|
|
read = cc.handle(read);
|
|
log(LOG_DEBUG,'CONTROL RETURN: ['+read+'] ('+cc.isComplete+')');
|
|
|
|
if (cc.isComplete) {
|
|
control.pop();
|
|
cc = null;
|
|
log(LOG_DEBUG,'CONTROL COMPLETE: ['+read+'] ('+control.length+')');
|
|
|
|
// If there are no more control items
|
|
if (control.length === 0)
|
|
inkey_timeout = INACTIVE_TIMEOUT;
|
|
}
|
|
}
|
|
|
|
log(LOG_DEBUG,'CONTROL END: ['+read+']');
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
log(LOG_DEBUG,'MODE START: ['+read.charCodeAt(0)+'] ('+mode+')');
|
|
switch (mode) {
|
|
// Normal navigation
|
|
case null:
|
|
log(LOG_DEBUG,'- MODE NULL: ['+read+']');
|
|
|
|
switch (read) {
|
|
case '*': action = ACTION_STAR;
|
|
break;
|
|
|
|
// Frame Routing
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
log(LOG_DEBUG,'- MODE null: Key ['+read+'] Route ['+so.page.key[read]+']');
|
|
|
|
if (so.page.key[read] !== null) {
|
|
// If a page routes to 0, requesting the home page
|
|
if (so.page.key[read] === 0) {
|
|
next_page = new PageObject(user.number ? FRAME_HOME : FRAME_LOGIN);
|
|
action = ACTION_GOTO;
|
|
log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+next_page.toString()+']');
|
|
|
|
} else if (so.page.key[read].toString().match(/^[0-9]+/)) {
|
|
next_page = new PageObject(so.page.key[read],'a');
|
|
action = ACTION_GOTO;
|
|
log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+next_page.toString()+']');
|
|
|
|
} else {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
}
|
|
|
|
} else {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
}
|
|
|
|
break;
|
|
|
|
case '_':
|
|
// Viewdata terminal's # is an _ character
|
|
if (SESSION_EXT === 'tex') break;
|
|
/* fallthrough */
|
|
case '#':
|
|
log(LOG_DEBUG,'- NULL: Key ['+read+'] ['+so.page.name.toString()+']');
|
|
next_page = so.page.pagenext;
|
|
|
|
if (next_page) {
|
|
action = ACTION_GOTO;
|
|
|
|
} else {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
// Command input on bottom line
|
|
case MODE_BL:
|
|
log(LOG_DEBUG,'- MODE_BL: ['+read+']');
|
|
if (read.match(/[0-9]/)) {
|
|
cmd += read;
|
|
console.write(read);
|
|
}
|
|
|
|
log(LOG_DEBUG,'- MODE_BL: cmd ['+cmd+']');
|
|
|
|
// If the user pressed backspace
|
|
// @todo We should get the user's configuration of backspace
|
|
if ((read === CTRL_H || read === KEY_DEL) && cmd.length) {
|
|
console.backspace();
|
|
cmd = cmd.substring(0,cmd.length-1);
|
|
}
|
|
|
|
if (cmd === '00') {
|
|
action = ACTION_RELOAD;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
so.baselineClear();
|
|
break;
|
|
}
|
|
|
|
// Special code to launch SQRL
|
|
if (cmd === '01' && ! user.number) {
|
|
action = ACTION_GOTO;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
so.baselineClear();
|
|
next_page = new PageObject(FRAME_SQRL);
|
|
break;
|
|
}
|
|
|
|
// Invalid system pages.
|
|
if (cmd.match(/^0[12367]/)) {
|
|
log(LOG_DEBUG,'- MODE_BL: Invalid System Page ['+cmd+']');
|
|
|
|
action = mode = null;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
}
|
|
|
|
// Edit specific frame
|
|
if (cmd.match(/^04/) && read.match(/[a-z]/)) {
|
|
log(LOG_DEBUG,'- MODE_BL: Edit ['+cmd+']');
|
|
|
|
// If we are not a user
|
|
if (! user.number) {
|
|
action = null;
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
|
|
} else {
|
|
action = ACTION_EDIT;
|
|
so.baselineClear();
|
|
next_page = new PageObject(parseInt(cmd.substr(2,cmd.length-1)),read);
|
|
|
|
log(LOG_DEBUG,'- MODE_BL: EDIT ['+JSON.stringify(next_page.toString())+']');
|
|
}
|
|
|
|
mode = null;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
|
|
break;
|
|
}
|
|
|
|
// Bookmark frame
|
|
if (cmd === '05') {
|
|
log(LOG_DEBUG,'- MODE_BL: Bookmark ['+cmd+']');
|
|
|
|
if (! user.number) {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
|
|
} else {
|
|
// @todo
|
|
so.baselineSend('ERR_NOT_IMPLEMENTED',false);
|
|
}
|
|
|
|
action = mode = null;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
|
|
break;
|
|
}
|
|
|
|
// Report Problem
|
|
if (cmd === '08') {
|
|
log(LOG_DEBUG,'- MODE_BL: Report Problem ['+cmd+'] ('+so.page.name.toString()+')');
|
|
|
|
if (! user.number) {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
|
|
} else {
|
|
// @todo
|
|
so.baselineSend('ERR_NOT_IMPLEMENTED',false);
|
|
}
|
|
|
|
action = mode = null;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
|
|
break;
|
|
}
|
|
|
|
// Reload frame
|
|
if (cmd === '09') {
|
|
log(LOG_DEBUG,'- MODE_BL: Reload frame ['+cmd+'] ('+so.page.name.toString()+')');
|
|
|
|
action = ACTION_GOTO;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
next_page = so.page.name;
|
|
|
|
break;
|
|
}
|
|
|
|
// Another star aborts the command
|
|
if (read === '*') {
|
|
log(LOG_DEBUG,'- MODE_BL: Abort ['+cmd+'])');
|
|
|
|
// 695 = NodeActionMain
|
|
bbs.replace_text(695,'\1h%s \1nViewing \1h*'+so.page.name.toString()+'#\1n');
|
|
bbs.node_action=NODE_MAIN;
|
|
|
|
action = mode = null;
|
|
cmd = '';
|
|
so.cursorOff();
|
|
so.baselineClear(false);
|
|
|
|
if (cf) {
|
|
// If there is a control for this field,
|
|
if (cc)
|
|
cc.prefield();
|
|
|
|
mode = MODE_FIELD;
|
|
so.gotoxy(cf.x,cf.y);
|
|
so.attr(cf.attribute);
|
|
console.write(cf.char.repeat(cf.value.length));
|
|
so.cursorOn(cf.x,cf.y);
|
|
cf.value = '';
|
|
}
|
|
}
|
|
|
|
if (((SESSION_EXT === 'vtx') && read === '_') || ((SESSION_EXT === 'tex') && read === '#') || ((read === "\r") && (cmd.length > 0))) {
|
|
log(LOG_DEBUG,'- MODE_BL: Return Received ['+cmd+'])');
|
|
|
|
// Nothing typed between * and #
|
|
// *# means go back
|
|
if (cmd === '') {
|
|
so.baselineClear(false);
|
|
action = ACTION_BACKUP;
|
|
|
|
} else if (cmd === '0') {
|
|
next_page = new PageObject(user.number ? FRAME_HOME : FRAME_LOGIN);
|
|
action = ACTION_GOTO;
|
|
|
|
// Edit frame
|
|
} else if (cmd === '04') {
|
|
// If we are not a user
|
|
if (! user.number) {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
action = null;
|
|
|
|
} else {
|
|
action = ACTION_EDIT;
|
|
}
|
|
|
|
} else {
|
|
next_page = new PageObject(cmd,'a');
|
|
action = ACTION_GOTO;
|
|
}
|
|
|
|
// Clear the command we are finished processing...
|
|
cmd = '';
|
|
mode = null;
|
|
so.cursorOff();
|
|
}
|
|
|
|
log(LOG_DEBUG,'- MODE_BL: END');
|
|
break;
|
|
|
|
// Key presses during field input.
|
|
case MODE_FIELD:
|
|
action = null;
|
|
|
|
switch (so.page.type) {
|
|
// Login frame.
|
|
case FRAME_TYPE_LOGIN:
|
|
switch (read) {
|
|
case '_':
|
|
if (SESSION_EXT === 'tex') break;
|
|
/* fallthrough */
|
|
case '#':
|
|
case "\r":
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_LOGIN: ['+read+'] A');
|
|
// If we are the main login screen, see if it is a new user
|
|
if (cf.type === FIELD_TEXT && cf.value.toUpperCase() === 'NEW') {
|
|
action = ACTION_GOTO;
|
|
next_page = new PageObject(FRAME_REGISTER);
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
/* fallthrough */
|
|
// Response frame.
|
|
case FRAME_TYPE_RESPONSE:
|
|
// If we came from FRAME_TYPE_LOGIN and the user typed NEW to register
|
|
if (action === ACTION_GOTO)
|
|
break;
|
|
|
|
switch (read) {
|
|
// End of field entry.
|
|
case '_':
|
|
if (SESSION_EXT === 'tex') break;
|
|
/* fallthrough */
|
|
case '#':
|
|
case "\r":
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: # ['+read+']');
|
|
// Next Field
|
|
fn++;
|
|
|
|
cf = so.page.input_fields[fn];
|
|
log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf));
|
|
|
|
if (cf) {
|
|
// If there is a control for this field,
|
|
if (cc)
|
|
cc.prefield();
|
|
|
|
mode = MODE_FIELD;
|
|
so.gotoxy(cf.x+cf.value.length,cf.y);
|
|
so.attr(cf.attribute);
|
|
|
|
// Finished all editable fields.
|
|
} else {
|
|
action = ACTION_SUBMITRF;
|
|
}
|
|
|
|
break;
|
|
|
|
case '*':
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+']');
|
|
|
|
action = ACTION_STAR;
|
|
|
|
break;
|
|
|
|
// Delete Key pressed
|
|
case CTRL_H:
|
|
case KEY_DEL:
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: DEL ['+read+']'+' cf:'+(cf ? cf.value.length : '{}')+' ct:'+cf.type);
|
|
|
|
if (cf.value.length > 0) {
|
|
cf.value = cf.value.substring(0,cf.value.length-1);
|
|
so.fieldbs(cf.char);
|
|
}
|
|
|
|
break;
|
|
|
|
case KEY_ESC:
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ESC ['+read+']');
|
|
break;
|
|
|
|
case KEY_DOWN:
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: DOWN ['+read+']');
|
|
// Next Field
|
|
fn++;
|
|
|
|
cf = so.page.input_fields[fn];
|
|
log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf));
|
|
|
|
if (! cf) {
|
|
fn = 0;
|
|
cf = so.page.input_fields[fn];
|
|
}
|
|
|
|
// If there is a control for this field,
|
|
if (cc)
|
|
cc.prefield();
|
|
|
|
mode = MODE_FIELD;
|
|
so.gotoxy(cf.x+cf.value.length,cf.y);
|
|
so.attr(cf.attribute);
|
|
|
|
break;
|
|
|
|
case KEY_UP:
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: UP ['+read+']');
|
|
// Next Field
|
|
fn--;
|
|
|
|
if (fn < 0) {
|
|
fn = so.page.input_fields.length-1;
|
|
}
|
|
|
|
cf = so.page.input_fields[fn];
|
|
log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf));
|
|
|
|
// If there is a control for this field,
|
|
if (cc)
|
|
cc.prefield();
|
|
|
|
mode = MODE_FIELD;
|
|
so.gotoxy(cf.x+cf.value.length,cf.y);
|
|
so.attr(cf.attribute);
|
|
|
|
break;
|
|
|
|
// Record Data Entry
|
|
default:
|
|
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] E:'+read.charCodeAt(0)+' cf:'+(cf ? cf.length : '{}'));
|
|
|
|
if (read.charCodeAt(0) > 31 && cf.value.length < cf.length) {
|
|
cf.value += read;
|
|
console.write((cf.type === FIELD_TEXT) ? read : FIELD_PASSWORD_MASK);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
// Other Frame Types - Shouldnt get here.
|
|
default:
|
|
log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']');
|
|
action = ACTION_TERMINATE;
|
|
}
|
|
|
|
break;
|
|
|
|
// Form submission: 1 to send, 2 not to send.
|
|
case MODE_SUBMITRF:
|
|
so.cursorOff();
|
|
|
|
switch (read) {
|
|
case '1':
|
|
so.baselineSend('PROCESSING');
|
|
|
|
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(so)+']');
|
|
log(LOG_DEBUG,' - Frame fields: '+JSON.stringify(so.page.input_fields));
|
|
log(LOG_DEBUG,' - Key 1 is:'+JSON.stringify(so.page.key[1]));
|
|
|
|
// If we are in a control method, complete it
|
|
if (control.length) {
|
|
log(LOG_DEBUG,'Last control method is:'+JSON.stringify(control[control.length-1]));
|
|
|
|
control[control.length-1].process();
|
|
|
|
} else if (so.page.key[1] === '*' || so.page.key[1].match(/[0-9]/)) {
|
|
so.baselineSend('NOACTION',false);
|
|
mode = MODE_RFSENT;
|
|
|
|
} else {
|
|
log(LOG_DEBUG,' - Key 1 is a METHOD check it exists: '+JSON.stringify(so.page.key[1]));
|
|
|
|
switch(so.page.key[1]) {
|
|
// User is logging in to system or CUG
|
|
case 'login':
|
|
log(LOG_DEBUG,' - User: '+so.page.input_fields[0].value+'/'+so.page.input_fields[1].value);
|
|
|
|
// In debug mode, we'll authenticate for the user
|
|
if (debug_mode) {
|
|
log(LOG_DEBUG,' - Debug mode user'+debug_mode.split('/')[0]);
|
|
log(LOG_DEBUG,' - Debug mode pass'+debug_mode.split('/')[1]);
|
|
if (bbs.login(debug_mode.split('/')[0],'',debug_mode.split('/')[1])) {
|
|
log(LOG_DEBUG,' - User: '+JSON.stringify(user.number));
|
|
bbs.logon();
|
|
log(LOG_DEBUG,' - SEND TO EXIT:');
|
|
|
|
action = ACTION_EXIT;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If login is successful, we'll exit here
|
|
if (bbs.login(so.page.input_fields[0].value,'',so.page.input_fields[1].value)) {
|
|
log(LOG_DEBUG,' - User: '+JSON.stringify(user.number));
|
|
bbs.logon();
|
|
log(LOG_DEBUG,' - SEND TO EXIT:');
|
|
|
|
action = ACTION_EXIT;
|
|
break;
|
|
}
|
|
|
|
log(LOG_DEBUG,' ! Login failed for User:'+JSON.stringify(so.page.input_fields[0].value));
|
|
action = ACTION_GOTO;
|
|
next_page = new PageObject(FRAME_LOGIN_FAILED);
|
|
break;
|
|
|
|
default:
|
|
// Its assumed that you get here after completing a form and you have pressed 1 to submit that form.
|
|
log(LOG_DEBUG,' ! EVAL method:'+JSON.stringify(so.page.key));
|
|
if (cc.process()) {
|
|
control.pop();
|
|
log(LOG_DEBUG,' = Process Completed.');
|
|
}
|
|
}
|
|
/*
|
|
|
|
} elseif ($ao = FrameClass\Action::factory($this->fo->route(1),$this,$user,$action,$mode)) {
|
|
$ao->handle();
|
|
$mode = $ao->mode;
|
|
$action = $ao->action;
|
|
|
|
if ($ao->page)
|
|
$next_page = $ao->page;
|
|
|
|
} else {
|
|
so.baselineSend('ERR_METHOD_NOT_EXIST',false);
|
|
|
|
mode = MODE_RFSENT;
|
|
}
|
|
*/
|
|
}
|
|
|
|
break;
|
|
|
|
case '2':
|
|
so.baselineClear();
|
|
|
|
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(so)+']');
|
|
// @todo Check if HASH is a valid next destination
|
|
|
|
if (so.page.type === 'l') {
|
|
action = ACTION_RELOAD;
|
|
mode = null;
|
|
|
|
} else {
|
|
so.baselineSend('MSG_NOTSENT',false);
|
|
mode = MODE_RFNOTSENT;
|
|
}
|
|
|
|
/*
|
|
// If a Control method was rejected, we can clear it
|
|
if ($control AND $method->count()) {
|
|
$save = $method->pop();
|
|
|
|
if ($method->count()) {
|
|
$control = $method->last()->state['control'];
|
|
|
|
} else {
|
|
$mode = $save->state['mode'];
|
|
$action = $save->state['action'];
|
|
$control = FALSE;
|
|
}
|
|
}
|
|
*/
|
|
|
|
break;
|
|
|
|
case '*':
|
|
action = ACTION_STAR;
|
|
|
|
break;
|
|
}
|
|
|
|
// Destroy the object.
|
|
cc = null;
|
|
|
|
break;
|
|
|
|
// Response form after Sent processing
|
|
case MODE_RFSENT:
|
|
so.cursorOff();
|
|
|
|
switch (read) {
|
|
case '*':
|
|
action = ACTION_STAR;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
// @todo to implement
|
|
if ($read === HASH) {
|
|
if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) {
|
|
$next_page = ['frame'=>$x];
|
|
|
|
} elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) {
|
|
$next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()];
|
|
|
|
} elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) {
|
|
$next_page = ['frame'=>$x];
|
|
|
|
// No further routes defined, go home.
|
|
} else {
|
|
$next_page = ['frame'=>0];
|
|
}
|
|
|
|
$action = ACTION_GOTO;
|
|
}
|
|
*/
|
|
|
|
break;
|
|
|
|
// Response form after NOT sending
|
|
case MODE_RFNOTSENT:
|
|
|
|
// Response form ERROR
|
|
case MODE_RFERROR:
|
|
so.cursorOff();
|
|
|
|
if (((SESSION_EXT === 'vtx') && read === '_') || ((SESSION_EXT === 'tex') && read === '#')) {
|
|
/*
|
|
// @todo to implement
|
|
if ($x = $this->fo->route(2) AND $x !== '*' AND is_numeric($x)) {
|
|
$next_page = ['frame'=>$x];
|
|
|
|
} elseif (FrameModel::where('frame',$this->fo->frame())->where('index',$this->fo->index_next())->exists()) {
|
|
$next_page = ['frame'=>$this->fo->frame(),'index'=>$this->fo->index_next()];
|
|
|
|
} elseif ($x = $this->fo->route(0) AND $x !== '*' AND is_numeric($x)) {
|
|
$next_page = ['frame'=>$x];
|
|
|
|
// No further routes defined, go home.
|
|
} else {
|
|
$next_page = ['frame'=>0];
|
|
}
|
|
*/
|
|
|
|
action = ACTION_GOTO;
|
|
|
|
} else if (read === '*') {
|
|
action = ACTION_STAR;
|
|
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']');
|
|
action = ACTION_TERMINATE;
|
|
}
|
|
log(LOG_DEBUG,'MODE END: ['+read+']');
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
log(LOG_DEBUG,'ACTION START: ['+read+'] ('+action+')');
|
|
switch (action) {
|
|
// Start command entry
|
|
case ACTION_STAR:
|
|
log(LOG_DEBUG,'- ACTION_STAR: ['+(next_page ? pageStr(next_page) : '')+']');
|
|
|
|
// @todo If something on the baseline preserve it
|
|
// @todo This should be SCREEN_LENGTH, not hard coded 24
|
|
so.cursorOn(0,24);
|
|
so.baselineSend('BASESTAR',true);
|
|
action = null;
|
|
mode = MODE_BL;
|
|
|
|
// 719 = NodeActionRetrieving
|
|
bbs.replace_text(719,'\1h%s \1n\1gJumping to page');
|
|
bbs.node_action=NODE_RFSD;
|
|
|
|
break;
|
|
|
|
// Submitting forms
|
|
case ACTION_SUBMITRF:
|
|
action = null;
|
|
mode = MODE_SUBMITRF;
|
|
so.cursorOff();
|
|
|
|
log(LOG_DEBUG,'- ACTION_SUBMITRF: ['+so.page.type+']');
|
|
so.baselineSend((so.page.type === 'l' ? 'MSG_LOGON' : 'MSG_SENDORNOT'),true);
|
|
|
|
break;
|
|
|
|
// Edit a frame
|
|
case ACTION_EDIT:
|
|
log(LOG_DEBUG,'- ACTION_EDIT: ['+JSON.stringify(next_page)+']');
|
|
|
|
if ((so.page.type === FRAME_TYPE_MESSAGE) || (! pageEditor(next_page ? next_page.frame : so.frame))) {
|
|
action = null;
|
|
so.cursorOff();
|
|
so.baselineSend('ACCESS_DENIED',false);
|
|
|
|
break;
|
|
}
|
|
|
|
require('ansitex/load/control/frameedit.js','CONTROL_FRAMEEDIT');
|
|
|
|
// If we are editing a specific frame, attempt to load it
|
|
if (next_page) {
|
|
// In case we need to fall back.
|
|
//var current = so;
|
|
so.get(next_page);
|
|
|
|
// If the frame doesnt exist, check that the parent frame exists in case we are creating a new one
|
|
// @todo This needs to be reworked with the new page object, and to handle editing new pages
|
|
if (so.page.name.toString() === null) {
|
|
log(LOG_DEBUG,'- ACTION_EDIT: check index: '+next_page.index+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')');
|
|
|
|
// We can always create an 'a' frame
|
|
if (next_page.index !== 'a') {
|
|
so.get(pageStr({frame: next_page.frame, index: String.fromCharCode(next_page.index.charCodeAt(0)-1)}));
|
|
|
|
log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(so)+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')');
|
|
if (so.page.name.toString() === null) {
|
|
//so = current;
|
|
//current = undefined;
|
|
// sendbaseline ERR_PAGE
|
|
so.baselineSend('ERR_NO_PARENT',false);
|
|
action = mode = null;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// New frame
|
|
so.page.name = new PageObject({frame: next_page.frame, index: next_page.index});
|
|
so.page.cost = 0;
|
|
so.page.owner = base64_decode(pageOwner(pageStr(next_page)).logo);
|
|
so.page.content = base64_encode('Start your new page...');
|
|
}
|
|
}
|
|
|
|
control.push(new edit(so));
|
|
|
|
action = mode = null;
|
|
break;
|
|
|
|
// GO Backwards
|
|
case ACTION_BACKUP:
|
|
log(LOG_DEBUG,'- ACTION_BACKUP: history size - '+history.length+' with ['+history.join('|')+']');
|
|
// Do we have anywhere to go, drop the current page from the history
|
|
if (history.length > 1)
|
|
history.pop();
|
|
|
|
// @todo If in control...
|
|
|
|
next_page = (history.length > 0) ? history[history.length-1] : null;
|
|
|
|
log(LOG_DEBUG,'- ACTION_BACKUP: Backing up to ['+JSON.stringify(next_page)+'] current ['+so.page.name.toString()+']');
|
|
|
|
// If there is no next page, we'll ignore the request.
|
|
if (! next_page || (next_page.toString() === so.page.name.toString())) {
|
|
action = null;
|
|
break;
|
|
}
|
|
|
|
// Goto specific page
|
|
case ACTION_GOTO:
|
|
// Clear any controls
|
|
control = [];
|
|
|
|
log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page.toString()+']'));
|
|
|
|
// For logged in users, we'll see if this is a mail page.
|
|
if (user.number) {
|
|
// @todo consider how we do mail security.
|
|
|
|
// Echoarea mail summary
|
|
if (/^1[0-9]{6}1$/.test(next_page.frame)) {
|
|
log(LOG_DEBUG,'- ACTION_GOTO - load echoarea summary: ['+next_page.frame+']');
|
|
|
|
var ma = new MsgAreas();
|
|
var area = ma.getArea(next_page.frame);
|
|
|
|
// If the template page doesnt exist
|
|
// @todo look for a template in the area or group first
|
|
if ((! so.get(new PageObject(MAIL_TEMPLATE_AREA_SUMMARY))) || (! area)) {
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
action = mode = null;
|
|
break;
|
|
}
|
|
|
|
// @todo Update the page details from the template
|
|
// Parent
|
|
so.page.name = new PageObject({frame: next_page.frame, index: next_page.index});
|
|
so.page.__properties__.isAccessible = true;
|
|
so.page.__properties__.isPublic = true;
|
|
so.page.build_system_fields(area);
|
|
|
|
so.page.key[0] = (''+next_page.frame).substr(0,7);
|
|
|
|
// First to me
|
|
so.page.key[1] = atcode('msg_area_msgtome_page',null,null,area);
|
|
// First Unread
|
|
so.page.key[2] = atcode('msg_area_msgunread_page',null,null,area);
|
|
|
|
// Oldest
|
|
so.page.key[3] = atcode('msg_area_msgoldest_page',null,null,area);
|
|
// Newest
|
|
so.page.key[4] = atcode('msg_area_msgnewest_page',null,null,area);
|
|
|
|
next_page = null;
|
|
|
|
// 1zzzzEEnnnn - get a message
|
|
} else if (/^1[0-9]{10}/.test(next_page.frame)) {
|
|
log(LOG_DEBUG,'- ACTION_GOTO - load message: ['+next_page.frame+']');
|
|
|
|
if (next_page.index === 'a') {
|
|
require('ansitex/load/control/echomail.js','CONTROL_ECHOMAIL');
|
|
control.push(new echomail(so,next_page.frame));
|
|
action = null;
|
|
next_page = null;
|
|
//log(LOG_DEBUG,'- ACTION_GOTO - control message: ['+JSON.stringify(control[control.length-1])+'] ('+control.length+')');
|
|
|
|
if (! control[control.length-1].ready()) {
|
|
log(LOG_DEBUG,'- ACTION_GOTO - control not ready aborting...');
|
|
control.pop();
|
|
mode = null;
|
|
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
break;
|
|
}
|
|
|
|
// @todo - Show message stats
|
|
} else if (next_page.index === 'b') {
|
|
action = mode = null;
|
|
next_page = null;
|
|
|
|
so.baselineSend('ERR_NOT_IMPLEMENTED',false);
|
|
break;
|
|
|
|
} else {
|
|
action = mode = null;
|
|
next_page = null;
|
|
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
var current = so.page; // If we dont have access to the page we load, we need to revert
|
|
if (next_page !== null) {
|
|
if (! so.get(next_page)) {
|
|
log(LOG_DEBUG,'- Next Page: ['+(next_page.toString()+'] doesnt exist?'));
|
|
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
action = mode = null;
|
|
break;
|
|
}
|
|
|
|
next_page = null;
|
|
}
|
|
|
|
// If the user has access to the frame
|
|
if (so.page.accessible) {
|
|
if (so.page.isMember && (so.page.type === FRAME_TYPE_LOGIN)) {
|
|
so.baselineSend('ALREADY_MEMBER',false);
|
|
action = mode = null;
|
|
break;
|
|
}
|
|
|
|
// Check if the frame exists, and the user is the Service Provider
|
|
} else {
|
|
so.baselineSend('ACCESS_DENIED',false);
|
|
action = mode = null;
|
|
// Reset the current frame to what it was.
|
|
so.page = current;
|
|
current = undefined;
|
|
break;
|
|
}
|
|
|
|
current = undefined;
|
|
|
|
log(LOG_DEBUG,'- ACTION_GOTO: next_page ['+JSON.stringify(next_page)+'] last history ['+JSON.stringify(history[history.length-1])+']');
|
|
|
|
// Record our history
|
|
if ((! history.length || (pageStr(history[history.length-1]) !== so.page.name)) && (so.page.type !== FRAME_TYPE_LOGIN)) {
|
|
// Ignore the login frames
|
|
if (FRAMES_NO_HISTORY.indexOf(so.page.name.toString()) === -1) {
|
|
history.push(so.page.name);
|
|
log(LOG_DEBUG,'- ACTION_GOTO: Added to history ['+so.page.name.toString()+'] now ['+history.length+']');
|
|
}
|
|
}
|
|
|
|
// Load frame
|
|
case ACTION_RELOAD:
|
|
// Clear our current field
|
|
cf = null;
|
|
|
|
log(LOG_DEBUG,'- ACTION_RELOAD: ['+(next_page ? pageStr(next_page) : '')+']');
|
|
if (debug_mode && so.page.name.toString() === '98b')
|
|
so.page.key[1] = debug_mode.split('/')[2];
|
|
|
|
console.line_counter = 0; // @todo fix to suppress a pause that is occurring before clear()
|
|
so.cursorOff();
|
|
|
|
// 695 = NodeActionMain
|
|
bbs.replace_text(695,'\1h%s \1nViewing \1h*'+so.page.name.toString()+'#\1n');
|
|
bbs.log_str(so.page.name.toString()+'|');
|
|
bbs.node_action=NODE_MAIN;
|
|
|
|
switch(so.page.type) {
|
|
// Terminate frame
|
|
case FRAME_TYPE_MAIL_TEMPLATE:
|
|
log(LOG_DEBUG,'- MAIL_TEMPLATE: ['+so.frame+']');
|
|
so.render();
|
|
action = mode = null;
|
|
|
|
break;
|
|
|
|
// Terminate frame
|
|
case FRAME_TYPE_TERMINATE:
|
|
so.render();
|
|
action = ACTION_TERMINATE;
|
|
mode = null;
|
|
|
|
break;
|
|
|
|
// External Frame
|
|
// @todo returning from the frame, go to the 0 key if it is set
|
|
case FRAME_TYPE_EXTERNAL:
|
|
var content = base64_decode(so.page.raw);
|
|
log(LOG_DEBUG,'- ACTION_GOTO: EXTERNAL ['+JSON.stringify(content)+']');
|
|
|
|
switch(content.replace(/\n/,'')) {
|
|
case 'bbs.user_config()':
|
|
case 'bbs.read_mail(MAIL_YOUR)':
|
|
case 'bbs.scan_subs(SCAN_NEW)':
|
|
case 'bbs.scan_posts()':
|
|
case 'bbs.post_msg()':
|
|
eval(content);
|
|
|
|
// Check and see if our shell was changed
|
|
if (user.command_shell !== 'ansitex') {
|
|
exit();
|
|
}
|
|
|
|
action = ACTION_BACKUP;
|
|
break;
|
|
|
|
default:
|
|
console.putmsg(JSON.stringify(content));
|
|
so.baselineSend('ERR_ROUTE',false);
|
|
action = null;
|
|
break;
|
|
}
|
|
|
|
mode = null;
|
|
break;
|
|
|
|
case FRAME_TYPE_LOGIN:
|
|
action = null;
|
|
/* fallthrough */
|
|
|
|
case FRAME_TYPE_RESPONSE:
|
|
fn = 0;
|
|
cf = null;
|
|
// In case we are reloading the frame, we need to include our input fields
|
|
so.page.build_input_fields();
|
|
so.render();
|
|
|
|
log(LOG_DEBUG,'* Response Frame with ['+so.page.input_fields.length+'] fields');
|
|
|
|
if (so.page.input_fields.length) {
|
|
cf = so.page.input_fields[fn];
|
|
log(LOG_DEBUG,'cf'+JSON.stringify(cf));
|
|
|
|
if (cf) {
|
|
mode = MODE_FIELD;
|
|
so.cursorOn(cf.x+cf.value.length,cf.y);
|
|
log(LOG_DEBUG,'- Current Field:'+JSON.stringify(cf));
|
|
log(LOG_DEBUG,'Writing attribute:'+JSON.stringify(cf.attribute));
|
|
so.attr(cf.attribute);
|
|
|
|
// There were no editable fields.
|
|
} else {
|
|
mode = MODE_COMPLETE;
|
|
so.cursorOff();
|
|
}
|
|
|
|
} else {
|
|
mode = null;
|
|
}
|
|
|
|
// If this is the register page
|
|
if (so.page.name.toString() === (new PageObject(FRAME_REGISTER)).toString()) {
|
|
log(LOG_DEBUG,'Adding REGISTER to control stack');
|
|
require('ansitex/load/control/'+so.page.key[1]+'.js','CONTROL_REGISTER');
|
|
//control.push(eval('new '+so.page.key[1]+'(so);'));
|
|
control.push(new register(so));
|
|
|
|
} else if (so.page.name.toString() === (new PageObject(FRAME_SQRL)).toString()) {
|
|
log(LOG_DEBUG,'Adding SQRL to control stack');
|
|
require('ansitex/load/control/'+so.page.key[1]+'.js','CONTROL_SQRL');
|
|
control.push(eval('new '+so.page.key[1]+'(so);'));
|
|
inkey_timeout = 1000;
|
|
|
|
} else if (so.page.key[1] && (so.page.type === FRAME_TYPE_RESPONSE) && (typeof(so.page.key[1]) !== 'number')) {
|
|
log(LOG_DEBUG,'Adding METHOD to control stack: '+so.page.key[1]);
|
|
require('ansitex/load/control/'+so.page.key[1]+'.js','CONTROL_'+so.page.key[1].toUpperCase());
|
|
control.push(eval('new '+so.page.key[1]+'(so);'));
|
|
}
|
|
|
|
action = null;
|
|
|
|
break;
|
|
|
|
// Standard Frame
|
|
case FRAME_TYPE_INFO:
|
|
default:
|
|
so.render();
|
|
action = mode = null;
|
|
break;
|
|
|
|
// Active frame
|
|
}
|
|
|
|
break;
|
|
}
|
|
log(LOG_DEBUG,'ACTION END: ['+read+']');
|
|
log(LOG_DEBUG,'------------------------------------------------');
|
|
|
|
} catch (e) {
|
|
log(LOG_ERROR,JSON.stringify(e));
|
|
so.baselineSend('SYS_ERROR',false);
|
|
action = mode = null;
|
|
}
|
|
}
|
|
|
|
log(LOG_DEBUG,'- FINISHED');
|
|
if (action === ACTION_TERMINATE) {
|
|
log(LOG_DEBUG,'! Hangup');
|
|
so.cursorOn();
|
|
bbs.hangup();
|
|
}
|
|
|
|
// We need to clear the baseline before we exit, esp during transition from login -> logged on
|
|
so.baselineClear();
|
|
exit();
|
|
}
|