@ -82,12 +82,10 @@ export interface ConversationAttributes {
active_at : number ;
lastJoinedTimestamp : number ; // ClosedGroup: last time we were added to this group
groupAdmins? : Array < string > ;
moderators? : Array < string > ; // TODO to merge to groupAdmins with a migration on the db
isKickedFromGroup? : boolean ;
avatarPath? : string ;
isMe? : boolean ;
subscriberCount? : number ;
sessionRestoreSeen? : boolean ;
is_medium_group? : boolean ;
type : string ;
avatarPointer? : string ;
@ -124,12 +122,10 @@ export interface ConversationAttributesOptionals {
timestamp? : number ; // timestamp of what?
lastJoinedTimestamp? : number ;
groupAdmins? : Array < string > ;
moderators? : Array < string > ;
isKickedFromGroup? : boolean ;
avatarPath? : string ;
isMe? : boolean ;
subscriberCount? : number ;
sessionRestoreSeen? : boolean ;
is_medium_group? : boolean ;
type : string ;
avatarPointer? : string ;
@ -164,11 +160,9 @@ export const fillConvoAttributesWithDefaults = (
lastMessageStatus : null ,
lastJoinedTimestamp : new Date ( '1970-01-01Z00:00:00:000' ) . getTime ( ) ,
groupAdmins : [ ] ,
moderators : [ ] ,
isKickedFromGroup : false ,
isMe : false ,
subscriberCount : 0 ,
sessionRestoreSeen : false ,
is_medium_group : false ,
lastMessage : null ,
expireTimer : 0 ,
@ -280,6 +274,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
public isMediumGroup() {
return this . get ( 'is_medium_group' ) ;
}
/ * *
* Returns true if this conversation is active
* i . e . the conversation is visibie on the left pane . ( Either we or another user created this convo ) .
@ -290,99 +285,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return Boolean ( this . get ( 'active_at' ) ) ;
}
public async bumpTyping() {
// We don't send typing messages if the setting is disabled
// or we blocked that user
if (
this . isPublic ( ) ||
this . isMediumGroup ( ) ||
! this . isActive ( ) ||
! window . storage . get ( 'typing-indicators-setting' ) ||
this . isBlocked ( )
) {
return ;
}
if ( ! this . typingRefreshTimer ) {
const isTyping = true ;
this . setTypingRefreshTimer ( ) ;
this . sendTypingMessage ( isTyping ) ;
}
this . setTypingPauseTimer ( ) ;
}
public setTypingRefreshTimer() {
if ( this . typingRefreshTimer ) {
global . clearTimeout ( this . typingRefreshTimer ) ;
}
this . typingRefreshTimer = global . setTimeout ( this . onTypingRefreshTimeout . bind ( this ) , 10 * 1000 ) ;
}
public onTypingRefreshTimeout() {
const isTyping = true ;
this . sendTypingMessage ( isTyping ) ;
// This timer will continue to reset itself until the pause timer stops it
this . setTypingRefreshTimer ( ) ;
}
public setTypingPauseTimer() {
if ( this . typingPauseTimer ) {
global . clearTimeout ( this . typingPauseTimer ) ;
}
this . typingPauseTimer = global . setTimeout ( this . onTypingPauseTimeout . bind ( this ) , 10 * 1000 ) ;
}
public onTypingPauseTimeout() {
const isTyping = false ;
this . sendTypingMessage ( isTyping ) ;
this . clearTypingTimers ( ) ;
}
public clearTypingTimers() {
if ( this . typingPauseTimer ) {
global . clearTimeout ( this . typingPauseTimer ) ;
this . typingPauseTimer = null ;
}
if ( this . typingRefreshTimer ) {
global . clearTimeout ( this . typingRefreshTimer ) ;
this . typingRefreshTimer = null ;
}
}
public sendTypingMessage ( isTyping : boolean ) {
if ( ! this . isPrivate ( ) ) {
return ;
}
const recipientId = this . id ;
if ( ! recipientId ) {
throw new Error ( 'Need to provide either recipientId' ) ;
}
const primaryDevicePubkey = window . storage . get ( 'primaryDevicePubKey' ) ;
if ( recipientId && primaryDevicePubkey === recipientId ) {
// note to self
return ;
}
const typingParams = {
timestamp : Date.now ( ) ,
isTyping ,
typingTimestamp : Date.now ( ) ,
} ;
const typingMessage = new TypingMessage ( typingParams ) ;
// send the message to a single recipient if this is a session chat
const device = new PubKey ( recipientId ) ;
getMessageQueue ( )
. sendToPubKey ( device , typingMessage )
. catch ( window ? . log ? . error ) ;
}
public async cleanup() {
const { deleteAttachmentData } = window . Signal . Migrations ;
await window . Signal . Types . Conversation . deleteExternalFiles ( this . attributes , {
@ -409,12 +311,10 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// removeMessage();
}
public getGroupAdmins () {
public getGroupAdmins ( ): Array < string > {
const groupAdmins = this . get ( 'groupAdmins' ) ;
if ( groupAdmins ? . length ) {
return groupAdmins ;
}
return this . get ( 'moderators' ) ;
return groupAdmins && groupAdmins ? . length > 0 ? groupAdmins : [ ] ;
}
// tslint:disable-next-line: cyclomatic-complexity
@ -558,9 +458,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const newAdmins = _ . uniq ( _ . sortBy ( groupAdmins ) ) ;
if ( _ . isEqual ( existingAdmins , newAdmins ) ) {
// window?.log?.info(
// 'Skipping updates of groupAdmins/moderators. No change detected.'
// );
return ;
}
this . set ( { groupAdmins } ) ;
@ -694,7 +591,8 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return {
author : quotedMessage.getSource ( ) ,
id : ` ${ quotedMessage . get ( 'sent_at' ) } ` || '' ,
text : body ,
// no need to quote the full message length.
text : body?.slice ( 0 , 100 ) ,
attachments : quotedAttachments ,
timestamp : quotedMessage.get ( 'sent_at' ) || 0 ,
convoId : this.id ,
@ -1626,7 +1524,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
await this . commit ( ) ;
}
} else {
// tslint:disable-next-line: no-dynamic-delete
this . typingTimer = null ;
if ( wasTyping ) {
// User was previously typing, and is no longer. State change!
@ -1635,7 +1532,7 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
}
p ublic async clearContactTypingTimer ( _sender : string ) {
p rivate async clearContactTypingTimer ( _sender : string ) {
if ( ! ! this . typingTimer ) {
global . clearTimeout ( this . typingTimer ) ;
this . typingTimer = null ;
@ -1654,6 +1551,112 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return typeof expireTimer === 'number' && expireTimer > 0 ;
}
private shouldDoTyping() {
// for typing to happen, this must be a private unblocked active convo, and the settings to be on
if (
! this . isActive ( ) ||
! window . storage . get ( 'typing-indicators-setting' ) ||
this . isBlocked ( ) ||
! this . isPrivate ( )
) {
return false ;
}
const msgRequestsEnabled =
window . lokiFeatureFlags . useMessageRequests &&
window . inboxStore ? . getState ( ) . userConfig . messageRequests ;
// if msg requests are unused, we have to send typing (this is already a private active unblocked convo)
if ( ! msgRequestsEnabled ) {
return true ;
}
// with message requests in use, we just need to check for isApproved
return Boolean ( this . get ( 'isApproved' ) ) ;
}
private async bumpTyping() {
if ( ! this . shouldDoTyping ( ) ) {
return ;
}
if ( ! this . typingRefreshTimer ) {
const isTyping = true ;
this . setTypingRefreshTimer ( ) ;
this . sendTypingMessage ( isTyping ) ;
}
this . setTypingPauseTimer ( ) ;
}
private setTypingRefreshTimer() {
if ( this . typingRefreshTimer ) {
global . clearTimeout ( this . typingRefreshTimer ) ;
}
this . typingRefreshTimer = global . setTimeout ( this . onTypingRefreshTimeout . bind ( this ) , 10 * 1000 ) ;
}
private onTypingRefreshTimeout() {
const isTyping = true ;
this . sendTypingMessage ( isTyping ) ;
// This timer will continue to reset itself until the pause timer stops it
this . setTypingRefreshTimer ( ) ;
}
private setTypingPauseTimer() {
if ( this . typingPauseTimer ) {
global . clearTimeout ( this . typingPauseTimer ) ;
}
this . typingPauseTimer = global . setTimeout ( this . onTypingPauseTimeout . bind ( this ) , 10 * 1000 ) ;
}
private onTypingPauseTimeout() {
const isTyping = false ;
this . sendTypingMessage ( isTyping ) ;
this . clearTypingTimers ( ) ;
}
private clearTypingTimers() {
if ( this . typingPauseTimer ) {
global . clearTimeout ( this . typingPauseTimer ) ;
this . typingPauseTimer = null ;
}
if ( this . typingRefreshTimer ) {
global . clearTimeout ( this . typingRefreshTimer ) ;
this . typingRefreshTimer = null ;
}
}
private sendTypingMessage ( isTyping : boolean ) {
if ( ! this . isPrivate ( ) ) {
return ;
}
const recipientId = this . id ;
if ( ! recipientId ) {
throw new Error ( 'Need to provide either recipientId' ) ;
}
if ( this . isMe ( ) ) {
// note to self
return ;
}
const typingParams = {
timestamp : Date.now ( ) ,
isTyping ,
typingTimestamp : Date.now ( ) ,
} ;
const typingMessage = new TypingMessage ( typingParams ) ;
// send the message to a single recipient if this is a session chat
const device = new PubKey ( recipientId ) ;
getMessageQueue ( )
. sendToPubKey ( device , typingMessage )
. catch ( window ? . log ? . error ) ;
}
}
export class ConversationCollection extends Backbone . Collection < ConversationModel > {