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; log(LOG_DEBUG,'- Start ACTION is ['+action+']'); // 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,'================================================'); 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 = 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) ) { 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)+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'); } // 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)+')'); 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+')'); // If there are no more control items if (control.length == 0) inkey_timeout = INKEY_TIMEOUT; } log(LOG_DEBUG,'CONTROL END: ['+read+']'); } log(LOG_DEBUG,'MODE START: ['+read.charCodeAt(0)+'] ('+mode+')'); 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; action = ACTION_GOTO; log(LOG_DEBUG,'- false: Key ['+read+'] ['+pageStr(next_page)+']'); } else if (fo.key[read].toString().match(/^[0-9]+/)) { 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); } } 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); } 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 = ''; fo.cursorOff(); break; } // Special code to launch SQRL if (cmd === '01' && ! user.number) { action = ACTION_GOTO; next_page = SQRL_FRAME; cmd = ''; break; } // Invalid system pages. if (cmd.match(/^0[12367]/)) { log(LOG_DEBUG,'- MODE_BL: Invalid System Page ['+cmd+']'); fo.cursorOff(); fo.sendBaseline('ERR_ROUTE',false); mode = action = false; cmd = ''; } // 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) { 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') { log(LOG_DEBUG,'- MODE_BL: Bookmark ['+cmd+']'); 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') { log(LOG_DEBUG,'- MODE_BL: Report Problem ['+cmd+'] ('+fo.page+')'); 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') { log(LOG_DEBUG,'- MODE_BL: Reload frame ['+cmd+'] ('+fo.page+')'); fo.cursorOff(); action = ACTION_GOTO; cmd = ''; next_page = {frame: fo.frame,index: fo.index}; break; } // Another star aborts the command if (read === '*') { log(LOG_DEBUG,'- MODE_BL: Abort ['+cmd+'])'); 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") && (cmd.length > 0))) { log(LOG_DEBUG,'- MODE_BL: Return Received ['+cmd+'])'); // 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; } log(LOG_DEBUG,'- MODE_BL: END'); 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.length) { 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; default: log(LOG_DEBUG,'- SHOULDNT GET HERE: ['+read+']'); action = ACTION_TERMINATE; } log(LOG_DEBUG,'MODE END: ['+read+']'); 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 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 to me fo.key[1] = atcode('msg_area_msgtome_page',null,null,area); // First Unread fo.key[2] = atcode('msg_area_msgunread_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: // Clear our current field cf = null; 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]+'();')); inkey_timeout = 1000; } 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(); }