// Our frame object function PageFrame() { 'use strict'; /* Frame type settings */ this.settings = {}; /* Frame Version */ this.version = 1; /* Frame Number [0-9] */ this.frame = null; /* Frame Index [a-z] */ this.index = null; /* The Service Provider owning the frame. */ this.owner = 0; /* The cost to view the frame @todo to implement */ this.cost = 0; /* The frame content, base64 encoded */ this.content = ''; // Frame's owned by the system where: // isPublic is FALSE - the user must be logged in to view it // isPublic is TRUE - can be viewed by non-logged in users // Frame's owned by Service Providers where: // isPublic is FALSE - can only be viewed if a user is // a member of the Service Providers CUG // isPublic is TRUE - can be viewed by users (if logged in) this.isPublic = false; // Is this frame accessible to non CUG users // If FALSE user must be a member of the CUG to view the frame // All users, including unauthenticated, are members of 'system' (owner = 0) this.isAccessible = false; // Is this frame available to be accessed // If FALSE, only the SP can view/edit the frame this.type = FRAME_TYPE_INFO; // The frame type - see FRAME_TYPES above this.key = [null,null,null,null,null,null,null,null,null,null]; // Key actions [0-9] /** * Determine if this frame is accessible to the current user */ Object.defineProperty(this,'accessible',{ get: function() { log(LOG_DEBUG,'- Checking if user can access frame: '+this.page); log(LOG_DEBUG,' - User: '+JSON.stringify(user.number)); log(LOG_DEBUG,' - Frame Owner: '+JSON.stringify(this.owner)+', System Frame: '+(this.pageowner === SYSTEM_OWNER)); // user.number 0 is unidentified user. if (user.number) { return ( (this.isAccessible && this.pageowner === SYSTEM_OWNER && ! this.isPublic) || (this.isAccessible && this.isPublic) || (this.isAccessible && ! this.isPublic && this.isMember) || (pageEditor(this.frame)) ); } else { return (this.isAccessible && this.pageowner === SYSTEM_OWNER && this.isPublic); } } }); /** * Return the frames fields */ Object.defineProperty(this,'fields', { get: function() { return this.frame_fields; } }); /** * Check if the user is already a member of the CUG */ Object.defineProperty(this,'isMember',{ get: function() { log(LOG_DEBUG,'- Checking if user is a member of frame: '+this.page); if (user.number) { return ( (this.pageowner === SYSTEM_OWNER) ); } else { return false; } } }) /** * Return the page number */ Object.defineProperty(this,'page', { get: function() { if (this.frame === null || this.index === null) return null; return this.frame.toString()+this.index; } }); Object.defineProperty(this,'pageowner', { get: function() { return pageOwner(this.frame).prefix; } }) Object.defineProperty(this,'pageownerlogo', { get: function() { return base64_decode(pageOwner(this.frame).logoans); } }) } /** * Enable pulling out submitted value by its name * * @param key * @returns {null|string|*} */ PageFrame.prototype.fieldValue = function(key) { for each (var k in this.frame_fields) { log(LOG_DEBUG,' - k:'+JSON.stringify(k)); if (k.fname === key) { return k.fvalue; } } return null; } /** * Return the message for a index * * @param index * @returns {string|*} */ PageFrame.prototype.getMessage = function(index) { eval('var msg = this.settings.'+index); return msg; } /** * Load a frame * * @param filename * @returns {null} */ PageFrame.prototype.load = function(filename) { log(LOG_DEBUG,'Loading FRAME from: '+filename+'.'+this.settings.ext); f = new File(system.mods_dir+'ansitex/text/'+filename+'.'+this.settings.ext); if (! f.exists || ! f.open('r')) { return null; } try { var load = JSON.parse(f.read()); for (property in SAVED_FRAME_ATTRS) { this[SAVED_FRAME_ATTRS[property]] = load[SAVED_FRAME_ATTRS[property]]; } } catch (error) { log(LOG_ERROR,'Frame error: '+error); // Load our system error frame. this.load(pageStr(FRAME_SYSTEM_ERROR)); return null; } log(LOG_DEBUG,'Loaded frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); }; /** * Load a message frame * * @param page */ PageFrame.prototype.loadMessage = function(page) { this.frame = ''+page; this.index = 'a'; this.owner = 1; this.isPublic = true; this.isAccessible = true; // @todo Keys should map to next/previous/send, etc as indicated in the template frame. this.key = [this.frame.substr(0,7)+'1',null,null,null,null,null,null,null,null,null]; this.type = FRAME_TYPE_MESSAGE; // Load our message var ma = new MsgAreas() var area = ma.getArea(this.frame); var msg = ma.getMessage(this.frame); var msg_header; if (! msg) return undefined; // @todo Search 1zzzzEE..., 1zzzz... var to = viewdata ? new FrameViewdata() : new FrameAnsi(); to.load(MAIL_TEMPLATE_FRAME); // @todo Check that this is a frame of type "m" and report error if not // @todo Take the cost from the template this.cost = 5; if (! to) { log(LOG_ERROR,'Echomail template missing :['+MAIL_TEMPLATE_FRAME+'] ?'); msg_header = 'TO: '+msg.to.substr(0,72)+"\n\r"; msg_header += 'FROM: '+msg.from.substr(0,72)+"\n\r"; msg_header += 'DATE: '+msg.date.substr(0,72)+"\n\r"; msg_header += 'SUBJECT: '+msg.subject.substr(0,72)+"\n\r"; } else { // @todo change this to use atcode() msg_header = base64_decode(to.content).replace(/@(.*)@/g, function (str, code, offset, s) { var length = code.split(':')[1]; switch(code.split(':')[0]) { case 'DATE': return msg.date.substr(0,length); case 'TO': return msg.to.substr(0,length); case 'FROM': return msg.from.substr(0,length); case 'SUBJECT': return msg.subject.substr(0,length); } } ); } //log(LOG_DEBUG,'Loaded message: '+msg_header+msg.content); this.content = base64_encode(msg_header+msg.content); // Update the user's pointers var stats = ma.getUserStats(this.frame); // if this message is to the user, and the msg number > scan_ptr and it is the next message on the user's new mail list var newmsgs = area.newMsgsToMe(); log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read to ME'); if (newmsgs.length) { var next = newmsgs[0]; //log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); if (next.tags === msg.tags) { log(LOG_DEBUG,'- Updating scan_ptr to: '+next.number); stats.scan_ptr = next.number; } } // if this message is the next message, update last_read var newmsgs = area.newMsgs(); log(LOG_DEBUG,'User has: '+newmsgs.length+' msgs to read'); if (newmsgs.length) { var next = newmsgs[0]; //log(LOG_DEBUG,'- NEXT is: '+next.tags+', this is: '+msg.tags); if (next.tags === msg.tags) { log(LOG_DEBUG,'- Updating last_read to: '+next.number); stats.last_read = next.number; } } log(LOG_DEBUG,'Built frame: ['+this.frame+']['+this.index+'] ('+this.page+')'); } /** * Parse the page text, and return the frame as 2 arrays: * + First array is all the characters and the position on the frame * + Second array is the array of the control codes that changes the color of the character * * The purpose of this function is to convert any special char sequences there are interpreted directly by Ansitex * Currently they are: * + ESC _ [;value] ESC \ * * Additionally, for response frames, if the cursor is moved to a field, its to determine what attributes (eg: color) * should apply for that field. * * @param text * @param context */ PageFrame.prototype.parse = function(text,context) { log(LOG_DEBUG,'Parsing Frame ...'); if (this.type === FRAME_TYPE_MESSAGE) { log(LOG_DEBUG,'- Not parsing message frame'); return text; } /** Column * @type {number} */ var c = 1; /** Row - row 1 is the header * @type {number} */ var r = 2; /** * The output to send to the screen * @type {string} */ var output = ''; /** * Fields within the frame * @type {{ * ftype: fieldtype, * flength: fieldlen, * fchar: fieldchar, * fname: field, * r: r, * c: c, * attribute: {i:i,f:f,b:b}, * fvalue: '', * }} */ this.frame_fields = []; // If there is no text return if (! text) return output; // Default Attributes // @note - this has no affect for viewdata frames /* Foreground */ var f = 39; /* Background */ var b = 49; /* Intensity */ var i = 0; for(var p=0;p= 0 && csi[num] <= 8) { i = csi[num]; f = 39; b = 49; // Forground Color } else if (csi[num] >= 30 && csi[num] <= 39) { f = csi[num]; // Background Color } else if (csi[num] >= 40 && csi[num] <= 49) { b = num; } } break; // Advance characters case 'C': //log(LOG_DEBUG,'CSI C ['+r+'x'+c+'] CHARS: '+matches[1]); c += parseInt(matches[1]); // Advance our position break; default: log(LOG_DEBUG,'? CSI: '+matches[2]); } break; case ' ': log(LOG_DEBUG,'LOOSE ESC? ['+r+'x'+c+'] '+advance); break; // X - dynamic fields, terminated with ESC // Dynamic fields are defined with ESC X FIELD;LENGTH;PAD ESC \ // @note FIELD & LENGTH are required // @note Dynamic field is can only be DYNAMIC_FIELD_SIZE_MAX chars in size case 'X': //log(LOG_DEBUG,'DF ['+r+'x'+c+'] '+advance); advance++; // Find our end ST param in the next DYNAMIC_FIELD_SIZE_MAX chars matches = text.substr(p+advance,DYNAMIC_FIELD_SIZE_MAX).match(/(([a-zA-Z._^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/); //log(LOG_DEBUG,'- DF ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+JSON.stringify(matches)+', LENGTH: '+(matches ? matches[0].length : 0)+', STRING: '+text.substr(p+advance,50)); if (! matches) { chars += nextbyte; break; } advance += matches[0].length-1; var df = matches[0].substr(0,matches[0].length-2).split(';'); //log(LOG_DEBUG,'- DF ['+r+'x'+c+'] ADVANCE: '+advance+', DF: '+df); log(LOG_DEBUG,'- DF found at ['+r+'x'+(c-1)+'], Field: '+df[0]+', Length: '+df[1]+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); chars = atcode(df[0],df[1],df[2],context); byte = ''; break; // _ - has our fields that take input case '_': //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] '+advance); advance++; // Find our end ST param in the next 50 chars matches = text.substr(p+advance,50).match(/(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/); //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', MATCHES: '+matches+', LENGTH: '+matches[0].length+', STRING: '+text.substr(p+advance,50)); if (! matches) { chars += nextbyte; break; } advance += matches[0].length-1; // The last 2 chars of matches[0] are the ESC \ var sos = matches[0].substr(0,matches[0].length-2).split(';'); //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] ADVANCE: '+advance+', SOS: '+sos); var num = null; var field = null; var fieldlen = null; for (num in sos) { //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM: '+num+', SOS: '+sos[num]); switch (num) { // First value is the field name case '0': field = sos[num]; break; // Second value is the length/type of the field case '1': x = sos[num].match(/([0-9]+)([a-z])/); if (! x) { log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]); break; } //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]); fieldlen = x[1]; fieldtype = x[2]; break; // Third field is the char to to use case '2': fieldchar = sos[num]; break; default: log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); } } if (fieldlen) { chars = fieldchar.repeat(fieldlen); } byte = ''; this.frame_fields.push({ ftype: fieldtype, flength: fieldlen, fchar: fieldchar, fname: field, r: r, c: c, attribute: {i:i,f:f,b:b}, fvalue: '', }); log(LOG_DEBUG,'SOS Field found at ['+r+'x'+(c-1)+'], Type: '+fieldtype+', Length: '+fieldlen+', Attrs: '+JSON.stringify({i:i,f:f,b:b})); break; default: log(LOG_DEBUG,'DEFAULT ['+r+'x'+c+'] '+advance); } break; default: c++; } output += byte; if (advance) { //log(LOG_DEBUG,'ADVANCE P ['+r+'x'+c+'] '+advance+', NEXT CHAR: '+text.charAt(p+advance)+' ('+text.charCodeAt(p+advance)+')'); output += chars; p += advance; } if (c>this.settings.FRAME_WIDTH) { c = 1; r++; } /* // @todo - If we are longer than FRAME_LENGTH, move the output into the next frame. if (r>this.settings.FRAME_LENGTH) { break; } */ } return output; };