MOBILE-2632 message: Send message online and offline

main
Dani Palou 2018-11-20 11:31:52 +01:00
parent 4710610909
commit 06f7a427c6
6 changed files with 464 additions and 110 deletions

View File

@ -40,6 +40,7 @@
"type_strangers": "Others",
"unblockuser": "Unblock user",
"unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
"warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
"warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
"you": "You:"
}

View File

@ -18,6 +18,7 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreSitesProvider } from '@providers/sites';
import { AddonMessagesProvider } from '../../providers/messages';
import { AddonMessagesOfflineProvider } from '../../providers/messages-offline';
import { AddonMessagesSyncProvider } from '../../providers/sync';
import { CoreUserProvider } from '@core/user/providers/user';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
@ -79,7 +80,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
private userProvider: CoreUserProvider, private navCtrl: NavController, private messagesSync: AddonMessagesSyncProvider,
private domUtils: CoreDomUtilsProvider, private messagesProvider: AddonMessagesProvider, logger: CoreLoggerProvider,
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private translate: TranslateService,
@Optional() private svComponent: CoreSplitViewComponent) {
@Optional() private svComponent: CoreSplitViewComponent, private messagesOffline: AddonMessagesOfflineProvider) {
this.siteId = sitesProvider.getCurrentSiteId();
this.currentUserId = sitesProvider.getCurrentSiteUserId();
this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
@ -166,7 +167,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
}
// Synchronize messages if needed.
this.messagesSync.syncDiscussion(this.userId).catch(() => {
this.messagesSync.syncDiscussion(this.conversationId, this.userId).catch(() => {
// Ignore errors.
}).then((warnings) => {
if (warnings && warnings[0]) {
@ -253,7 +254,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
this.fetching = true;
// Wait for synchronization process to finish.
return this.messagesSync.waitForSync(this.userId).then(() => {
return this.messagesSync.waitForSyncConversation(this.conversationId, this.userId).then(() => {
// Fetch messages. Invalidate the cache before fetching.
if (this.groupMessagingEnabled) {
return this.messagesProvider.invalidateConversationMessages(this.conversationId).catch(() => {
@ -269,49 +270,57 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
});
}
}).then((messages) => {
if (this.viewDestroyed) {
return Promise.resolve();
}
// Check if we are at the bottom to scroll it after render.
this.scrollBottom = this.domUtils.getScrollHeight(this.content) - this.domUtils.getScrollTop(this.content) ===
this.domUtils.getContentHeight(this.content);
if (this.messagesBeingSent > 0) {
// Ignore polling due to a race condition.
return Promise.reject(null);
}
// Add new messages to the list and mark the messages that should still be displayed.
messages.forEach((message) => {
this.addMessage(message);
});
// Remove messages that shouldn't be in the list anymore.
for (const hash in this.keepMessageMap) {
this.removeMessage(hash);
}
// Sort the messages.
this.messagesProvider.sortMessages(this.messages);
// Calculate which messages need to display the date or user data.
this.messages.forEach((message, index): any => {
message.showDate = this.showDate(message, this.messages[index - 1]);
message.showUserData = this.showUserData(message, this.messages[index - 1]);
});
// Notify that there can be a new message.
this.notifyNewMessage();
// Mark retrieved messages as read if they are not.
this.markMessagesAsRead();
this.loadMessages(messages);
}).finally(() => {
this.fetching = false;
});
}
/**
* Format and load a list of messages into the view.
*
* @param {any[]} messages Messages to load.
*/
protected loadMessages(messages: any[]): void {
if (this.viewDestroyed) {
return;
}
// Check if we are at the bottom to scroll it after render.
this.scrollBottom = this.domUtils.getScrollHeight(this.content) - this.domUtils.getScrollTop(this.content) ===
this.domUtils.getContentHeight(this.content);
if (this.messagesBeingSent > 0) {
// Ignore polling due to a race condition.
return;
}
// Add new messages to the list and mark the messages that should still be displayed.
messages.forEach((message) => {
this.addMessage(message);
});
// Remove messages that shouldn't be in the list anymore.
for (const hash in this.keepMessageMap) {
this.removeMessage(hash);
}
// Sort the messages.
this.messagesProvider.sortMessages(this.messages);
// Calculate which messages need to display the date or user data.
this.messages.forEach((message, index): any => {
message.showDate = this.showDate(message, this.messages[index - 1]);
message.showUserData = this.showUserData(message, this.messages[index - 1]);
});
// Notify that there can be a new message.
this.notifyNewMessage();
// Mark retrieved messages as read if they are not.
this.markMessagesAsRead();
}
/**
* Get the conversation.
*
@ -328,18 +337,29 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
} else {
// We don't have the conversation ID, check if it exists.
promise = this.messagesProvider.getConversationBetweenUsers(this.userId).catch((error) => {
if (error.errorcode == 'conversationdoesntexist') {
// Conversation does not exist, return undefined.
return;
}
return Promise.reject(error);
// Probably conversation does not exist or user is offline. Try to load offline messages.
return this.messagesOffline.getMessages(this.userId).then((messages) => {
if (messages && messages.length) {
// We have offline messages, this probably means that the conversation didn't exist. Don't display error.
messages.forEach((message) => {
message.pending = true;
message.text = message.smallmessage;
});
this.loadMessages(messages);
} else if (error.errorcode != 'conversationdoesntexist') {
// Display the error.
return Promise.reject(error);
}
});
});
}
return promise.then((conversation) => {
this.conversation = conversation;
if (conversation) {
this.conversationId = conversation.id;
this.title = conversation.name;
this.conversationImage = conversation.imageurl;
this.isGroup = conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP;
@ -677,7 +697,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
* Function to delete a message.
*
* @param {any} message Message object to delete.
* @param {number} index Index where the mesasge is to delete it from the view.
* @param {number} index Index where the message is to delete it from the view.
*/
deleteMessage(message: any, index: number): void {
const langKey = message.pending ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation';
@ -779,6 +799,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
text: text,
timecreated: new Date().getTime()
};
message.showDate = this.showDate(message, this.messages[this.messages.length - 1]);
this.addMessage(message, false);
this.messagesBeingSent++;
@ -786,7 +807,15 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
// If there is an ongoing fetch, wait for it to finish.
// Otherwise, if a message is sent while fetching it could disappear until the next fetch.
this.waitForFetch().finally(() => {
this.messagesProvider.sendMessage(this.userId, text).then((data) => {
let promise;
if (this.conversationId) {
promise = this.messagesProvider.sendMessageToConversation(this.conversationId, text);
} else {
promise = this.messagesProvider.sendMessage(this.userId, text);
}
promise.then((data) => {
let promise;
this.messagesBeingSent--;
@ -836,9 +865,6 @@ export class AddonMessagesDiscussionPage implements OnDestroy {
if (!prevMessage) {
// First message, show it.
return true;
} else if (message.pending) {
// If pending, it has no date, not show.
return false;
}
// Check if day has changed.

View File

@ -26,7 +26,8 @@ export class AddonMessagesOfflineProvider {
protected logger;
// Variables for database.
static MESSAGES_TABLE = 'addon_messages_offline_messages';
static MESSAGES_TABLE = 'addon_messages_offline_messages'; // When group messaging isn't available or a new conversation starts.
static CONVERSATION_MESSAGES_TABLE = 'addon_messages_offline_conversation_messages'; // Conversation messages.
protected tablesSchema = [
{
name: AddonMessagesOfflineProvider.MESSAGES_TABLE,
@ -53,6 +54,28 @@ export class AddonMessagesOfflineProvider {
}
],
primaryKeys: ['touserid', 'smallmessage', 'timecreated']
},
{
name: AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE,
columns: [
{
name: 'conversationid',
type: 'INTEGER'
},
{
name: 'text',
type: 'TEXT'
},
{
name: 'timecreated',
type: 'INTEGER'
},
{
name: 'deviceoffline', // If message was stored because device was offline.
type: 'INTEGER'
}
],
primaryKeys: ['conversationid', 'text', 'timecreated']
}
];
@ -61,6 +84,25 @@ export class AddonMessagesOfflineProvider {
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
}
/**
* Delete a message.
*
* @param {number} conversationId Conversation ID.
* @param {string} message The message.
* @param {number} timeCreated The time the message was created.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
deleteConversationMessage(conversationId: number, message: string, timeCreated: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().deleteRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {
conversationid: conversationId,
text: message,
timecreated: timeCreated
});
});
}
/**
* Delete a message.
*
@ -84,24 +126,18 @@ export class AddonMessagesOfflineProvider {
* Get all messages where deviceoffline is set to 1.
*
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with messages.
* @return {Promise<any[]>} Promise resolved with messages.
*/
getAllDeviceOfflineMessages(siteId?: string): Promise<any> {
getAllDeviceOfflineMessages(siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {deviceoffline: 1});
});
}
const promises = [];
/**
* Get offline messages to send to a certain user.
*
* @param {number} toUserId User ID to get messages to.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with messages.
*/
getMessages(toUserId: number, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {touserid: toUserId});
promises.push(site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {deviceoffline: 1}));
promises.push(site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, {deviceoffline: 1}));
return Promise.all(promises).then((results) => {
return results[0].concat(results[1]);
});
});
}
@ -113,7 +149,54 @@ export class AddonMessagesOfflineProvider {
*/
getAllMessages(siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getAllRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE);
const promises = [];
promises.push(site.getDb().getAllRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE));
promises.push(site.getDb().getAllRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE));
return Promise.all(promises).then((results) => {
return results[0].concat(results[1]);
});
});
}
/**
* Get offline messages to send to a certain user.
*
* @param {number} conversationId Conversation ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with messages.
*/
getConversationMessages(conversationId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE,
{conversationid: conversationId});
});
}
/**
* Get offline messages to send to a certain user.
*
* @param {number} toUserId User ID to get messages to.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any[]>} Promise resolved with messages.
*/
getMessages(toUserId: number, siteId?: string): Promise<any[]> {
return this.sitesProvider.getSite(siteId).then((site) => {
return site.getDb().getRecords(AddonMessagesOfflineProvider.MESSAGES_TABLE, {touserid: toUserId});
});
}
/**
* Check if there are offline messages to send to a conversation.
*
* @param {number} conversationId Conversation ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline messages, false otherwise.
*/
hasConversationMessages(conversationId: number, siteId?: string): Promise<boolean> {
return this.getConversationMessages(conversationId, siteId).then((messages) => {
return !!messages.length;
});
}
@ -122,14 +205,37 @@ export class AddonMessagesOfflineProvider {
*
* @param {number} toUserId User ID to check.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with boolean: true if has offline messages, false otherwise.
* @return {Promise<boolean>} Promise resolved with boolean: true if has offline messages, false otherwise.
*/
hasMessages(toUserId: number, siteId?: string): Promise<any> {
hasMessages(toUserId: number, siteId?: string): Promise<boolean> {
return this.getMessages(toUserId, siteId).then((messages) => {
return !!messages.length;
});
}
/**
* Save a conversation message to be sent later.
*
* @param {number} conversationId Conversation ID.
* @param {string} message The message to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if stored, rejected if failure.
*/
saveConversationMessage(conversationId: number, message: string, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const entry = {
conversationid: conversationId,
text: message,
timecreated: Date.now(),
deviceoffline: this.appProvider.isOnline() ? 0 : 1
};
return site.getDb().insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, entry).then(() => {
return entry;
});
});
}
/**
* Save a message to be sent later.
*
@ -169,7 +275,11 @@ export class AddonMessagesOfflineProvider {
data = { deviceoffline: value ? 1 : 0 };
messages.forEach((message) => {
promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data));
if (message.conversationid) {
promises.push(db.insertRecord(AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, data));
} else {
promises.push(db.insertRecord(AddonMessagesOfflineProvider.MESSAGES_TABLE, data));
}
});
return Promise.all(promises);

View File

@ -115,7 +115,11 @@ export class AddonMessagesProvider {
}
// It's an offline message.
return this.messagesOffline.deleteMessage(message.touserid, message.smallmessage, message.timecreated);
if (message.conversationid) {
return this.messagesOffline.deleteConversationMessage(message.conversationid, message.text, message.timecreated);
} else {
return this.messagesOffline.deleteMessage(message.touserid, message.smallmessage, message.timecreated);
}
}
/**
@ -127,13 +131,15 @@ export class AddonMessagesProvider {
* @return {Promise<any>} Promise resolved when the message has been deleted.
*/
deleteMessageOnline(id: number, read: number, userId?: number): Promise<any> {
userId = userId || this.sitesProvider.getCurrentSiteUserId();
const params = {
const params: any = {
messageid: id,
userid: userId,
read: read
userid: userId || this.sitesProvider.getCurrentSiteUserId()
};
if (typeof read != 'undefined') {
params.read = read;
}
return this.sitesProvider.getCurrentSite().write('core_message_delete_message', params).then(() => {
return this.invalidateDiscussionCache(userId);
});
@ -526,11 +532,11 @@ export class AddonMessagesProvider {
}
// Get offline messages.
return this.messagesOffline.getMessages(userId).then((offlineMessages) => {
return this.messagesOffline.getConversationMessages(conversationId).then((offlineMessages) => {
// Mark offline messages as pending.
offlineMessages.forEach((message) => {
message.pending = true;
message.text = message.smallmessage;
message.useridfrom = userId;
});
result.messages = result.messages.concat(offlineMessages);
@ -1497,6 +1503,113 @@ export class AddonMessagesProvider {
});
}
/**
* Send a message to a conversation.
*
* @param {number} conversationId Conversation ID.
* @param {string} message The message to send.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with:
* - sent (boolean) True if message was sent to server, false if stored in device.
* - message (any) If sent=false, contains the stored message.
* @since 3.6
*/
sendMessageToConversation(conversationId: number, message: string, siteId?: string): Promise<any> {
// Convenience function to store a message to be synchronized later.
const storeOffline = (): Promise<any> => {
return this.messagesOffline.saveConversationMessage(conversationId, message, siteId).then((entry) => {
return {
sent: false,
message: entry
};
});
};
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (!this.appProvider.isOnline()) {
// App is offline, store the message.
return storeOffline();
}
// Check if this conversation already has offline messages.
// If so, store this message since they need to be sent in order.
return this.messagesOffline.hasConversationMessages(conversationId, siteId).catch(() => {
// Error, it's safer to assume it has messages.
return true;
}).then((hasStoredMessages) => {
if (hasStoredMessages) {
return storeOffline();
}
// Online and no messages stored. Send it to server.
return this.sendMessageToConversationOnline(conversationId, message).then(() => {
return { sent: true };
}).catch((error) => {
if (this.utils.isWebServiceError(error)) {
// It's a WebService error, the user cannot send the message so don't store it.
return Promise.reject(error);
}
// Error sending message, store it to retry later.
return storeOffline();
});
});
}
/**
* Send a message to a conversation. It will fail if offline or cannot connect.
*
* @param {number} conversationId Conversation ID.
* @param {string} message The message to send
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if success, rejected if failure.
* @since 3.6
*/
sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const messages = [
{
text: message,
textformat: 1
}
];
return this.sendMessagesToConversationOnline(conversationId, messages, siteId).then((response) => {
return this.invalidateConversationMessages(conversationId, siteId).catch(() => {
// Ignore errors.
}).then(() => {
return response[0];
});
});
}
/**
* Send some messages to a conversation. It will fail if offline or cannot connect.
*
* @param {number} conversationId Conversation ID.
* @param {any} messages Messages to send. Each message must contain text and, optionally, textformat.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if success, rejected if failure.
* @since 3.6
*/
sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => {
const params = {
conversationid: conversationId,
messages: messages.map((message) => {
return {
text: message.text,
textformat: typeof message.textformat != 'undefined' ? message.textformat : 1
};
})
};
return site.write('core_message_send_messages_to_conversation', params);
});
}
/**
* Helper method to sort messages by time.
*

View File

@ -42,6 +42,21 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
super('AddonMessagesSync', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate);
}
/**
* Get the ID of a discussion sync.
*
* @param {number} conversationId Conversation ID.
* @param {number} userId User ID talking to (if no conversation ID).
* @return {string} Sync ID.
*/
protected getSyncId(conversationId: number, userId: number): string {
if (conversationId) {
return 'conversationid:' + conversationId;
} else {
return 'userid:' + userId;
}
}
/**
* Try to synchronize all the discussions in a certain site or in all sites.
*
@ -70,22 +85,39 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
return promise.then((messages) => {
const userIds = [],
conversationIds = [],
promises = [];
// Get all the discussions to be synced.
// Get all the conversations to be synced.
messages.forEach((message) => {
if (userIds.indexOf(message.touserid) == -1) {
if (message.conversationid) {
if (conversationIds.indexOf(message.conversationid) == -1) {
conversationIds.push(message.conversationid);
}
} else if (userIds.indexOf(message.touserid) == -1) {
userIds.push(message.touserid);
}
});
// Sync all discussions.
userIds.forEach((userId) => {
promises.push(this.syncDiscussion(userId, siteId).then((warnings) => {
// Sync all conversations.
conversationIds.forEach((conversationId) => {
promises.push(this.syncDiscussion(conversationId, undefined, siteId).then((warnings) => {
if (typeof warnings != 'undefined') {
// Sync successful, send event.
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
userid: userId,
conversationId: conversationId,
warnings: warnings
}, siteId);
}
}));
});
userIds.forEach((userId) => {
promises.push(this.syncDiscussion(undefined, userId, siteId).then((warnings) => {
if (typeof warnings != 'undefined') {
// Sync successful, send event.
this.eventsProvider.trigger(AddonMessagesSyncProvider.AUTO_SYNCED, {
userId: userId,
warnings: warnings
}, siteId);
}
@ -99,24 +131,39 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
/**
* Synchronize a discussion.
*
* @param {number} userId User ID of the discussion.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
* @param {number} conversationId Conversation ID.
* @param {number} userId User ID talking to (if no conversation ID).
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
*/
syncDiscussion(userId: number, siteId?: string): Promise<any> {
syncDiscussion(conversationId: number, userId: number, siteId?: string): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (this.isSyncing(userId, siteId)) {
// There's already a sync ongoing for this SCORM, return the promise.
return this.getOngoingSync(userId, siteId);
const syncId = this.getSyncId(conversationId, userId),
groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled();
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this conversation, return the promise.
return this.getOngoingSync(syncId, siteId);
}
const warnings = [];
this.logger.debug(`Try to sync discussion with user '${userId}'`);
if (conversationId) {
this.logger.debug(`Try to sync conversation '${conversationId}'`);
} else {
this.logger.debug(`Try to sync discussion with user '${userId}'`);
}
// Get offline messages to be sent.
const syncPromise = this.messagesOffline.getMessages(userId, siteId).then((messages) => {
let syncPromise;
if (conversationId) {
syncPromise = this.messagesOffline.getConversationMessages(conversationId, siteId);
} else {
syncPromise = this.messagesOffline.getMessages(userId, siteId);
}
syncPromise = syncPromise.then((messages) => {
if (!messages.length) {
// Nothing to sync.
return [];
@ -134,12 +181,19 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
messages = this.messagesProvider.sortMessages(messages);
// Send the messages.
// We don't use AddonMessagesProvider#sendMessagesOnline because there's a problem with display order.
// @todo Use AddonMessagesProvider#sendMessagesOnline once the display order is fixed.
// Send them 1 by 1 to simulate web's behaviour and to make sure we know which message has failed.
messages.forEach((message, index) => {
// Chain message sending. If 1 message fails to be sent we'll stop sending.
promise = promise.then(() => {
return this.messagesProvider.sendMessageOnline(userId, message.smallmessage, siteId).catch((error) => {
let subPromise;
if (conversationId) {
subPromise = this.messagesProvider.sendMessageToConversationOnline(conversationId, message.text, siteId);
} else {
subPromise = this.messagesProvider.sendMessageOnline(userId, message.smallmessage, siteId);
}
return subPromise.catch((error) => {
if (this.utils.isWebServiceError(error)) {
// Error returned by WS. Store the error to show a warning but keep sending messages.
if (errors.indexOf(error) == -1) {
@ -158,22 +212,61 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
return Promise.reject(error);
}).then(() => {
// Message was sent, delete it from local DB.
return this.messagesOffline.deleteMessage(userId, message.smallmessage, message.timecreated, siteId);
if (conversationId) {
return this.messagesOffline.deleteConversationMessage(conversationId, message.text,
message.timecreated, siteId);
} else {
return this.messagesOffline.deleteMessage(userId, message.smallmessage, message.timecreated, siteId);
}
}).then(() => {
// All done. Wait 1 second to ensure timecreated of messages is different.
if (index < messages.length - 1) {
// In some Moodle versions, wait 1 second to make sure timecreated is different.
// This is because there was a bug where messages with the same timecreated had a wrong order.
if (!groupMessagingEnabled && index < messages.length - 1) {
return setTimeout(() => {return; }, 1000);
}
});
});
});
return promise.then(() => {
return errors;
});
return promise;
}).then((errors) => {
if (errors && errors.length) {
// At least an error occurred, get user full name and add errors to warnings array.
return this.handleSyncErrors(conversationId, userId, errors, warnings);
}).then(() => {
// All done, return the warnings.
return warnings;
});
return this.addOngoingSync(syncId, syncPromise, siteId);
}
/**
* Handle sync errors.
*
* @param {number} conversationId Conversation ID.
* @param {number} userId User ID talking to (if no conversation ID).
* @param {any[]} errors List of errors.
* @param {any[]} warnings Array where to place the warnings.
* @return {Promise<any>} Promise resolved when done.
*/
protected handleSyncErrors(conversationId: number, userId: number, errors: any[], warnings: any[]): Promise<any> {
if (errors && errors.length) {
if (conversationId) {
// Get conversation name and add errors to warnings array.
return this.messagesProvider.getConversation(conversationId, false, false).catch(() => {
// Ignore errors.
return {};
}).then((conversation) => {
errors.forEach((error) => {
warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', {
conversation: conversation.name ? conversation.name : conversationId,
error: this.textUtils.getErrorMessageFromError(error)
}));
});
});
} else {
// Get user full name and add errors to warnings array.
return this.userProvider.getProfile(userId, undefined, true).catch(() => {
// Ignore errors.
return {};
@ -181,16 +274,26 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
errors.forEach((error) => {
warnings.push(this.translate.instant('addon.messages.warningmessagenotsent', {
user: user.fullname ? user.fullname : userId,
error: error
error: this.textUtils.getErrorMessageFromError(error)
}));
});
});
}
}).then(() => {
// All done, return the warnings.
return warnings;
});
}
}
return this.addOngoingSync(userId, syncPromise, siteId);
/**
* If there's an ongoing sync for a certain conversation, wait for it to end.
* If there's no sync ongoing the promise will be resolved right away.
*
* @param {number} conversationId Conversation ID.
* @param {number} userId User ID talking to (if no conversation ID).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when there's no sync going on for the identifier.
*/
waitForSyncConversation(conversationId: number, userId: number, siteId?: string): Promise<any> {
const syncId = this.getSyncId(conversationId, userId);
return this.waitForSync(syncId, siteId);
}
}

View File

@ -188,6 +188,7 @@
"addon.messages.type_strangers": "Others",
"addon.messages.unblockuser": "Unblock user",
"addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?",
"addon.messages.warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}",
"addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}",
"addon.messages.you": "You:",
"addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.",