Source: connection/orion_objects.js

/** @namespace OrionObjects
    @desc      Collection of classes/methods for parsing, sanitizing, and normalizing data from various sources.
*/
var OrionObjects = OrionObjects ||
{
    /* properties */
    
    'my'            : {},
    'extensions'    : {},
    'users'         : {},
    'locations'     : {},
    'groups'        : {},
    'conferences'   : {},
    'holdCalls'     : {},
    'parkedCalls'   : {},
    'messagesFilter': {},
    'settings'      : {},
    'userGroups'    : {},
    
    /* classes */
    
    /** @class   OrionObjects.Activity
        @desc    Creates or returns a normalized activity record.
        
        @arg     {Object} type - Modality of the record.
        @arg     {Object} item - Data object.
        @arg     {Object} end  - Time the event concluded.
        @returns {Object} Normalized activity record.
    */
    'Activity': function (type, item, end)
    {
        $.extend(this,
        {
            'type'        : 'activityBean', // spoof api format
            'activityBean': item,
            'activityType': type,
            'endTime'     : end
        });
        
        return this;
    },
    
    /** @class   OrionObjects.ChatMessage
        @desc    Creates or returns a normalized chat message envelope from XMPP message element.
        
        @arg     {Object} message - XMPP message element.
        @returns {Object}
    */
    'ChatMessage': function (message)
    {
        console.log('chatmessage orion object: ', message);
        var $message  = $(message),
            from      = $message.attr('from'),
            info      = Strophe.getBareJidFromJid(from).match(/^(\d+)_(.+)@conference\.jabber\.orion\.com$/i),
            roomId    = info ? Number    (info[1]) : null,
            roomName  = info ? htmlDecode(info[2]) : null,
            info      = htmlDecode(Strophe.getResourceFromJid(from)).match(/^(\d+)_([a-z0-9]+)_(.+)$/i),
            userId    = info ? Number    (info[1]) : null,
            sessionId = info ?            info[2]  : null,
            nick      = info ? htmlDecode(info[3]) : null,
            body      = $message.children('body').text(),
            attach    = $message.children('data').text(),
            segment   = $message.children('info').children('count').text(),
            total     = $message.children('info').children('total').text(),
            uuid      = $message.children('info').children('uuid').text(),
            anonmsg   = body.indexOf('This room is not anonymous') > -1,
            deferred  = new $.Deferred();
            
        var iSeg = parseInt(segment), iTot = parseInt(total);
        
        
        $.extend(this,
        {
            'objType'  : 'chatMessage',
            'timestamp': $message.children('delay')
                             && $message.children('delay').attr('stamp')
                                 ? moment($message.children('delay').attr('stamp')).valueOf()
                                 : (new Date()).getTime(),
            'delayed'  : $message.children('delay').length                     > 0 ||
                         $message.children('x[xmlns="urn:xmpp:delay"]').length > 0,
            'notice'   : $message.attr('notice') === 'true' || anonmsg,
            'roomId'   : roomId,
            'roomName' : roomName,
            'userId'   : userId,
            'sessionId': sessionId,
            'nick'     : nick,
            'self'     : userId === Connection.user_id,
            'body'     : body,
            'attach'   : attach,
            'segment'  : isNaN(iSeg)? 1: iSeg,
            'total'    : isNaN(iTot)? 1: iTot,
            'uuid'     : uuid,
            'prerendered': (attach.length && userId === Connection.user_id) ? true : false,
            'promise'  : deferred.promise()
        });
        
        deferred[anonmsg ? 'reject' : 'resolve'](this);
        return true;
    },
    
    /** @class   OrionObjects.ChatPresence
        @desc    Creates or returns a normalized presence envelope from XMPP presence element.
        
        @arg     {Object} presence - XMPP presence element.
        @returns {Object}
    */
    'ChatPresence': function (presence)
    {
        // init
        
        var chatPresence = this,
            $presence    = $(presence),
            from         = $presence.attr('from'),
            info         = Strophe.getBareJidFromJid(from).match(/^(\d+)_(.+)@conference\.jabber\.orion\.com$/i),
            roomId       = info ? Number    (info[1]) : null,
            roomName     = info ? htmlDecode(info[2]) : null,
            info         = htmlDecode(Strophe.getResourceFromJid(from)).match(/^(\d+)_([\da-z]+)_(.+)$/i),
            userId       = info ? Number    (info[1]) : null,
            sessionId    = info ?            info[2]  : null,
            nick         = info ? htmlDecode(info[3]) : null,
            type         = $presence.attr('type'),
            available    = !type || (type && type.toLowerCase() !== 'unavailable'),
            deferred     = new $.Deferred();
        
        //console.log('ChatPresence', $presence);
        
        // build
        
        $.extend(this,
        {
            'objType'  : 'chatMessageSystem',
            //'timestamp': (new Date()).getTime(),
            'delayed'  : $presence.children('delay').length                     > 0 ||
                         $presence.children('x[xmlns="urn:xmpp:delay"]').length > 0,
            'notice'   : true,
            'roomId'   : roomId,
            'roomName' : roomName,
            'userId'   : userId,
            'sessionId': sessionId,
            'nick'     : nick,
            'self'     : $presence.find('status[code="110"]').length > 0,
            'body'     : null,
            'promise'  : deferred.promise()
        });
        
        // subrequest
        
        Bus.ask('chatsessions', {'action': 'get', 'id': roomId}).then(function (packet)
        {
            chatPresence.startDate = packet.data[0].startDate;
            
            //console.log('made it to the return of get:',  packet);
            $.each(packet.data[0].participants, function (index, participant)
            {
                //console.log('-----', index, participant);
                if (userId === participant.id)
                {
                    !available
                        ? (chatPresence.body = '{0} left'  .format(nick))
                        : (chatPresence.body = '{0} joined'.format(nick));
                    
                    deferred.resolve(chatPresence);
                    return false;
                }
            });
        });
        
        return true;
    },
    
    /** @class   OrionObjects.ChatParticipant
        @desc    Creates or returns a normalized participant object from attendee object.
        
        @arg     {Object} name - Attendee object.
        @returns {Object}
    */
    'ChatParticipant': function (attendee)
    {
        $.extend(this,
        {
            'objType'    : 'chatParticipant',
            'id'         : attendee.id,
            'displayName': attendee.displayname,
            'firstname'  : attendee.firstname,
            'lastname'   : attendee.lastname,
            'email'      : attendee.email,
            'organizer'  : attendee.organizer,
            'isOrganizer': attendee.organizer,
            'startDate'  : attendee.start_date,
            'endDate'    : attendee.end_date,
            'joined'     : attendee.start_date && !attendee.end_date ? true : false
        });
    },
    
    /** @class   OrionObjects.VideoToChatSession
        @desc    Creates or returns a normalized chat session object from video session object.
        
        @arg     {Object} session - Video session object.
        @returns {Object}
    */
    'VideoToChatSession': function (session)
    {
        if (session && session.attendees)
        {
            // convert attendees to participants
            
            var participants = session.attendees
                                   .map(function (attendee) { return new OrionObjects.ChatParticipant(attendee); });
            
            // reformat
            
            session =
            {
                'id'             : session.meetingId,
                'meetingId'      : session.meetingId,
                'displayName'    : session.displayName,
                'startDate'      : moment().valueOf(),
                'participants'   : participants
            };
        }
        
        // add organizer info to wrapper
        
        if (session && session.participants)
        {
            var organizer = session.participants.filter(function (participant) { return participant.isOrganizer; })[0];
            
            $.extend(session,
            {
                'organizer'    : organizer,
                'organizerIsMe': organizer.id === Connection.my_user_id
            });
        }
        
        session && (session.objType = 'chatSession');
        
        return session;
    },
    
    /** @class   OrionObjects.ChatSession
        @desc    Creates or returns a normalized chat session object.
        
        @arg     {Object} session - Chat session object.
        @returns {Object}
    */
    'ChatSession': function (session)
    {
        if (session && session.attendees)
        {
            // convert attendees to participants
            
            var participants = session.attendees
                                   .map(function (attendee) { return new OrionObjects.ChatParticipant(attendee); });
            
            // reformat
            
            session =
            {
                'id'             : session.id,
                'meetingId'      : session.id,
                'displayName'    : session.display_name,
                'customerId'     : session.customer_id,
                'startDate'      : session.start_date,
                'activitySession': session.session_name,
                'participants'   : participants
            };
        }
        
        // add organizer info to wrapper
        
        if (session && session.participants)
        {
            var organizer = session.participants.filter(function (participant) { return participant.isOrganizer; })[0];
            
            $.extend(session,
            {
                'organizer'    : organizer,
                'organizerIsMe': organizer.id === Connection.my_user_id
            });
        }
        
        session && (session.objType = 'chatSession');
        
        return session;
    },
    
    'CallDetails': function (status, channel, destination, source_id, source_name, call_direction, duration)
    {
        this.objType          = 'CallDetails';
        this.status           = status;
        this.channel          = channel;
        this.destination      = destination;
        this.source_id        = source_id;
        this.source_name      = source_name;
        this.destination_id   = '';
        this.destination_name = '';
        this.call_direction   = call_direction;
        this.duration         = duration;
        
        (status === '2') && (this.call_direction = 'outbound');
        (status === '3') && (this.call_direction = 'inbound');
    },
    
    //'CallPop': function (number, name, ringgroup, key, note, last_note, last_call, channel)
    
    /** @class   OrionObjects.CallPop
        @desc    Creates or returns a normalized call pop object from XMPP presence element.
        
        @arg     {Object} presence - XMPP presence element.
        @returns {Object}
    */
    'CallPop': function (presence)
    {
        var $presence  = $(presence);
        
        $.extend(this,
        {
            'objType'     : 'CallPop',
            'extension_id': $presence.find('id').text(),
            'number'      : decodeURIComponentSafe($presence.find('number').text()),
            'name'        : decodeURIComponentSafe($presence.find('name').text()),
            'ringgroup'   : $presence.find('ringgroup').text(),
            'key'         : $presence.find('key').text(),
            'note'        : $presence.find('note').text(),
            'last_note'   : $presence.find('last_note').text(),
            'last_call'   : $presence.find('last_call').text(),
            'channel'     : $presence.find('channel').text()
        });
        
        return this;
    },
    
    'CallRecording': function (filename, uniqueid, event_id)
    {
        this.objType   = 'CallRecording';
        this.filename  = filename;
        this.unique_id = uniqueid;
        this.event_id  = event_id;
        this.token = event_id;
        this.summary   = {};
    },
    
    'CallRecordingSummary': function (server_ip, recording_permission,location_id)
    {
        this.objType     = 'CallRecordingSummary';
        this.server      = (server_ip.indexOf('http:') < 0 ? 'http://'+server_ip : server_ip);
        this.location_id = location_id;
        this.permission  = recording_permission;
    },
    
    'Conference': function (id, name, extension, duration)
    {
        this.objType   = 'Conference';
        this.id        = id;
        this.name      = name;
        this.extension = extension;
        this.duration  = duration;
        this.display   = false;
        this.time      = new Date();
        this.time.setSeconds(this.time.getSeconds() - parseInt(duration));
        this.members   = {};
    },
    
    'ConferenceMember': function (extension, caller_id, caller_number, member_id, location_id, conference_id)
    {
        this.objType       = 'ConferenceMember';
        this.extension     = extension;
        this.caller_id     = caller_id;
        this.caller_number = caller_number;
        this.member_id     = member_id;
        this.location_id   = location_id;
        this.conference_id = conference_id;
    },
    
    'ContactInfo': function (voicemail, email, phone, fax_number)
    {
        this.objType    = 'ContactInfo';
        this.voicemail  = voicemail;
        this.email      = email;
        this.phone      = phone;
        this.fax_number = fax_number;
    },
    
    /** @class   OrionObjects.DataPacket
        @desc    Creates or returns a normalized data envelope suitable for inter-View messaging (JSON-safe).
        
        @arg     {String}       name       - Indicates type of data within.
        @arg     {String}       action     - REST-like intention for recipient interface.
        @arg     {Object|Array} data       - Single or multiple data rows for display or processing.
        @arg     {Object|Array} metas      - Single or multiple sets of recipient header information.
        @arg     {String}       source     - Indicates to recipient where data originated. (connection|cache|xmpp|api)
        @arg     {Object|Array} [messages] - Single or multiple informational objects to allow communication with recipient
                                             in non REST-like ways.
        @arg     {String}       [indexed]  - If supplied, data will be converted into a hashlike object with values of the
                                             specified property (e.g. id) used as keys. All supplied data objects must
                                             contain this property.
        @returns {Object}
    */
    'DataPacket': function (name, action, data, metas, source, messages, indexed)
    {
        // init
        
        var funcName = 'OrionObjects.DataPacket',
            obj      = {};
        
        obj.isPacket = false;
        
        source   = source   || '';
        messages = messages || [];
        
        // validate args
        
        if (!name || !action || !data || !metas)
        {
            log(L_ERROR, funcName, 'invalid arguments', arguments);
            return obj;
        }
        
        if (!~$.inArray(action, ['read', 'create', 'update', 'delete', 'info']))
        {
            log(L_ERROR, funcName, 'invalid action "{0}"'.format(action), arguments);
            return obj;
        }
        
        // validate metas
        
        var type = dltypeof(metas);
        
        switch (type)
        {
            case 'object'   :
            case 'jsobject' : metas = [metas]; break;
            case 'array'    :                  break;
            default         :
                
                log(L_ERROR, funcName, 'invalid meta(s) type: {0}'.format(type), metas);
                return obj;
        }
        
        // validate messages
        
        var type = dltypeof(messages);
        
        switch (type)
        {
            case 'undefined': messages = [];         break;
            case 'object'   :
            case 'jsobject' : messages = [messages]; break;
            case 'array'    :                        break;
            default         :
                
                log(L_ERROR, funcName, 'invalid message type: {0}'.format(type), messages);
                return obj;
        }
        
        // validate data
        
        var type = dltypeof(data);
        
        switch (type)
        {
            case 'array'   :                break;
            case 'object'  :
            case 'jsobject': data = [data]; break;
            default        :
                
                log(L_ERROR, funcName, 'invalid data type: {0}'.format(type), data);
                return obj;
        }
        
        // convert to indexed object?
        
        indexed = indexed || metas[0].indexed || false;
        
        if (indexed)
        {
            data = dataArrayToObject(data, indexed);
            
            if (!data)
            {
                log(L_ERROR, funcName, 'data could not be converted to indexed object', data);
                return obj;
            }
        }
        
        //messages = messages.concat(meta.messages);
        //meta.messages = messages;
        
        // assemble
        
        $.extend(obj,
        {
            'isPacket': true,
            'name'    : name,
            'action'  : action,
            'data'    : data,
            'metas'   : metas,
            'source'  : source,
            'messages': messages,
            'indexed' : indexed
        });
        
        return obj;
    },
    
    'Extension': function extension_object
    (
        id, location_id, extension_number, lines, callerid,
        availability_status, privacy, status, remote, user_id,
        deleted, fax_number, virtual, receptionist, operator
    ){
        this.objType             = 'Extension';
        this.id                  = id;
        this.user_id             = user_id;
        this.location_id         = location_id;
        this.lines               = lines;
        this.number              = extension_number;
        this.callerid            = callerid;
        this.availability_status = availability_status;
        this.privacy             = privacy;
        this.remote              = remote;
        this.status              = status;
        this.deleted             = deleted;
        this.fax_number          = fax_number;
        this.contact_info        = {};//new OrionObjects.ContactInfo('', '', '', '');//OrionObjects.setContactInfo;
        this.set_mask            = {};//OrionObjects.setMask;
        this.chat_status         = 'offline';
        this.virtual             = virtual;
        this.receptionist        = receptionist;
        this.operator            = operator;
        this.line_status         = {};
    },
    
    /** @class   OrionObjects.FaxFromXMPP
        @desc    Creates or returns a normalized fax data object from XMPP fax job.
        
        @arg     {Object} job - Fax job object.
        @returns {Object}
    */
    'FaxFromXMPP': function (job)
    {
        job = job || {};
        
        $.extend(this,
        {
            'ataJobId'              : job.ata_job_id,
            'callerId'              : job.caller_id,
            'creationTimestamp'     : moment(job.creation_timestamp + 'Z').valueOf(),
            'currentWorkflowStateId': Number(job.current_workflow_stateId),
            'faxSource'             : job.fax_source,
            'faxTimestamp'          : moment(job.fax_timestamp + 'Z').valueOf(),
            'fileServer'            : job.file_server,
            'folderId'              : job.folderId,
            'fromLocationId'        : Number(job.from_locationId),
            'fromUsersID'           : Number(job.from_usersID),
            'id'                    : Number(job.id),
            'orgDid'                : job.org_did,
            'orionPathBase'         : job.orion_path_base,
            'orionRenameFile'       : job.orion_rename_file,
            'pageCount'             : Number(job.page_count),
            'termCarrier'           : job.term_carrier,
            'termDid'               : job.term_did,
            'tiffPath'              : job.tiff_path,
            'tiffSize'              : Number(job.tiff_size),
            'toLocationId'          : Number(job.to_locationId),
            'toUsersID'             : Number(job.to_usersID),
            'workflowDescription'   : job.final_state,
            'workflowStateChanged'  : moment(job.workflow_state_changed + 'Z').valueOf(),
            'thumbUrl'              : 'http://{0}/{1}?file={2}.png&user={3}'.format
                                    (
                                        job.file_server,
                                        job.orion_path_base,
                                        job.orion_rename_file,
                                        Connection.my_user_id
                                    ),
            'direction'             : job.folderId == '-3' ? 'outbound' : 'inbound'
        });
    },
    
    /** @class   OrionObjects.FaxesFromXMPP
        @desc    Creates or returns an array of normalized fax data objects from XMPP presence element.
        
        @arg     {Object} presence - XMPP presence element.
        @returns {Object}
    */
    'FaxesFromXMPP' :function (presence)
    {
        var $presence = $(presence),
            response  = JSON.parse($presence.find('list').text()).response,
            deferred  = $.Deferred(),
            faxes     = [];
        
        faxes.promise = deferred.promise();
        
        if (response.status !== 'SUCCESS')
        {
            var description = 'xmpp presence status was "{0}"'.format(response.status),
                message     = {'event': 'newfax', 'description': description};
            
            log(L_ERROR, 'OrionObjects.FaxesFromXMPP', description);
            deferred.reject(message);
            return false;
        }
        else
        {
            Bus.ask('faxfolders', {'action': 'list'}).then(function (packet)
            {
                var folders = dataArrayToObject(packet.data, 'title');
                
                $.each(response.data.faxpersonal, function (index, bucket)
                {
                    var bucketLabel  = bucket.bucket_label,
                        bucketLabels = {'Inbox': -1, 'Outbox': -2, 'Sent': -3, 'Trash': -4},
                        folderId     = bucketLabels[bucketLabel]
                                         || (folders[bucketLabel] && folders[bucketLabel].id)
                                         || undefined;
                    
                    bucket.folderId = folderId;
                    
                    $.each(bucket.fax_jobs, function (index, job)
                    {
                        job.folderId = folderId;
                        faxes.push(new OrionObjects.FaxFromXMPP(job));
                    });
                });
                
                deferred.resolve(faxes);
            });
        }
        
        return faxes;
    },
    
    'Fax': function
    (
        id, from_usersID, to_usersID, org_did, term_did,
        org_firstname, org_lastname, term_firstname, term_lastname, fax_timestamp,
        page_count, file_server, orion_path_base, orion_rename_file, final_state
    ){
        this.objType           = 'Fax';
        this.id                = id;
        this.from_usersID      = from_usersID;
        this.to_usersID        = to_usersID;
        this.org_did           = org_did;
        this.term_did          = term_did;
        this.term_firstname    = term_firstname;
        this.term_lastname     = term_lastname;
        this.org_firstname     = org_firstname;
        this.org_lastname      = org_lastname;
        this.fax_timestamp     = fax_timestamp;
        this.page_count        = page_count;
        this.file_server       = file_server;
        this.orion_path_base   = orion_path_base;
        this.orion_rename_file = orion_rename_file;
        this.final_state       = final_state;      
    },
    
    'FaxBucket': function (bucket_id, bucket_label)
    {
        this.objType      = 'FaxBucket';
        this.bucket_id    = bucket_id;
        this.bucket_label = bucket_label;
        this.fax_objects  = {};
    },
    
    'FaxPacket': function (faxstorage_server, fax_server, org_did)
    {
        this.objType            = 'FaxPacket';
        this.faxstorage_server  = faxstorage_server;     
        this.fax_server         = fax_server;
        this.org_did            = org_did;
        this.recent_numbers     = {};
        this.fax_bucket_objects = {};
    },
    
    
    
    'FaxFolder': function ()
    {
        
    },
    
    /**@class    OrionObjects.FeaturesFromAPI
        @desc    Creates or returns an array of features filtered from API starlet data array.
        
        @arg     {Array} data - API starlet data.
        @returns {Array}
    */
    'FeaturesFromAPI': function (data)
    {
        return data.filter(function (item) { return item.feature; });
    },
    
    'Filter': function (inbound, outbound, missed, unanswered, notes, voicemail, count)
    {
        this.objType    = 'Filter';
        this.inbound    = inbound;
        this.outbound   = outbound;
        this.missed     = missed;
        this.unanswered = unanswered;
        this.voicemail  = voicemail;
        this.notes      = notes;
        this.count      = count;
    },
    
    'Group': function (id, name)
    {
        this.objType    = 'Group';
        this.id         = id;
        this.name       = name;
        this.extensions = {};
    },
    
    'HoldCalls': function
    (
        id, source_channel, channel, destination, source,
        source_name, destination_name, duration, privacy, operator
    ){
        this.objType             = 'HoldCalls';
        this.id                  = id;
        this.source_channel      = source_channel;
        this.destination_channel = channel;
        this.source              = source;
        this.source_name         = source_name;
        this.destination_name    = destination_name;
        this.destination         = destination;
        this.duration            = duration;
        this.privacy             = privacy;
        this.operator            = operator;
    },
    
    'LineStatus': function (line_id)
    {
        this.objType      = 'LineStatus';
        this.line_id      = line_id;
        this.call_details = {};
    },
    
    'Location': function (id, name, prefix, connected_status, deleted)
    {
        this.objType          = 'Location';
        this.id               = id;
        this.name             = name;
        this.prefix           = prefix;
        this.connected_status = connected_status;
        this.deleted          = deleted;
        this.extensions       = {};
    },
    
//     'Message': function (id, start, end, note, dial_time, location_id)
//     {
//         this.objType            = 'Message';
//         this.id                 = id;
//         this.start              = start;
//         this.end                = end;
//         this.note               = note;
//         this.dial_time          = dial_time;
//         this.source_object      = {};
//         this.destination_object = {};
//         this.location_id        = location_id;
//     },
    
    'Message': function (code, message)
    {
        this.code    = code;
        this.message = message;
    },
    
    'MessageCaller': function (caller, caller_number, caller_name, caller_uniqueid, note)
    {
        this.objType      = 'MessageCaller';
        this.id           = caller;
        this.number       = caller_number;
        this.name         = caller_name;
        this.uniqueid     = caller_uniqueid;
        this.chat_status  = 'offline';
        this.contact_info = new OrionObjects.ContactInfo('', '', '', '');
        this.note         = note;
    },
    
    /** @class   OrionObjects.UpdateNotification
        @desc    Creates or returns a normalized software update notification object.
        
        @arg     {Boolean}             beta          - Indicates whether only a limited subset of end users should receive this notification.
        @arg     {Boolean}             forced        - Indicates whether the update is to be forced via user interface.
        @arg     {String}              downloadpath  - Location of update package.
        @arg     {String|Object|Array} releasenotes  - Description of update or general information.
        @arg     {String|Number}       targetversion - Version of Framework the update is intended for.
        @arg     {Object}              object        - The update object.
        @arg     {Boolean}             sticky        - Whether to require manual dismissal of notification.
        @returns {Object}
    */
    'UpdateNotification': function (beta, forced, downloadpath, releasenotes, targetversion, object, sticky)
    {
        return (
        {
            'beta'         : beta   || false,
            'forced'       : forced || false,
            'orionDownload': downloadpath,
            'releaseNotes' : releasenotes,
            'targetVersion': targetversion,
            'object'       : object || {},
            'status'       : 'upgrade', 
            'sticky'       : !!sticky
        });
    },
    
    /** @class   OrionObjects.Notification
        @desc    Creates or returns a normalized notification object.
        
        @arg     {String}  type    - Indicates whether only a limited subset of end users should receive this notification.
        @arg     {String}  purpose - Indicates whether the update is to be forced via user interface.
        @arg     {Object}  object  - Location of update package.
        @arg     {Boolean} sticky  - Whether to require manual dismissal of notification.
        @returns {Object}
    */
    'Notification': function (type, purpose, object, sticky)
    {
        //console.log('--- Orion Object: Notificatoi0n: ', type, purpose, object, sticky);
        
        return (
        {
            'type'   : type    || '',
            'purpose': purpose || '',
            'object' : object  || {},
            'sticky' : !!sticky
        });
    },
    
    'ParkedCalls': function (id, extension, timeout, channel, source, source_name, destination_name, duration)
    {
        this.objType          = 'ParkedCalls';
        this.id               = id;
        this.extension        = extension;
        this.channel          = channel;
        this.source           = source;
        this.source_name      = source_name;
        this.destination_name = destination_name;
        this.duration         = duration;
        this.timeout          = timeout;
    },
    
    /** @class   OrionObjects.Starlet
        @desc    Creates or returns a normalized starlet object.
        
        @arg     {String} url          - Location of WebView.
        @arg     {String} name         - Framework's identifier of WebView, used for inter-View messaging.
        @arg     {String} identifier   - Folder containing starlet resources.
        @arg     {String} title        - Display name.
        @arg     {String} [type]       - Determines features/capabilities of the WebView
        @arg     {Number} [width=1280] - Width of WebView
        @arg     {Number} [height=800] - Height of WebView
        @returns {Object}
    */
    'Starlet': function (url, name, identifier, title, type, width, height)
    {
        type = type || 'trusted_starlet';
        
        var starlet     = {},
            urlParsed   = url.parseUrl(),
            connection  = type === 'connection',
            overlay     = !!~$.inArray(type, S_NAMES_OVERLAYS  ),
            lightbox    = !!~$.inArray(name, S_NAMES_LIGHTBOXES) || String(urlParsed.query.lightbox   ).toBoolean(),
            tearoff     =                                           String(urlParsed.query.tearoff    ).toBoolean(),
            minitearoff =                                           String(urlParsed.query.minitearoff).toBoolean(),
            main        = !!~$.inArray(type, S_TYPES_MAIN) && !tearoff  && !minitearoff,
            ordered     = main                             && !lightbox && !overlay,
            order       = ordered ? Connection.starlets.toArray().filter(function (item) { return item.ordered; }).length + 1 : -1,
            hidden      = type === 'notification';
        
        return $.extend(starlet,
        {
            // S_PROPERTIES
            
            'type'       : type,
            'url'        : url,
            'name'       : name,
            'identifier' : connection ? 'connection' : urlParsed.hash.slice(1),
            'title'      : title,
            'width'      : $.isNumeric(width ) ? Number(width ) : 1280,
            'height'     : $.isNumeric(height) ? Number(height) : 800,
            'order'      : order,
            'state'      : connection ? 3 : 0,
            'reloading'  : false,
            
            // S_MODES
            
            'main'       : main,
            'overlay'    : overlay,
            'lightbox'   : lightbox,
            'ordered'    : ordered,
            'tearoff'    : tearoff,
            'minitearoff': minitearoff,
            
            // S_STATES
            
            'visible'    : type !== 'notification' && !lightbox && !hidden,
            'active'     : false,
            'focused'    : false,
            'minimized'  : false,
            'hidden'     : hidden,
            'mouse'      : false
        });
    },
    
    /** @class   OrionObjects.StarletsFromAPI
        @desc    Creates or returns an array of normalized starlet objects from API data.
        
        @arg     {Array} data - API starlet data.
        @returns {Array}
    */    
    'StarletsFromAPI': function (data)
    {
        var starlets = [];
        
        $.each(data, function (index, item)
        {
            if (!item.feature)
            {
                var special    = !!~$.inArray(item.type, ['shoebox', 'navigation', 'notification']),
                    identifier = special ? item.type : item.appKey;
                
                starlets.push(new OrionObjects.Starlet
                (
                    item.source,
                    identifier,
                    identifier,
                    item.title,
                    item.type,
                    item.width,
                    item.height
                ));
            }
        });
        
        return starlets;
    },
    
    /** @class   OrionObjects.StarletsFromFramework
        @desc    Creates or returns an array of normalized starlet objects from Framework data.
        
        @arg     {Object} starlets - API starlet data.
        @returns {Object}
    */
    'StarletsFromFramework': function (starlets)
    {
        for (var name in starlets)
        {
            var starlet     = starlets[name],
                minitearoff = starlet.url.parseUrl().query.minitearoff;
            
            starlets[name] = new OrionObjects.Starlet
            (
                starlet.url,
                name,
                name,
                starlet.title,
                starlet.type,
                starlet.width,
                starlet.height
            );
        }
        
        return starlets;
    },
    
    'Voicemail': function (from_number, from_name, to_number, token, folder, dial_time, duration)
    {
        this.objType     = 'Voicemail';
        this.from_number = from_number;
        this.from_name   = from_name;
        this.to_number   = to_number;
        this.token       = token;
        this.old         = folder;
        this.dial_time   = dial_time;
        this.duration    = duration;
        this.summary     = {};
    },
    
    'VoicemailSummary': function (server_ip, unread_voicemail, location_id)
    {
        this.objType          = 'VoicemailSummary';
        this.server           = server_ip;
        this.unread_voicemail = unread_voicemail;
        this.location_id      = location_id;
    },
    
    /* methods (alphabetical) */
    
    'compareRoster': function (a, b)
    {
        return (
            a.user_id             !== b.user_id             ||
            a.location_id         !== b.location_id         ||
            a.lines               !== b.lines               ||
            a.number              !== b.number              ||
            a.callerid            !== b.callerid            ||
            a.availability_status !== b.availability_status ||
            a.privacy             !== b.privacy             ||
            a.remote              !== b.remote              ||
            a.voicemail           !== b.voicemail           ||
            a.email               !== b.email               ||
            a.phone               !== b.phone
        )
        ? 0 : 1; 
    },
    
    /** @method     OrionObjects.deleteStored
        @desc       Clears user-related caching objects.
        @deprecated since version 1.9
        @see        {@link Connection.cache}
        @see        {@link Connection.provData}
    */
    'deleteStored': function ()
    {
        this.my             = {};
        this.extensions     = {};
        this.users          = {};
        this.locations      = {};
        this.groups         = {};
        this.conferences    = {};
        this.holdCalls      = {};
        this.parkedCalls    = {};
        this.messagesFilter = {};
        this.settings       = {};
        this.userGroups     = {};
    },
    
    'setContactInfo': function (voicemail, email, phone, fax_number)
    {
        this.voicemail  = voicemail;
        this.email      = email;
        this.phone      = phone;
        this.fax_number = fax_number;
    },
    
    'setMask': function (mask)
    {
        this.mask = mask;
    }
};