UNPKG

rets

Version:

Node.js client for RETS.

380 lines (308 loc) 10.4 kB
/** * RETS Client * =========== * * ### Event Types * - digest.error * - connection: General connection event - could mean success or error. * - connection.success: Successful connection only * - connection.error: General connection error. * - connection.parse.error: General connection parsing error. * - connection.closed: Digest authentication connection closed. * - request.error: General request error. * - request.parse.error: Request successful, but parsing failed. * - request.{TYPE}.complete: Request complete. * - get_meta.complete: Meta loaded * * ### Response Types * classes - * * @constructor * @module RETS * @param settings * @param cb * @returns {*} * @constructor */ function RETS( settings, cb ) { // Make sure context is correct otherwise we could screw up the global scope. if( !( this instanceof RETS ) ) { return new RETS( settings, cb ); } var Instance = this; var extend = require( 'extend' ); var digest = require( './digest' ); // Mixin Settings and EventEmitter require( 'object-settings' ).mixin( Instance ); require( 'object-emitter' ).mixin( Instance ); // Configure Instance. Instance.set({ settings: settings, digest: true, property: 'Property' }); var _connection_data = []; // Make Authentication Request. digest( settings.user, settings.pass ).request({ host: settings.host, path: settings.path, port: settings.port || 80, method: 'GET', headers: settings.headers || { "User-Agent": "NODE-RETS/1.0" } }, function authorization( res ) { // e.g. ECONNRESET this.on( 'error', function error( error ) { if( error.message === 'ECONNRESET' ) { Instance.emit( 'connection.closed', Instance ); } else { console.error( 'Uncaught RETS error:', error.message ); } }); // Connection complete. res.on( 'end', function digest_end() { // RETS.debug( 'end' ); // Save authentication response headers. Instance.set( 'headers', this.headers ) // Save RETS-server details. Instance.set( 'rets.version', this.headers[ 'rets-version' ] ) Instance.set( 'rets.server', this.headers[ 'server' ] ) Instance.set( 'rets.cookie', this.headers[ 'set-cookie' ] ) // Parse connection data. Instance._parse( _connection_data, function parsed( error, connection_data ) { // Parse Error of connection data. if( error ) { return Instance.emit( 'connection.parse.error', error, connection_data ); } // Connection response code must be 0. if( connection_data.code != 0 ) { return Instance.emit( 'connection.error', new Error( 'Connection response code not the expected 0.' ), connection_data ); } // Save general provider information. Instance.set( 'provider.name', connection_data.data.MemberName ); Instance.set( 'provider.user', connection_data.data.User ); Instance.set( 'provider.broker', connection_data.data.Broker ); // Save meta data. Instance.set( 'meta.version', connection_data.data.MetadataVersion ); Instance.set( 'meta.min_version', connection_data.data.MinMetadataVersion ); Instance.set( 'meta.timestamp', connection_data.data.MetadataTimestamp ); // Save connection URLs. Instance.set( 'url.get_meta', connection_data.data.GetMetadata ); Instance.set( 'url.get_object', connection_data.data.GetObject ); Instance.set( 'url.login', connection_data.data.Login ); Instance.set( 'url.logout', connection_data.data.Logout ); Instance.set( 'url.search', connection_data.data.Search ); // Emit connection success. Instance.emit( 'connection', null, Instance ); Instance.emit( 'connection.success', Instance ); }); }); // Connection failure. res.on( 'error', function digest_error( error ) { Instance.emit( 'connection', error, Instance ); Instance.emit( 'connection.error', error, Instance ); RETS.debug( error ); }); // Connection data. res.on( 'data', function digest_data( data ) { _connection_data.push( data.toString() ); RETS.debug( 'data', data.toString() ); }); }); // Return context. return this; } /** * RETS Instance Properties. * */ Object.defineProperties( RETS.prototype, { request: { /** * Abstract RETS Query wrapper. * * @async * @method request * @param type * @param query * @param cb * @returns {*} */ value: function request( query, cb ) { var Instance = this; var request = require( 'request' ); var parse = require( 'xml2js' ).parseString; // DEBUG var url = "http://www.parmls.com/SERetsPensacola/GetMetadata.aspx"; // Normalize parametrs. query = 'object' === typeof query ? { Type: 'METADATA-CLASS' } : query; cb = 'function' === typeof cb ? cb : function default_callback() {} request({ method: 'GET', url: url, qs: { Type: query.type || 'METADATA-CLASS', ID: query.id || 'Property', Format: 'STANDARD-XML' }, auth: { user: Instance.get( 'settings.user' ), pass: Instance.get( 'settings.pass' ), sendImmediately: false } }, function response( error, res, body ) { // Request error. if( error ) { return Instance.emit( 'request.error', error, res, cb( error, res ) ); } Instance._parse( body, function parsed( error, data ) { if( error ) { return Instance.emit( 'request.parse.error', error, res, cb( error, res ) ); } // Emit response and trigger callback. return Instance.emit( [ 'request', query.Type.toLowerCase(), 'complete' ].join( '.' ), null, data, cb( null, data ) ); }); }); return this; }, enumerable: true, configurable: true, writable: true }, get_classifications: { /** * Get Classification Meta Data * * @async * @chainable * @method get_classifications * @param cb {Function} Callback function. * @returns {Object} Context */ value: function get_classifications( cb ) { this.request({ type: 'METADATA-CLASS', id: 'Property' }, cb ); return this; }, enumerable: true, configurable: true, writable: true }, _parse: { /** * Parse RETS response and return an Object. * * @async * @chainable * @method _parse * @private * * @param data {String} * @returns {*} Instance. */ value: function _parse( data, cb ) { var Instance = this; var parse = require( 'xml2js' ).parseString; var _ = require( 'lodash' ); // Parse XML parse( data, function xml_parsed( error, data ) { try { if( error ) { throw new Error( 'Response parse error: ' + error.message ); } if( !data ) { throw new Error( 'No data to parse.' ); } if( 'object' !== typeof data.RETS ) { throw new Error( 'Response does not contain a proper RETS property. ' ); } // Standard response code and text, response type and empty data container. var parsed = { code: data.RETS[ '$' ].ReplyCode, text: data.RETS[ '$' ].ReplyText, type: undefined, resource: undefined, data: {} }; // Connection data. @wiki: https://github.com/UsabilityDynamics/node-rets/wiki/Connection-Response if( data.RETS[ 'RETS-RESPONSE' ] ) { parsed.type = 'connection'; // Iterate through each line and convert to key and value pair. data.RETS[ 'RETS-RESPONSE' ][0].split( "\r\n," ).forEach( function line_parser( line, index ) { // Ignore completely blank lines. if( !line ) { return; } var split = line.split( '=' ); var key = split[0].replace(/^\s+|\s+$/g, '' ); var value = split[1].replace(/^\s+|\s+$/g, '' ); parsed.data[ key ] = value; }); // Trigger callback with parsed data. return cb( null, parsed ); } // Some sort of meta. if( data.RETS[ 'METADATA' ] && _.first( data.RETS[ 'METADATA' ] ) ) { // Classification meta. if( _.first( data.RETS[ 'METADATA' ][0][ 'METADATA-CLASS' ] ) ) { parsed.type = 'classifications'; parsed.resource = data.RETS[ 'METADATA' ][0][ 'METADATA-CLASS' ][0]['$'].Resource; // Iterate through each class data type and create object data.RETS[ 'METADATA' ][0][ 'METADATA-CLASS' ][0].Class.forEach( function iterate( class_data ) { parsed.data[ class_data.ClassName ] = class_data; }); // Trigger callback with parsed data. return cb( null, parsed ); } throw new Error( 'Unknown RETS METADATA response sub-type.' ); } throw new Error( 'Unknown RETS response type, could not identify nor parse.' ); } catch( error ) { console.error( 'Paser Error:', error.message, error ); RETS.debug( error.message ); cb( error, parsed ) } }); // Chainable. return this; }, enumerable: false, configurable: true, writable: true } }); /** * RETS Constructor Properties. * */ Object.defineProperties( module.exports = RETS, { debug: { /** * RETS Debugger * * @esample * RETS.debug( 'Debug Mesage' ); * */ value: require( 'debug' )( 'RETS' ), enumerable: true, configurable: true, writable: true }, createConnection: { /** * Create new Connection * * @param settings * @param cb * @returns {RETS} */ value: function createConnection( settings, cb ) { return new RETS( settings, cb ) }, enumerable: true, configurable: true, writable: true } });