/** * 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: require(ANSITEX_HOME+'/load/session/viewdata.js','SESSION_VIEWDATA'); break; // Assume ANSItex default: 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+'])'); 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(); }