nativescript-connectycube
Version:
ConnectyCube chat and video chat SDK for NativeScript
1,148 lines (965 loc) • 34.9 kB
JavaScript
const Config = require('../cubeConfig'),
Utils = require('../cubeInternalUtils'),
ChatUtils = require('./cubeChatInternalUtils'),
ChatHelpers = require('./cubeChatHelpers'),
StreamManagement = require('./cubeStreamManagement'),
ContactListProxy = require('./cubeContactList'),
PrivacyListProxy = require('./cubePrivacyList'),
MucProxy = require('./cubeMultiUserChat'),
XMPP = require('./xmpp');
class ChatService {
constructor(proxy) {
this.proxy = proxy;
this.xmppClient = XMPP.client({
service: Config.chatProtocol.websocket,
credentials: (auth, mechanism) => {
const crds = {
username: this.xmppClient.options.username,
password: this.xmppClient.options.password,
};
return auth(crds);
},
});
this.webrtcSignalingProcessor = null;
this.stanzasCallbacks = {};
this.earlyIncomingMessagesQueue = [];
this.isConnected = false;
this._isConnecting = false;
this._isLogout = false;
this._isReconnect = false;
this._checkPingTimer = undefined;
this.helpers = new ChatHelpers();
this.dialog = null;
this.message = null;
this.webrtcSignalingProcessor = null;
this.xmppClientListeners = [];
this.connectPromise = null;
// Chat additional modules
const options = {
xmppClient: this.xmppClient,
helpers: this.helpers,
stanzasCallbacks: this.stanzasCallbacks,
};
this.contactList = new ContactListProxy(options);
this.privacylist = new PrivacyListProxy(options);
this.muc = new MucProxy(options);
if (Config.chat.streamManagement.enable) {
if (Config.chatProtocol.active === 2) {
this.streamManagement = new StreamManagement();
this._sentMessageCallback = (messageLost, messageSent) => {
if (typeof this.onSentMessageCallback === 'function') {
if (messageSent) {
this.onSentMessageCallback(null, messageSent);
} else {
this.onSentMessageCallback(messageLost);
}
}
};
}
}
if (Config.chat.reconnect.enable) {
this.xmppClient.reconnect.delay = Config.chat.reconnect.timeInterval * 1000;
}
this.onChatStatusListener = null;
this.onConnectionErrorListener = null;
this.onDisconnectedListener = null;
this.onReconnectListener = null;
this.onMessageListener = null;
this.onSentMessageCallback = null;
this.onSystemMessageListener = null;
this.onMessageErrorListener = null;
this.onMessageTypingListener = null;
this.onMessageUpdateListener = null;
this.onMessageDeleteListener = null;
this.onMessageReactionsListener = null;
this.onDeliveredStatusListener = null;
this.onReadStatusListener = null;
this.onLastUserActivityListener = null;
this.onSubscribeListener = null;
this.onConfirmSubscribeListener = null;
this.onRejectSubscribeListener = null;
this.onContactListListener = null;
this.onJoinOccupant = null;
this.onLeaveOccupant = null;
this.onKickOccupant = null;
}
get connectionStatus() {
return this.xmppClient.status;
}
connect(params) {
this.connectPromise = new Promise((resolve, reject) => {
Utils.DLog('[Chat]', 'Connect with parameter:', params);
if (this._isConnecting) {
Utils.DLog('[Chat]', 'Warning! Already in CONNECTING state');
resolve();
return;
}
if (this.isConnected) {
Utils.DLog('[Chat]', 'Warning! Chat is already connected!');
resolve();
return;
}
this._isConnecting = true;
this._isLogout = false;
// remove all old listeners
this.xmppClientListeners.forEach((listener) => {
this.xmppClient.removeListener(listener.name, listener.callback);
});
const callbackConnect = () => {
Utils.DLog('[Chat]', this._isReconnect ? 'RECONNECTING' : 'CONNECTING');
};
this.xmppClient.on('connect', callbackConnect);
this.xmppClientListeners.push({
name: 'connect',
callback: callbackConnect,
});
const callbackOnline = () => {
Utils.DLog('[Chat]', 'ONLINE');
this.startPingTimer();
this._postConnectActions();
resolve();
this.connectPromise = null;
};
this.xmppClient.on('online', callbackOnline);
this.xmppClientListeners.push({
name: 'online',
callback: callbackOnline,
});
const callbackOffline = () => {
Utils.DLog('[Chat]', 'OFFLINE');
};
this.xmppClient.on('offline', callbackOffline);
this.xmppClientListeners.push({
name: 'offline',
callback: callbackOffline,
});
const callbackDisconnect = () => {
Utils.DLog('[Chat]', 'DISCONNECTED');
if (typeof this.onDisconnectedListener === 'function') {
Utils.safeCallbackCall(this.onDisconnectedListener);
}
if (Config.chat.reconnect.enable) {
this.isConnected = false;
this._isConnecting = false;
} else {
this.disconnect();
}
this.stopPingTimer();
};
this.xmppClient.on('disconnect', callbackDisconnect);
this.xmppClientListeners.push({
name: 'disconnect',
callback: callbackDisconnect,
});
const callbackStatus = (status, value) => {
Utils.DLog('[Chat]', `status - ${status}`, typeof value === 'object' ? JSON.stringify(value) : '');
if (typeof this.onChatStatusListener === 'function') {
/*
* "online" - indicates that xmpp is authenticated and addressable
* "offline" - indicates that xmpp disconnected and no automatic attempt to reconnect will happen (after calling xmpp.stop())
* "connecting" - socket is connecting
* "connect" - socket is connected
* "opening" - stream is opening
* "open" - stream is open
* "closing" - stream is closing
* "close" - stream is closed
* "disconnecting" - socket is disconnecting
* "disconnect" - socket is disconnected
*/
Utils.safeCallbackCall(this.onChatStatusListener, status);
}
};
this.xmppClient.on('status', callbackStatus);
this.xmppClientListeners.push({
name: 'status',
callback: callbackStatus,
});
const callbackStanza = (stanza) => {
// it can be a case,
// when message came after xmpp auth but before resource bindging,
// and it can cause some crashes, e.g.
// https://github.com/ConnectyCube/connectycube-js-sdk-releases/issues/28
if (stanza.is('message') && !this.isConnected) {
this.earlyIncomingMessagesQueue.push(stanza);
Utils.DLog('[Chat]', "on 'stanza': enqueue incoming stanza (isConnected=false)");
return;
}
// after 'input' and 'element' (only if stanza, not nonza)
if (stanza.is('presence')) {
this._onPresence(stanza);
} else if (stanza.is('iq')) {
this._onIQ(stanza);
} else if (stanza.is('message')) {
if (stanza.attrs.type === 'headline') {
this._onSystemMessageListener(stanza);
} else if (stanza.attrs.type === 'error') {
this._onMessageErrorListener(stanza);
} else {
this._onMessage(stanza);
}
}
};
this.xmppClient.on('stanza', callbackStanza);
this.xmppClientListeners.push({
name: 'stanza',
callback: callbackStanza,
});
const callbackError = (err) => {
Utils.DLog('[Chat]', 'ERROR:', err, {
_isReconnect: this._isReconnect,
connectPromise: !!this.connectPromise,
});
if (this.connectPromise) {
if (!this._isReconnect) {
if (err.name == 'SASLError') {
err = err.condition;
}
reject(err);
this.connectPromise = null;
}
} else {
if (typeof this.onConnectionErrorListener === 'function') {
Utils.safeCallbackCall(this.onConnectionErrorListener, err);
}
}
};
this.xmppClient.on('error', callbackError);
this.xmppClientListeners.push({ name: 'error', callback: callbackError });
const callbackOutput = (str) => {
Utils.callTrafficUsageCallback('xmppDataWrite', { body: str });
Utils.DLog('[Chat]', 'SENT:', str);
};
this.xmppClient.on('output', callbackOutput);
this.xmppClientListeners.push({
name: 'output',
callback: callbackOutput,
});
const callbackInput = (str) => {
Utils.callTrafficUsageCallback('xmppDataRead', { body: str });
Utils.DLog('[Chat]', 'RECV:', str);
};
this.xmppClient.on('input', callbackInput);
this.xmppClientListeners.push({ name: 'input', callback: callbackInput });
// save user connection data so they will be used when authenticate (above)
this.xmppClient.options.username = ChatUtils.buildUserJidLocalPart(params.userId);
this.xmppClient.options.password = params.password;
// start connect
this.xmppClient.start().catch((err) => {
console.error('[Chat] xmppClient.start error', err);
if (this.connectPromise) {
reject(err);
this.connectPromise = null;
} else {
if (typeof this.onConnectionErrorListener === 'function') {
Utils.safeCallbackCall(this.onConnectionErrorListener, err);
}
}
});
});
return this.connectPromise;
}
/**
* @deprecated since version 2.0
*/
getContacts() {
return new Promise((resolve, reject) => {
this.contactList
.get()
.then((contacts) => {
this.contactList.contacts = contacts;
resolve(contacts);
})
.catch((error) => {
reject(reject);
});
});
}
ping() {
return new Promise((resolve, reject) => {
const iqParams = {
id: ChatUtils.getUniqueId('ping'),
to: Config.endpoints.chat,
type: 'get',
};
const iqStanza = ChatUtils.createIqStanza(iqParams);
iqStanza.c('ping', {
xmlns: 'urn:xmpp:ping',
});
this.stanzasCallbacks[iqParams.id] = (stanza) => {
const error = ChatUtils.getElement(stanza, 'error');
if (error) {
reject(ChatUtils.buildErrorFromXMPPErrorStanza(error));
} else {
resolve();
}
};
this.xmppClient.send(iqStanza);
});
}
pingWithTimeout(timeout = 5000) {
return Promise.race([
this.ping(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Chat ping() timed out')), timeout)),
]);
}
startPingTimer() {
this.stopPingTimer();
if (Config.chat.ping.enable) {
// Min time interval between pings is 30 seconds.
const validTime = Config.chat.ping.timeInterval < 30 ? 30 : Config.chat.ping.timeInterval;
this._checkPingTimer = setInterval(() => {
this.ping();
}, validTime * 1000);
}
}
stopPingTimer() {
if (this._checkPingTimer) {
clearInterval(this._checkPingTimer);
this._checkPingTimer = null;
}
}
send(jidOrUserId, message) {
const stanzaParams = {
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(jidOrUserId),
type: message.type ? message.type : this.helpers.typeChat(jidOrUserId),
id: message.id ? message.id : Utils.getBsonObjectId(),
};
let messageStanza = ChatUtils.createMessageStanza(stanzaParams);
if (message.body) {
messageStanza.c('body').t(message.body).up();
}
if (message.markable) {
messageStanza
.c('markable', {
xmlns: 'urn:xmpp:chat-markers:0',
})
.up();
}
if (message.extension) {
messageStanza.c('extraParams', {
xmlns: 'jabber:client',
});
messageStanza = ChatUtils.filledExtraParams(messageStanza, message.extension);
}
if (Config.chat.streamManagement.enable) {
message.id = stanzaParams.id;
this.xmppClient.send(messageStanza, message);
} else {
this.xmppClient.send(messageStanza);
}
return stanzaParams.id;
}
sendSystemMessage(jidOrUserId, message) {
const stanzaParams = {
type: 'headline',
id: message.id ? message.id : Utils.getBsonObjectId(),
to: this.helpers.jidOrUserId(jidOrUserId),
};
let messageStanza = ChatUtils.createMessageStanza(stanzaParams);
if (message.body) {
messageStanza.c('body').t(message.body).up();
}
// custom parameters
if (message.extension) {
messageStanza
.c('extraParams', {
xmlns: 'jabber:client',
})
.c('moduleIdentifier')
.t('SystemNotifications')
.up();
messageStanza = ChatUtils.filledExtraParams(messageStanza, message.extension);
}
this.xmppClient.send(messageStanza);
return stanzaParams.id;
}
sendIsTypingStatus(jidOrUserId) {
const stanzaParams = {
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(jidOrUserId),
type: this.helpers.typeChat(jidOrUserId),
};
const messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza.c('composing', {
xmlns: 'http://jabber.org/protocol/chatstates',
});
this.xmppClient.send(messageStanza);
}
sendIsStopTypingStatus(jidOrUserId) {
const stanzaParams = {
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(jidOrUserId),
type: this.helpers.typeChat(jidOrUserId),
};
const messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza.c('paused', {
xmlns: 'http://jabber.org/protocol/chatstates',
});
this.xmppClient.send(messageStanza);
}
sendDeliveredStatus(params) {
const stanzaParams = {
type: 'chat',
from: this.helpers.getUserCurrentJid(),
id: Utils.getBsonObjectId(),
to: this.helpers.jidOrUserId(params.userId),
};
const messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza
.c('received', {
xmlns: 'urn:xmpp:chat-markers:0',
id: params.messageId,
})
.up();
messageStanza
.c('extraParams', {
xmlns: 'jabber:client',
})
.c('dialog_id')
.t(params.dialogId);
this.xmppClient.send(messageStanza);
}
sendReadStatus(params) {
const stanzaParams = {
type: 'chat',
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(params.userId),
id: Utils.getBsonObjectId(),
};
const messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza
.c('displayed', {
xmlns: 'urn:xmpp:chat-markers:0',
id: params.messageId,
})
.up();
messageStanza
.c('extraParams', {
xmlns: 'jabber:client',
})
.c('dialog_id')
.t(params.dialogId);
this.xmppClient.send(messageStanza);
}
// params:
// - to (user id OR dialog id)
// - dialogId
// - body
// - originMessageId
// - last?
editMessage(params) {
const stanzaParams = {
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(params.to),
type: this.helpers.typeChat(params.to),
id: Utils.getBsonObjectId(),
};
let messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza.c('body').t(params.body).up();
messageStanza
.c('replace', {
xmlns: 'urn:xmpp:message-correct:0',
id: params.originMessageId,
last: params.last ? 'true' : 'false',
})
.up();
messageStanza
.c('extraParams', {
xmlns: 'jabber:client',
})
.c('dialog_id')
.t(params.dialogId);
if (params.extension) {
messageStanza = ChatUtils.filledExtraParams(messageStanza, params.extension);
}
this.xmppClient.send(messageStanza);
}
// params:
// - to (user id OR dialog id)
// - dialogId
// - messageId
deleteMessage(params) {
const stanzaParams = {
from: this.helpers.getUserCurrentJid(),
to: this.helpers.jidOrUserId(params.to),
type: this.helpers.typeChat(params.to),
id: Utils.getBsonObjectId(),
};
const messageStanza = ChatUtils.createMessageStanza(stanzaParams);
messageStanza
.c('remove', {
xmlns: 'urn:xmpp:message-delete:0',
id: params.messageId,
})
.up();
messageStanza
.c('extraParams', {
xmlns: 'jabber:client',
})
.c('dialog_id')
.t(params.dialogId);
this.xmppClient.send(messageStanza);
}
getLastUserActivity(jidOrUserId) {
return new Promise((resolve, reject) => {
const iqParams = {
from: this.helpers.getUserCurrentJid(),
id: ChatUtils.getUniqueId('lastActivity'),
to: this.helpers.jidOrUserId(jidOrUserId),
type: 'get',
};
const iqStanza = ChatUtils.createIqStanza(iqParams);
iqStanza.c('query', { xmlns: 'jabber:iq:last' });
this.stanzasCallbacks[iqParams.id] = (stanza) => {
const error = ChatUtils.getElement(stanza, 'error');
const { userId, seconds } = this._onLastActivityStanza(stanza);
if (error) {
reject(ChatUtils.buildErrorFromXMPPErrorStanza(stanza));
} else {
resolve({ userId, seconds });
}
};
this.xmppClient.send(iqStanza);
});
}
_onLastActivityStanza(stanza) {
const from = ChatUtils.getAttr(stanza, 'from');
const userId = this.helpers.getUserIdFromJID(from);
const query = ChatUtils.getElement(stanza, 'query');
const seconds = +ChatUtils.getAttr(query, 'seconds');
// trigger onLastUserActivityListener callback
Utils.safeCallbackCall(this.onLastUserActivityListener, userId, seconds);
return { userId, seconds };
}
markActive() {
const iqParams = {
id: this.helpers.getUniqueId('markActive'),
type: 'set',
};
const iqStanza = ChatUtils.createIqStanza(iqParams);
iqStanza.c('mobile', {
xmlns: 'http://tigase.org/protocol/mobile#v2',
enable: 'false',
});
this.xmppClient.send(iqStanza);
}
markInactive() {
const iqParams = {
id: this.helpers.getUniqueId('markActive'),
type: 'set',
};
const iqStanza = ChatUtils.createIqStanza(iqParams);
iqStanza.c('mobile', {
xmlns: 'http://tigase.org/protocol/mobile#v2',
enable: 'true',
});
this.xmppClient.send(iqStanza);
}
disconnect() {
Utils.DLog('[Chat]', 'disconnect');
if (this._isLogout) {
Utils.DLog('[Chat]', 'Warning! Chat is already disconnected!');
return;
}
this.muc.joinedRooms = {};
this.isConnected = false;
this._isConnecting = false;
this._isLogout = true;
this._isReconnect = false;
this.helpers.setUserCurrentJid('');
if (Config.chat.streamManagement.enable) {
this.streamManagement._removeElementHandler();
}
return this.xmppClient.stop();
}
search(params) {
const query = Object.assign({}, params);
if (query.start_date) {
query.start_date = new Date(query.start_date).toISOString();
}
if (query.end_date) {
query.end_date = new Date(query.end_date).toISOString();
}
if (Utils.isArray(query.chat_dialog_ids)) {
query.chat_dialog_ids = query.chat_dialog_ids.join(',');
}
const ajaxParams = {
type: 'GET',
url: Utils.getUrl(`${Config.urls.chat}/search`),
data: query,
};
return this.proxy.ajax(ajaxParams);
}
/// PRIVATE ///
_onMessage(rawStanza) {
const forwardedStanza = ChatUtils.getElementTreePath(rawStanza, ['sent', 'forwarded', 'message']),
stanza = forwardedStanza || rawStanza,
from = ChatUtils.getAttr(stanza, 'from'),
type = ChatUtils.getAttr(stanza, 'type'),
messageId = ChatUtils.getAttr(stanza, 'id'),
markable = ChatUtils.getElement(stanza, 'markable'),
delivered = ChatUtils.getElement(stanza, 'received'),
read = ChatUtils.getElement(stanza, 'displayed'),
replaceSubElement = ChatUtils.getElement(stanza, 'replace'),
reactionsSubElement = ChatUtils.getElement(stanza, 'reactions'),
removeSubElement = ChatUtils.getElement(stanza, 'remove'),
composing = ChatUtils.getElement(stanza, 'composing'),
paused = ChatUtils.getElement(stanza, 'paused'),
invite = ChatUtils.getElement(stanza, 'invite'),
delay = ChatUtils.getElement(stanza, 'delay'),
extraParams = ChatUtils.getElement(stanza, 'extraParams'),
bodyContent = ChatUtils.getElementText(stanza, 'body'),
isForwarded = !!forwardedStanza,
resource = (this.xmppClient.jid && this.xmppClient.jid._resource) || '';
// ignore private message from the same resource
if (type === 'chat' && from.indexOf(resource) !== -1) {
return true;
}
let extraParamsParsed, recipientId, recipient;
recipient = type === 'chat' ? ChatUtils.getAttr(stanza, 'to') : null;
recipientId = recipient ? this.helpers.getUserIdFromJID(recipient) : null;
let dialogId = type === 'groupchat' ? this.helpers.getDialogIdFromJID(from) : null,
userId = type === 'groupchat' ? this.helpers.getIdFromResource(from) : this.helpers.getUserIdFromJID(from),
marker = delivered || read || null;
// ignore invite messages from MUC
if (invite) return true;
if (extraParams) {
extraParamsParsed = ChatUtils.parseExtraParams(extraParams);
if (extraParamsParsed.dialogId) {
dialogId = extraParamsParsed.dialogId;
}
}
// typing statuses
if (composing || paused) {
if (typeof this.onMessageTypingListener === 'function' && (type === 'chat' || type === 'groupchat' || !delay)) {
Utils.safeCallbackCall(this.onMessageTypingListener, !!composing, userId, dialogId);
}
return true;
}
// edit message
if (replaceSubElement) {
if (typeof this.onMessageUpdateListener === 'function') {
Utils.safeCallbackCall(
this.onMessageUpdateListener,
ChatUtils.getAttr(replaceSubElement, 'id'),
ChatUtils.getAttr(replaceSubElement, 'last') === 'true',
bodyContent,
dialogId,
userId,
extraParamsParsed ? extraParamsParsed.extension : null
);
}
return true;
}
// reactions
if (reactionsSubElement) {
if (isForwarded && type === 'groupchat') {
return true;
}
if (typeof this.onMessageReactionsListener === 'function') {
const messageId = ChatUtils.getAttr(reactionsSubElement, 'message_id');
const userId = +ChatUtils.getAttr(reactionsSubElement, 'user_id');
const { add, remove } = ChatUtils.parseReactions(reactionsSubElement);
const { dialogId } = extraParamsParsed;
Utils.safeCallbackCall(this.onMessageReactionsListener, messageId, userId, dialogId, add, remove);
}
return true;
}
// delete message
if (removeSubElement) {
if (typeof this.onMessageDeleteListener === 'function') {
Utils.safeCallbackCall(
this.onMessageDeleteListener,
ChatUtils.getAttr(removeSubElement, 'id'),
dialogId,
userId
);
}
return true;
}
// delivered / read statuses
if (marker) {
if (delivered) {
if (typeof this.onDeliveredStatusListener === 'function' && type === 'chat') {
Utils.safeCallbackCall(this.onDeliveredStatusListener, ChatUtils.getAttr(delivered, 'id'), dialogId, userId);
}
} else {
if (typeof this.onReadStatusListener === 'function' && type === 'chat') {
Utils.safeCallbackCall(this.onReadStatusListener, ChatUtils.getAttr(read, 'id'), dialogId, userId);
}
}
return;
}
// autosend 'received' status (ignore messages from yourself)
if (markable && userId != this.helpers.getUserIdFromJID(this.helpers.userCurrentJid(this.xmppClient))) {
const autoSendReceiveStatusParams = {
messageId: messageId,
userId: userId,
dialogId: dialogId,
};
this.sendDeliveredStatus(autoSendReceiveStatusParams);
}
const message = {
id: messageId,
dialog_id: dialogId,
recipient_id: recipientId,
is_forwarded: isForwarded,
type: type,
body: bodyContent,
extension: extraParamsParsed ? extraParamsParsed.extension : null,
delay: delay,
};
if (markable) {
message.markable = 1;
}
if (typeof this.onMessageListener === 'function' && (type === 'chat' || type === 'groupchat')) {
Utils.safeCallbackCall(this.onMessageListener, userId, message);
}
}
_onPresence(stanza) {
const from = ChatUtils.getAttr(stanza, 'from'),
id = ChatUtils.getAttr(stanza, 'id'),
type = ChatUtils.getAttr(stanza, 'type'),
currentUserId = this.helpers.getUserIdFromJID(this.helpers.userCurrentJid(this.xmppClient)),
x = ChatUtils.getElement(stanza, 'x');
let xXMLNS, status, statusCode;
if (x) {
xXMLNS = ChatUtils.getAttr(x, 'xmlns');
status = ChatUtils.getElement(x, 'status');
if (status) {
statusCode = ChatUtils.getAttr(status, 'code');
}
}
// MUC presences
if (xXMLNS && xXMLNS.startsWith('http://jabber.org/protocol/muc')) {
// Error
if (type === 'error') {
// JOIN to dialog error
if (id.endsWith(':join')) {
if (typeof this.stanzasCallbacks[id] === 'function') {
this.stanzasCallbacks[id](stanza);
}
}
return;
}
const dialogId = this.helpers.getDialogIdFromJID(from);
const userId = this.helpers.getUserIdFromRoomJid(from);
// self presence
if (status) {
// KICK from dialog event
if (statusCode == '301') {
if (typeof this.onKickOccupant === 'function') {
const actorElement = ChatUtils.getElement(ChatUtils.getElement(x, 'item'), 'actor');
const initiatorUserJid = ChatUtils.getAttr(actorElement, 'jid');
const initiatorId = this.helpers.getUserIdFromJID(initiatorUserJid);
Utils.safeCallbackCall(this.onKickOccupant, dialogId, initiatorId);
}
delete this.muc.joinedRooms[this.helpers.getRoomJidFromRoomFullJid(from)];
return;
} else {
if (type === 'unavailable') {
// LEAVE response
if (status && statusCode == '110') {
if (typeof this.stanzasCallbacks['muc:leave'] === 'function') {
Utils.safeCallbackCall(this.stanzasCallbacks['muc:leave'], null);
}
}
return;
}
// JOIN response
if (id.endsWith(':join') && status && statusCode == '110') {
if (typeof this.stanzasCallbacks[id] === 'function') {
this.stanzasCallbacks[id](stanza);
}
return;
}
}
// Occupants JOIN/LEAVE events
} else {
if (userId != currentUserId) {
// Leave
if (type === 'unavailable') {
if (typeof this.onLeaveOccupant === 'function') {
Utils.safeCallbackCall(this.onLeaveOccupant, dialogId, parseInt(userId));
}
return;
// Join
} else {
if (typeof this.onJoinOccupant === 'function') {
Utils.safeCallbackCall(this.onJoinOccupant, dialogId, parseInt(userId));
}
return;
}
}
}
}
// ROSTER presences
const userId = this.helpers.getUserIdFromJID(from);
let contact = this.contactList.contacts[userId];
if (!type) {
if (typeof this.onContactListListener === 'function' && contact && contact.subscription !== 'none') {
Utils.safeCallbackCall(this.onContactListListener, userId);
}
} else {
switch (type) {
case 'subscribe':
if (contact && contact.subscription === 'to') {
contact ? (contact.ask = null) : (contact = { ask: null });
contact.subscription = 'both';
this.contactList._sendSubscriptionPresence({
jid: from,
type: 'subscribed',
});
} else {
if (typeof this.onSubscribeListener === 'function') {
Utils.safeCallbackCall(this.onSubscribeListener, userId);
}
}
break;
case 'subscribed':
if (contact && contact.subscription === 'from') {
contact ? (contact.ask = null) : (contact = { ask: null });
contact.subscription = 'both';
} else {
contact ? (contact.ask = null) : (contact = { ask: null });
contact.subscription = 'to';
if (typeof this.onConfirmSubscribeListener === 'function') {
Utils.safeCallbackCall(this.onConfirmSubscribeListener, userId);
}
}
break;
case 'unsubscribed':
contact ? (contact.ask = null) : (contact = { ask: null });
contact.subscription = 'none';
if (typeof this.onRejectSubscribeListener === 'function') {
Utils.safeCallbackCall(this.onRejectSubscribeListener, userId);
}
break;
case 'unsubscribe':
contact ? (contact.ask = null) : (contact = { ask: null });
contact.subscription = 'to';
break;
case 'unavailable':
if (typeof this.onContactListListener === 'function' && contact && contact.subscription !== 'none') {
Utils.safeCallbackCall(this.onContactListListener, userId, type);
}
// send initial presence if one of client (instance) goes offline
if (userId === currentUserId) {
this.xmppClient.send(ChatUtils.createPresenceStanza());
}
break;
}
}
}
_onIQ(stanza) {
const stanzaId = ChatUtils.getAttr(stanza, 'id');
if (this.stanzasCallbacks[stanzaId]) {
Utils.safeCallbackCall(this.stanzasCallbacks[stanzaId], stanza);
delete this.stanzasCallbacks[stanzaId];
} else {
const from = ChatUtils.getAttr(stanza, 'from');
const query = ChatUtils.getElement(stanza, 'query');
if (!from || !query) {
return;
}
this._onLastActivityStanza(stanza);
}
}
_onSystemMessageListener(rawStanza) {
const forwardedStanza = ChatUtils.getElementTreePath(rawStanza, ['sent', 'forwarded', 'message']),
stanza = forwardedStanza || rawStanza,
from = ChatUtils.getAttr(stanza, 'from'),
messageId = ChatUtils.getAttr(stanza, 'id'),
extraParams = ChatUtils.getElement(stanza, 'extraParams'),
userId = this.helpers.getUserIdFromJID(from),
delay = ChatUtils.getElement(stanza, 'delay'),
moduleIdentifier = ChatUtils.getElementText(extraParams, 'moduleIdentifier'),
bodyContent = ChatUtils.getElementText(stanza, 'body'),
extraParamsParsed = ChatUtils.parseExtraParams(extraParams);
if (moduleIdentifier === 'SystemNotifications' && typeof this.onSystemMessageListener === 'function') {
const message = {
id: messageId,
userId: userId,
body: bodyContent,
extension: extraParamsParsed.extension,
};
Utils.safeCallbackCall(this.onSystemMessageListener, message);
} else if (this.webrtcSignalingProcessor && !delay && moduleIdentifier === 'WebRTCVideoChat') {
this.webrtcSignalingProcessor._onMessage(userId, extraParams);
}
}
_onMessageErrorListener(stanza) {
// <error code="503" type="cancel">
// <service-unavailable xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/>
// <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas" xml:lang="en">Service not available.</text>
// </error>
const messageId = ChatUtils.getAttr(stanza, 'id');
//
const error = ChatUtils.buildErrorFromXMPPErrorStanza(stanza);
if (typeof this.onMessageErrorListener === 'function') {
Utils.safeCallbackCall(this.onMessageErrorListener, messageId, error);
}
}
_postConnectActions() {
Utils.DLog('[Chat]', this._isReconnect ? 'RECONNECTED' : 'CONNECTED');
const presence = ChatUtils.createPresenceStanza();
if (Config.chat.streamManagement.enable && Config.chatProtocol.active === 2) {
this.streamManagement.enable(this.xmppClient);
this.streamManagement.sentMessageCallback = this._sentMessageCallback;
}
this.helpers.setUserCurrentJid(this.helpers.userCurrentJid(this.xmppClient));
this.isConnected = true;
this._isConnecting = false;
this._enableCarbons();
this.xmppClient.send(presence); // initial presence
if (this._isReconnect) {
// reconnect
if (typeof this.onReconnectListener === 'function') {
Utils.safeCallbackCall(this.onReconnectListener);
}
} else {
this._isReconnect = true;
}
if (this.earlyIncomingMessagesQueue.length > 0) {
Utils.DLog('[Chat]', `Flush 'earlyIncomingMessagesQueue' (length=${this.earlyIncomingMessagesQueue.length})`);
const stanzasCallback = this.xmppClientListeners.filter((listener) => listener.name === 'stanza')[0].callback;
this.earlyIncomingMessagesQueue.forEach((stanza) => {
stanzasCallback(stanza);
});
this.earlyIncomingMessagesQueue = [];
}
}
_enableCarbons() {
const carbonParams = {
type: 'set',
from: this.helpers.getUserCurrentJid(),
id: ChatUtils.getUniqueId('enableCarbons'),
};
const iqStanza = ChatUtils.createIqStanza(carbonParams);
iqStanza.c('enable', {
xmlns: 'urn:xmpp:carbons:2',
});
this.xmppClient.send(iqStanza);
}
_setSubscriptionToUserLastActivity(jidOrUserId, _isEnable) {
const iqParams = {
id: this.helpers.getUniqueId('statusStreaming'),
type: 'set',
};
const iqStanza = ChatUtils.createIqStanza(iqParams);
iqStanza.c('subscribe', {
xmlns: 'https://connectycube.com/protocol/status_streaming',
user_jid: this.helpers.jidOrUserId(jidOrUserId),
enable: _isEnable,
});
this.xmppClient.send(iqStanza);
}
subscribeToUserLastActivityStatus(jidOrUserId) {
this._setSubscriptionToUserLastActivity(jidOrUserId, true);
}
unsubscribeFromUserLastActivityStatus(jidOrUserId) {
this._setSubscriptionToUserLastActivity(jidOrUserId, false);
}
}
module.exports = ChatService;