sbbs/main.js
2022-05-09 22:24:28 +10:00

1190 lines
32 KiB
JavaScript

log(LOG_DEBUG,'- INIT: ANSITEX');
// Load many SBBS definitions
require('sbbsdefs.js','SS_USERON');
// Load text.dat definitions
require('text.js','TOTAL_TEXT');
// Key definitions
require('key_defs.js','KEY_ESC');
ansi = load({},'ansiterm_lib.js');
load('frame.js');
load('graphic.js');
// Ansitex specific includes
load('ansitex/load/funcs.js');
load('ansitex/load/msgbases.js');
require('ansitex/load/defs.js','ACTION_EXIT');
require('ansitex/load/frame-ansi.js','FRAME_ANSI');
require('ansitex/load/frame-viewdata.js','FRAME_VIEWDATA');
// @todo Returning from chat should refresh the frame
// @todo Suppress displays of telegrams
bbs.node_settings &= ~(NM_LOGON_P); // Dont always ask for a password.
/**
* This is our main event loop - we where interact with the user.
* This loop takes care of first connect (unauthenticated users), or after authentication (main shell).
*/
while(bbs.online) {
/**
* State of the current action
* - FALSE means we are not doing anything
* - MODE_* is the mode (as defined in defs.js)
* @type number|boolean
*/
var mode = false; // Initial mode
/**
* The next page to render
* @type {{frame: number,index: string}}
*/
var next_page = user.name ? HOME_FRAME_AUTH : HOME_FRAME_CONNECT; // Start Frame
/**
* Next action to take
* - FALSE means no action
* - ACTION_* is the action (as defined in defs.js)
* @type {number}
*/
var action = ACTION_GOTO; // Initial action
/**
* Variable holding our current key timeout value
* @type {number}
*/
var inkey_timeout = INKEY_TIMEOUT;
/**
* Current Frame Object that is being displayed to the user
* - FrameViewdata - for viewdata frames
* - FrameAnsi - for ANSItex frames
* @type {FrameAnsi|FrameViewdata}
*/
var fo = null;
/**
* 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;
/**
* History of frames that the user has seen this session
* @type {array}
*/
var history = [];
/**
* Current Input Field.
* @type {object}
*/
var cf = null;
var cc = null; // Current Control Method
/**
* 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();
/**
* 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 = '';
/**
* Our session is a viewdata session or an ansitex session
* @type {boolean}
*/
const viewdata = (client.socket.local_port === 516);
while (action !== ACTION_TERMINATE && action !== ACTION_EXIT && bbs.online) {
try {
bbs.nodesync(); // @todo Stop the display of telegrams
/* The current input character */
var read = '';
/* ESC key sequence received */
var esc = false;
// If we have no action, read from the terminal
if (action === false) {
// If a special key sequence is coming...
while ((esc || ! read) && (action !== ACTION_TERMINATE)) {
log(LOG_DEBUG,'- READ START');
// 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 = INKEY_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 = INKEY_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) ) {
// Terminate the user if they have been inactive too long.
if (time() > timer+((user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)+INKEY_TIMEOUT)/1000) {
fo.sendBaseline('INACTIVE',false);
action = ACTION_TERMINATE;
mode = false;
log(LOG_INFO,'User INACTIVE - terminating...');
// Idle warning - due to inactivity.
} else if (time() > timer+(user.number ? INACTIVE_LOGIN : INACTIVE_NOLOGIN)/1000) {
timeout = true;
fo.sendBaseline('INACTIVITY',false);
if (cf) {
fo.gotoxy(cf.c+cf.fvalue.length,cf.r);
fo.attr(cf.attribute);
}
}
} else {
// If the user become active during inactivity, clear the baseline message
if (timeout) {
fo.clearBaseline(false);
if (cf) {
fo.gotoxy(cf.c+cf.fvalue.length,cf.r);
fo.attr(cf.attribute);
}
}
timer = time();
timeout = false;
}
if (esc) {
log(LOG_DEBUG,'- READ SPECIAL KEY LOOP');
}
}
}
log(LOG_DEBUG,'READ: ['+read+'] ('+read.charCodeAt(0)+')');
system.node_list[bbs.node_num-1].action=0xff; // to ensure our node status is updated correctly
// 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,'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+')');
}
log(LOG_DEBUG,'CONTROL END: ['+read+']');
}
log(LOG_DEBUG,'MODE START: ['+read.charCodeAt(0)+']');
switch (mode) {
// Normal navigation
case false:
log(LOG_DEBUG,'- MODE false: ['+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 false: Key ['+read+'] Route ['+fo.key[read]+']');
if (fo.key[read] !== null) {
// If are requesting a home page
if (fo.key[read] === 0) {
next_page = user.number ? HOME_FRAME : LOGIN_FRAME;
} else {
next_page = {frame: fo.key[read],index: 'a'};
}
action = ACTION_GOTO;
log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']');
} else {
fo.sendBaseline('ERR_ROUTE',false);
}
break;
case '_':
// Viewdata terminal's # is an _ character
if (! viewdata) break;
/* fallthrough */
case '#':
log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(fo)+']');
if (fo.index !== 'z') {
next_page = {frame: fo.frame,index: String.fromCharCode(fo.index.charCodeAt(0)+1)};
action = ACTION_GOTO;
} else {
fo.sendBaseline('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);
}
// 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 = '';
fo.cursorOff();
break;
}
if (cmd === '01' && ! user.number) {
action = ACTION_GOTO;
next_page = SQRL_FRAME;
break;
}
// Invalid system pages.
if (cmd.match(/^0[2367]/)) {
fo.cursorOff();
fo.sendBaseline('ERR_ROUTE',false);
mode = action = false;
cmd = '';
}
// Edit specific frame
if (cmd.match(/^04/) && read.match(/[a-z]/)) {
// If we are not a user
if (! user.number) {
fo.cursorOff();
fo.sendBaseline('ERR_ROUTE',false);
action = false;
} else {
next_page = {frame: parseInt(cmd.substr(2,cmd.length-1)),index: read};
fo.cursorOff();
action = ACTION_EDIT;
log(LOG_DEBUG,'- MODE_BL: EDIT ['+JSON.stringify(next_page)+']');
}
mode = false;
cmd = '';
break;
}
// Bookmark frame
if (cmd === '05') {
if (! user.number) {
fo.cursorOff();
fo.sendBaseline('ERR_ROUTE',false);
mode = action = false;
cmd = '';
} else {
// @todo
fo.cursorOff();
fo.sendBaseline('ERR_NOT_IMPLEMENTED',false);
mode = action = false;
cmd = '';
}
break;
}
// Report Problem
if (cmd === '08') {
if (! user.number) {
fo.cursorOff();
fo.sendBaseline('ERR_ROUTE',false);
mode = action = false;
cmd = '';
} else {
// @todo
fo.cursorOff();
fo.sendBaseline('ERR_NOT_IMPLEMENTED',false);
mode = action = false;
cmd = '';
}
break;
}
// Reload frame
if (cmd === '09') {
fo.cursorOff();
action = ACTION_GOTO;
cmd = '';
next_page = {frame: fo.frame,index: fo.index};
break;
}
// Another star aborts the command
if (read === '*') {
fo.clearBaseline(false);
fo.cursorOff();
mode = action = false;
cmd = '';
if (cf) {
// If there is a control for this field,
if (cc)
cc.prefield();
mode = MODE_FIELD;
fo.gotoxy(cf.c,cf.r);
fo.attr(cf.attribute);
console.write(cf.fchar.repeat(cf.fvalue.length));
fo.cursorOn(cf.c,cf.r);
cf.fvalue = '';
}
}
if ((viewdata && read === '_') || (! viewdata && read === '#') || read === "\r") {
// Nothing typed between * and #
// *# means go back
if (cmd === '') {
fo.clearBaseline(false);
action = ACTION_BACKUP;
} else if (cmd === '0') {
next_page = user.number ? HOME_FRAME : LOGIN_FRAME;
action = ACTION_GOTO;
// Edit frame
} else if (cmd === '04') {
// If we are not a user
if (! user.number) {
fo.sendBaseline('ERR_ROUTE',false);
action = false;
} else {
action = ACTION_EDIT;
}
} else {
next_page = {frame: parseInt(cmd),index: 'a'};
action = ACTION_GOTO;
}
// Clear the command we are finished processing...
fo.cursorOff();
cmd = '';
mode = false;
}
break;
// Key presses during field input.
case MODE_FIELD:
action = false;
switch (fo.type) {
// Login frame.
case FRAME_TYPE_LOGIN:
switch (read) {
case '_':
if (! viewdata) 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.ftype === 't' && cf.fvalue.toUpperCase() === 'NEW') {
action = ACTION_GOTO;
next_page = REGISTER_FRAME;
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 (! viewdata) break;
/* fallthrough */
case '#':
case "\r":
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: # ['+read+']');
// Next Field
fn++;
cf = fo.frame_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;
fo.gotoxy(cf.c,cf.r);
fo.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.fvalue.length : '{}')+' ct:'+cf.ftype);
if (cf.fvalue.length > 0) {
cf.fvalue = cf.fvalue.substring(0,cf.fvalue.length-1);
fo.fieldbs(cf.fchar);
}
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 = fo.frame_fields[fn];
log(LOG_DEBUG,'fn:'+fn+', cf'+JSON.stringify(cf));
if (! cf) {
fn = 0;
cf = fo.frame_fields[fn];
}
// If there is a control for this field,
if (cc)
cc.prefield();
mode = MODE_FIELD;
fo.gotoxy(cf.c+cf.fvalue.length,cf.r);
fo.attr(cf.attribute);
break;
case KEY_UP:
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: UP ['+read+']');
// Next Field
fn--;
if (fn < 0) {
fn = fo.frame_fields.length-1;
}
cf = fo.frame_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;
fo.gotoxy(cf.c+cf.fvalue.length,cf.r);
fo.attr(cf.attribute);
break;
// Record Data Entry
default:
log(LOG_DEBUG,'- MODE_FIELD:FRAME_TYPE_RESPONSE: ['+read+'] E:'+read.charCodeAt(0)+' cf:'+(cf ? cf.flength : '{}'));
if (read.charCodeAt(0) > 31 && cf.fvalue.length < cf.flength) {
cf.fvalue += read;
console.write((cf.ftype === 't') ? read : 'x');
}
}
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:
switch (read) {
case '1':
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']');
log(LOG_DEBUG,' - Frame fields: '+JSON.stringify(fo.frame_fields));
log(LOG_DEBUG,' - Key 1 is:'+JSON.stringify(fo.key[1]));
// If we are in a control method, complete it
if (control.count) {
log(LOG_DEBUG,'Last control method is:'+JSON.stringify(control[control.length-1]));
control[control.length-1].process();
} else if (fo.key[1] === '*' || fo.key[1].match(/[0-9]/)) {
fo.sendBaseline('NOACTION',false);
mode = MODE_RFSENT;
} else {
log(LOG_DEBUG,' - Key 1 is a METHOD check it exists: '+JSON.stringify(fo.key[1]));
switch(fo.key[1]) {
// User is logging in to system or CUG
case 'login':
log(LOG_DEBUG,' - User: '+fo.frame_fields[0].fvalue+'/'+fo.frame_fields[1].fvalue);
// If login is successful, we'll exit here
if (bbs.login(fo.frame_fields[0].fvalue,'',fo.frame_fields[1].fvalue)) {
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(fo.frame_fields[0].fvalue));
action = ACTION_GOTO;
next_page = LOGIN_FAILED_FRAME;
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(fo.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 {
fo.sendBaseline('ERR_METHOD_NOT_EXIST',false);
mode = MODE_RFSENT;
}
*/
}
break;
case '2':
log(LOG_DEBUG,'- MODE_SUBMITRF: Key ['+read+'] ['+pageStr(fo)+']');
// @todo Check if HASH is a valid next destination
if (fo.type === 'l') {
action = ACTION_RELOAD;
mode = false;
} else {
fo.sendBaseline('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:
fo.cursorOff();
switch (read) {
case '*':
action = ACTION_STAR;
break;
}
/*
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:
fo.cursorOff();
if ((viewdata && read === '_') || (! viewdata && read === '#')) {
/*
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;
// @todo MODE_CONTROL
default:
log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']');
action = ACTION_TERMINATE;
}
log(LOG_DEBUG,'MODE END: ['+read+']');
log(LOG_DEBUG,'ACTION START: ['+read+']');
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
fo.cursorOn(0,24);
fo.sendBaseline('BASESTAR',true);
action = false;
mode = MODE_BL;
bbs.replace_text(NodeActionRetrieving,'\1h%s \1n\1gJumping to page');
bbs.node_action=NODE_RFSD;
break;
// Submitting forms
case ACTION_SUBMITRF:
action = false;
fo.cursorOff();
log(LOG_DEBUG,'- ACTION_SUBMITRF: ['+fo.type+']');
fo.sendBaseline((fo.type === 'l' ? 'MSG_LOGON' : 'MSG_SENDORNOT'),true);
mode = MODE_SUBMITRF;
break;
// Edit a frame
case ACTION_EDIT:
log(LOG_DEBUG,'- ACTION_EDIT: ['+JSON.stringify(next_page)+']');
if ((fo.type === FRAME_TYPE_MESSAGE) || (! pageEditor(next_page ? next_page.frame : fo.frame))) {
fo.cursorOff();
fo.sendBaseline('ACCESS_DENIED',false);
action = false;
break;
}
require('ansitex/load/control-frameedit.js','CONTROL_FRAMEEDIT');
// If we are editing a specific frame, attempt to load it
if (next_page) {
var current = fo;
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
fo.load(pageStr(next_page));
// If the frame doesnt exist, check that the parent frame exists in case we are creating a new one
if (fo.page === 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') {
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
fo.load(pageStr({frame: next_page.frame, index: String.fromCharCode(next_page.index.charCodeAt(0)-1)}));
log(LOG_DEBUG,'- ACTION_EDIT: check index: '+JSON.stringify(fo)+' ('+String.fromCharCode(next_page.index.charCodeAt(0)-1)+')');
if (fo.page === null) {
fo = current;
// sendbaseline ERR_PAGE
fo.sendBaseline('ERR_NO_PARENT',false);
mode = action = false;
break;
}
}
// New frame
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
fo.frame = next_page.frame;
fo.index = next_page.index;
fo.cost = 0;
fo.owner = base64_decode(pageOwner(pageStr(next_page)).logo);
fo.content = base64_encode('Start your new page...');
}
}
control.push(new edit(fo));
mode = false;
action = false;
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 ['+fo.page+']');
// If there is no next page, we'll ignore the request.
if (! next_page || (pageStr(next_page) === fo.page)) {
action = false;
break;
}
// Goto specific page
case ACTION_GOTO:
// Clear any controls
control = [];
log(LOG_DEBUG,'- ACTION_GOTO: ['+(next_page ? pageStr(next_page) : '')+']');
var current = null;
// 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+']');
to = viewdata ? new FrameViewdata() : new FrameAnsi();
// @todo look for a template in the area or group first
to.load(MAIL_TEMPLATE_AREA_SUMMARY);
var ma = new MsgAreas();
var area = ma.getArea(next_page.frame);
// If the template page doesnt exist
if ((! to.content) || (! area)) {
fo.sendBaseline('ERR_ROUTE',false);
mode = action = false;
break;
}
current = fo;
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
fo.frame = next_page.frame;
fo.index = next_page.index;
fo.content = to.content;
fo.isPublic = true;
fo.isAccessible = to.isAccessible;
fo.owner = to.owner;
fo.type = to.type;
// Parent
fo.key[0] = (''+next_page.frame).substr(0,7);
// First Unread
fo.key[1] = atcode('msg_area_msgunread_page',null,null,area);
// First to me
fo.key[2] = atcode('msg_area_msgtome_page',null,null,area);
// Oldest
fo.key[3] = atcode('msg_area_msgoldest_page',null,null,area);
// Newest
fo.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(next_page.frame));
action = false;
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 = false;
fo.sendBaseline('ERR_ROUTE',false);
break;
}
// @todo - Show message stats
} else if (next_page.index === 'b') {
action = mode = false;
next_page = null;
fo.sendBaseline('ERR_NOT_IMPLEMENTED',false);
break;
} else {
mode = action = false;
next_page = null;
fo.sendBaseline('ERR_ROUTE',false);
break;
}
}
}
if (next_page !== null) {
current = fo;
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
fo.load(pageStr(next_page));
if (fo.page === null) {
fo = current;
// In case the frame doesnt exist
if (fo === null)
fo = viewdata ? new FrameViewdata() : new FrameAnsi();
// sendbaseline ERR_PAGE
fo.sendBaseline('ERR_ROUTE',false);
mode = action = false;
break;
}
next_page = null;
}
// If the user has access to the frame
if (fo.accessible) {
if (fo.isMember && fo.type === FRAME_TYPE_LOGIN) {
fo.sendBaseline('ALREADY_MEMBER',false);
mode = action = false;
break;
}
// Check if the frame exists, and the user is the Service Provider
} else {
fo.sendBaseline('ACCESS_DENIED',false);
mode = action = false;
// Reset the current frame to what it was.
fo = current;
break;
}
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]) !== fo.page)) && (fo.type !== FRAME_TYPE_LOGIN)) {
// Ignore the login frames
if (NO_HISTORY_FRAMES.indexOf(fo.page) === -1) {
history.push({frame:fo.frame,index:fo.index});
log(LOG_DEBUG,'- ACTION_GOTO: Added to history ['+fo.page+'] now ['+history.length+']');
}
}
// Load frame
case ACTION_RELOAD:
log(LOG_DEBUG,'- ACTION_RELOAD: ['+(next_page ? pageStr(next_page) : '')+']');
console.line_counter = 0; // @todo fix to suppress a pause that is occurring before clear()
fo.cursorOff();
bbs.replace_text(NodeActionMain,'\1h%s \1nViewing \1h*'+fo.frame+'#\1n ['+fo.index+']');
bbs.log_str(fo.page+'|');
bbs.node_action=NODE_MAIN;
switch(fo.type) {
// Terminate frame
case FRAME_TYPE_MAIL_TEMPLATE:
log(LOG_DEBUG,'- MAIL_TEMPLATE: ['+fo.frame+']');
fo.render(ma.getArea(fo.frame));
mode = false;
action = false;
break;
// Terminate frame
case FRAME_TYPE_TERMINATE:
fo.render();
mode = false;
action = ACTION_TERMINATE;
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(fo.content);
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));
fo.sendBaseline('ERR_ROUTE',false);
action = false;
break;
}
mode = false;
break;
case FRAME_TYPE_LOGIN:
action = false;
/* fallthrough */
case FRAME_TYPE_RESPONSE:
fn = 0;
cf = null;
fo.render();
if (fo.frame_fields.length) {
cf = fo.frame_fields[fn];
log(LOG_DEBUG,'cf'+JSON.stringify(cf));
if (cf) {
mode = MODE_FIELD;
fo.cursorOn(cf.c,cf.r);
fo.attr(cf.attribute);
// There were no editable fields.
} else {
mode = MODE_COMPLETE;
fo.cursorOff();
}
} else {
mode = false;
}
// If this is the register page
if (fo.page === pageStr(REGISTER_FRAME)) {
log(LOG_DEBUG,'Adding REGISTER to control stack');
require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_REGISTER');
control.push(eval('new '+fo.key[1]+'();'));
} else if (fo.page === pageStr(SQRL_FRAME)) {
log(LOG_DEBUG,'Adding SQRL to control stack');
require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_SQRL');
control.push(eval('new '+fo.key[1]+'();'));
} else if (fo.key[1] && (fo.type === FRAME_TYPE_RESPONSE) && (typeof(fo.key[1]) !== 'number')) {
log(LOG_DEBUG,'Adding METHOD to control stack: '+fo.key[1]);
require('ansitex/load/control-'+fo.key[1]+'.js','CONTROL_'+fo.key[1].toUpperCase());
control.push(eval('new '+fo.key[1]+'();'));
}
action = false;
break;
// Standard Frame
case FRAME_TYPE_INFO:
default:
fo.render();
mode = action = false;
break;
// Active frame
}
break;
}
log(LOG_DEBUG,'ACTION END: ['+read+']');
} catch (e) {
log(LOG_ERROR,JSON.stringify(e));
fo.sendBaseline('SYS_ERROR',false);
mode = action = false;
}
}
log(LOG_DEBUG,'- FINISHED');
if (action === ACTION_TERMINATE) {
log(LOG_DEBUG,'! Hangup');
fo.cursorOn();
bbs.hangup();
}
exit();
}