/*global BridgeServiceMixin, Draft, Drafts, MozMobileMessageClient */ 'use strict'; /** * Type description for the ConversationSummary object that is return from the * Conversation service. * @typedef {Object} ConversationSummary * @property {number} id Unique identifier of the the conversation. * @property {string} body Short content of the last message in the * conversation. * @property {Array.} participants List of the conversation * participants. * @property {Date} timestamp Conversation timestamp. * @property {{ hasUnread: boolean, hasNewError: boolean }} status Conversation * status that indicates whether conversation has unread messages or/and failed * messages that user hasn't seen yet. * @property {string} lastMessageType Type of the last message in the * conversation. Can be either 'sms' or 'mms'. * @property {Draft} draft If conversation is draft or has draft, then this * field contains appropriate draft information, otherwise it's null. */ [ ['BridgeServiceMixin', '/services/js/bridge_service_mixin.js'], [ 'MozMobileMessageClient', '/services/js/moz_mobile_message/moz_mobile_message_client.js' ], ['Drafts', '/services/js/drafts.js'] ].forEach(([dependencyName, dependencyPath]) => { if (!(dependencyName in self)) { importScripts(dependencyPath); } }); (function(exports) { const debug = 0 ? (arg1, ...args) => console.log(`[ConversationService] ${arg1}`, ...args): () => {}; // We use @-directive to override marker context that is scoped by // SharedWorker lifetime by default, but we need it to be aligned with the // main app context. const mark = 0 ? (...args) => exports.performance.mark( `[ConversationService] ${args}@sms.gaiamobile.org` ): () => {}; const SERVICE_CONTRACT = Object.freeze({ name: 'conversation-service', streams: Object.freeze( ['getAllConversations', 'getMessagesForConversation'] ), methods: Object.freeze([ 'deleteConversations', 'deleteMessages', 'markConversationsAs', 'getConversationSummary', 'getMessage', 'findConversationFromAddress' ]), events: Object.freeze(['message-change']) }); function draftToConversationSummary(draft) { var body = Array.isArray(draft.content) ? draft.content.find((content) => typeof content === 'string') : ''; return { id: +draft.id, participants: Array.isArray(draft.recipients) ? draft.recipients : [], body: body, timestamp: new Date(draft.timestamp), status: { hasUnread: false, hasNewError: false }, lastMessageType: draft.type || 'sms', draft: new Draft(draft) }; } function threadToConversationSummary(thread) { return { id: thread.id, participants: thread.participants, body: thread.body, timestamp: thread.timestamp, status: { hasUnread: thread.unreadCount > 0, hasNewError: false }, lastMessageType: thread.lastMessageType, draft: Drafts.byThreadId(thread.id) }; } var ConversationService = { /** * Initializes our service using the Service mixin's initService. */ init() { this.initService(); }, /** * Stream that returns everything needed to display conversations in the * inbox view. * @param {ServiceStream.} serviceStream The stream to * use in the implementation: it will be passed automatically by the Bridge * library. * @param {appInstanceId} appInstanceId Unique id of the app instance * requesting service. */ getAllConversations(serviceStream, appInstanceId) { mark('start retrieving conversations'); var getThreadsStream = MozMobileMessageClient.forApp( appInstanceId ).getThreads(); // If conversation stream is cancelled we should cancel internal threads // stream as well. serviceStream.cancel = (reason) => { debug('getAllConversations stream is cancelled: %s', reason); return getThreadsStream.cancel(reason); }; var draftsPromise = Drafts.request().then(() => { // Return draft list that is sorted by timestamp in inverse order. return Drafts.getAllThreadless().sort( (draftA, draftB) => draftB.timestamp - draftA.timestamp ); }); // Assume that we get the data in an inverse sort order. var index = 0; getThreadsStream.listen((thread) => { mark(++index, ' conversation retrieved'); debug('Retrieved conversation with id %s', thread.id); draftsPromise.then((drafts) => { // Flush drafts that are created earlier than current thread. while(drafts.length && drafts[0].timestamp >= thread.timestamp) { serviceStream.write(draftToConversationSummary(drafts.shift())); } serviceStream.write(threadToConversationSummary(thread)); }); }); return getThreadsStream.closed.then(() => draftsPromise).then( (drafts) => { for(var draft of drafts) { serviceStream.write(draftToConversationSummary(draft)); } mark('All conversations retrieved'); serviceStream.close(); }).catch((e) => { // Pass error object once the following issue is fixed: // https://github.com/gaia-components/threads/issues/74 serviceStream.abort(`[${e.name}] ${e.message || ''}`); }); }, /** * Stream that returns messages in a conversation, with all suitable * information to display in the conversation view. * @param {ServiceStream.} The stream to use in the * implementation: it will be passed automatically by the Bridge library. * @param {Number} conversationId Conversation to retrieve. */ getMessagesForConversation(stream, conversationId) {}, /** * Returns all information related to a single conversation, to display in * the conversation view. * @param {Number} conversationId Conversation to retrieve. * @returns {ConversationSummary} */ getConversationSummary(conversationId) {}, /** * Deletes one or several conversations, including their messages. * @param {Array.} conversationIds ID of the conversations to be * deleted. */ deleteConversations(conversationIds) {}, /** * Deletes one or several messages. * @param {Array.} messageIds ID of the messages to be deleted. */ deleteMessages(messageIds) {}, /** * Marks a conversation as read or unread. * @param {Object} param * @param {Array.} param.conversations Conversation IDs to be * marked. * @param {Boolean} param.asRead Specifies whether we mark as read of * unread. */ markConversationsAs({conversations, asRead}) {}, /** * Fetches a message information. * @param {Number} messageId The message ID to retrieve. * @returns {Message} All message information, including contact, delivery, * read reports, size, timestamps, etc. */ getMessage(messageId) {}, /** * Finds a conversation from an address (that is: a phone number or an * email). * @param {String} address This is either a phone number or an email, to * match an existing conversation from. * @returns {Number?} The id of the conversation, or null if not found. */ findConversationFromAddress(address) {} }; exports.ConversationService = Object.seal( BridgeServiceMixin.mixin( ConversationService, SERVICE_CONTRACT.name, SERVICE_CONTRACT ) ); // Automatically init service if it's run inside worker. if (!self.document) { ConversationService.init(); } })(self);