Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
converse.js
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
converse.js
Commits
7f851208
Commit
7f851208
authored
Jan 26, 2021
by
JC Brand
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Move converse-roster plugin into folder and split up
parent
7199e63f
Changes
11
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
1162 additions
and
1121 deletions
+1162
-1121
src/headless/headless.js
src/headless/headless.js
+15
-15
src/headless/plugins/roster.js
src/headless/plugins/roster.js
+0
-1105
src/headless/plugins/roster/api.js
src/headless/plugins/roster/api.js
+73
-0
src/headless/plugins/roster/contact.js
src/headless/plugins/roster/contact.js
+182
-0
src/headless/plugins/roster/contacts.js
src/headless/plugins/roster/contacts.js
+465
-0
src/headless/plugins/roster/group.js
src/headless/plugins/roster/group.js
+18
-0
src/headless/plugins/roster/groups.js
src/headless/plugins/roster/groups.js
+58
-0
src/headless/plugins/roster/index.js
src/headless/plugins/roster/index.js
+222
-0
src/headless/plugins/roster/presence.js
src/headless/plugins/roster/presence.js
+84
-0
src/headless/plugins/roster/utils.js
src/headless/plugins/roster/utils.js
+44
-0
src/plugins/rosterview/index.js
src/plugins/rosterview/index.js
+1
-1
No files found.
src/headless/headless.js
View file @
7f851208
...
...
@@ -2,23 +2,23 @@
* --------------------
* Any of the following components may be removed if they're not needed.
*/
import
"
./plugins/adhoc.js
"
;
// XEP-0050 Ad Hoc Commands
import
"
./plugins/adhoc.js
"
;
// XEP-0050 Ad Hoc Commands
import
"
./plugins/bookmarks/index.js
"
;
// XEP-0199 XMPP Ping
import
"
./plugins/bosh.js
"
;
// XEP-0206 BOSH
import
"
./plugins/caps.js
"
;
// XEP-0115 Entity Capabilities
import
"
./plugins/carbons.js
"
;
// XEP-0280 Message Carbons
import
"
./plugins/chat/index.js
"
;
// RFC-6121 Instant messaging
import
"
./plugins/bosh.js
"
;
// XEP-0206 BOSH
import
"
./plugins/caps.js
"
;
// XEP-0115 Entity Capabilities
import
"
./plugins/carbons.js
"
;
// XEP-0280 Message Carbons
import
"
./plugins/chat/index.js
"
;
// RFC-6121 Instant messaging
import
"
./plugins/chatboxes.js
"
;
import
"
./plugins/disco.js
"
;
// XEP-0030 Service discovery
import
"
./plugins/headlines.js
"
;
// Support for headline messages
import
"
./plugins/mam/index.js
"
;
// XEP-0313 Message Archive Management
import
"
./plugins/muc/index.js
"
;
// XEP-0045 Multi-user chat
import
"
./plugins/ping.js
"
;
// XEP-0199 XMPP Ping
import
"
./plugins/pubsub.js
"
;
// XEP-0060 Pubsub
import
"
./plugins/roster
.js
"
;
// RFC-6121 Contacts Roster
import
"
./plugins/smacks.js
"
;
// XEP-0198 Stream Management
import
"
./plugins/status.js
"
;
// XEP-0199 XMPP Ping
import
"
./plugins/vcard.js
"
;
// XEP-0054 VCard-temp
import
"
./plugins/disco.js
"
;
// XEP-0030 Service discovery
import
"
./plugins/headlines.js
"
;
// Support for headline messages
import
"
./plugins/mam/index.js
"
;
// XEP-0313 Message Archive Management
import
"
./plugins/muc/index.js
"
;
// XEP-0045 Multi-user chat
import
"
./plugins/ping.js
"
;
// XEP-0199 XMPP Ping
import
"
./plugins/pubsub.js
"
;
// XEP-0060 Pubsub
import
"
./plugins/roster
/index.js
"
;
// RFC-6121 Contacts Roster
import
"
./plugins/smacks.js
"
;
// XEP-0198 Stream Management
import
"
./plugins/status.js
"
;
// XEP-0199 XMPP Ping
import
"
./plugins/vcard.js
"
;
// XEP-0054 VCard-temp
/* END: Removable components */
import
{
converse
}
from
"
./core.js
"
;
...
...
src/headless/plugins/roster.js
deleted
100644 → 0
View file @
7199e63f
This diff is collapsed.
Click to expand it.
src/headless/plugins/roster/api.js
0 → 100644
View file @
7f851208
import
{
_converse
,
api
,
converse
}
from
"
@converse/headless/core
"
;
const
{
Strophe
}
=
converse
.
env
;
export
default
{
/**
* @namespace _converse.api.contacts
* @memberOf _converse.api
*/
contacts
:
{
/**
* This method is used to retrieve roster contacts.
*
* @method _converse.api.contacts.get
* @params {(string[]|string)} jid|jids The JID or JIDs of
* the contacts to be returned.
* @returns {promise} Promise which resolves with the
* _converse.RosterContact (or an array of them) representing the contact.
*
* @example
* // Fetch a single contact
* _converse.api.listen.on('rosterContactsFetched', function () {
* const contact = await _converse.api.contacts.get('buddy@example.com')
* // ...
* });
*
* @example
* // To get multiple contacts, pass in an array of JIDs:
* _converse.api.listen.on('rosterContactsFetched', function () {
* const contacts = await _converse.api.contacts.get(
* ['buddy1@example.com', 'buddy2@example.com']
* )
* // ...
* });
*
* @example
* // To return all contacts, simply call ``get`` without any parameters:
* _converse.api.listen.on('rosterContactsFetched', function () {
* const contacts = await _converse.api.contacts.get();
* // ...
* });
*/
async
get
(
jids
)
{
await
api
.
waitUntil
(
'
rosterContactsFetched
'
);
const
_getter
=
jid
=>
_converse
.
roster
.
get
(
Strophe
.
getBareJidFromJid
(
jid
));
if
(
jids
===
undefined
)
{
jids
=
_converse
.
roster
.
pluck
(
'
jid
'
);
}
else
if
(
typeof
jids
===
'
string
'
)
{
return
_getter
(
jids
);
}
return
jids
.
map
(
_getter
);
},
/**
* Add a contact.
*
* @method _converse.api.contacts.add
* @param {string} jid The JID of the contact to be added
* @param {string} [name] A custom name to show the user by in the roster
* @example
* _converse.api.contacts.add('buddy@example.com')
* @example
* _converse.api.contacts.add('buddy@example.com', 'Buddy')
*/
async
add
(
jid
,
name
)
{
await
api
.
waitUntil
(
'
rosterContactsFetched
'
);
if
(
typeof
jid
!==
'
string
'
||
!
jid
.
includes
(
'
@
'
))
{
throw
new
TypeError
(
'
contacts.add: invalid jid
'
);
}
return
_converse
.
roster
.
addAndSubscribe
(
jid
,
name
);
}
}
}
src/headless/plugins/roster/contact.js
0 → 100644
View file @
7f851208
import
{
_converse
,
api
,
converse
}
from
"
@converse/headless/core
"
;
import
{
Model
}
from
'
@converse/skeletor/src/model.js
'
;
const
{
Strophe
,
$iq
,
$pres
,
u
}
=
converse
.
env
;
/**
* @class
* @namespace RosterContact
*/
const
RosterContact
=
Model
.
extend
({
defaults
:
{
'
chat_state
'
:
undefined
,
'
image
'
:
_converse
.
DEFAULT_IMAGE
,
'
image_type
'
:
_converse
.
DEFAULT_IMAGE_TYPE
,
'
num_unread
'
:
0
,
'
status
'
:
undefined
,
},
async
initialize
(
attributes
)
{
this
.
initialized
=
u
.
getResolveablePromise
();
this
.
setPresence
();
const
{
jid
}
=
attributes
;
const
bare_jid
=
Strophe
.
getBareJidFromJid
(
jid
).
toLowerCase
();
attributes
.
jid
=
bare_jid
;
this
.
set
(
Object
.
assign
({
'
groups
'
:
[],
'
id
'
:
bare_jid
,
'
jid
'
:
bare_jid
,
'
user_id
'
:
Strophe
.
getNodeFromJid
(
jid
)
},
attributes
));
/**
* When a contact's presence status has changed.
* The presence status is either `online`, `offline`, `dnd`, `away` or `xa`.
* @event _converse#contactPresenceChanged
* @type { _converse.RosterContact }
* @example _converse.api.listen.on('contactPresenceChanged', contact => { ... });
*/
this
.
listenTo
(
this
.
presence
,
'
change:show
'
,
()
=>
api
.
trigger
(
'
contactPresenceChanged
'
,
this
));
this
.
listenTo
(
this
.
presence
,
'
change:show
'
,
()
=>
this
.
trigger
(
'
presenceChanged
'
));
/**
* Synchronous event which provides a hook for further initializing a RosterContact
* @event _converse#rosterContactInitialized
* @param { _converse.RosterContact } contact
*/
await
api
.
trigger
(
'
rosterContactInitialized
'
,
this
,
{
'
Synchronous
'
:
true
});
this
.
initialized
.
resolve
();
},
setPresence
()
{
const
jid
=
this
.
get
(
'
jid
'
);
this
.
presence
=
_converse
.
presences
.
findWhere
({
'
jid
'
:
jid
})
||
_converse
.
presences
.
create
({
'
jid
'
:
jid
});
},
openChat
()
{
const
attrs
=
this
.
attributes
;
api
.
chats
.
open
(
attrs
.
jid
,
attrs
,
true
);
},
/**
* Return a string of tab-separated values that are to be used when
* matching against filter text.
*
* The goal is to be able to filter against the VCard fullname,
* roster nickname and JID.
* @returns { String } Lower-cased, tab-separated values
*/
getFilterCriteria
()
{
const
nick
=
this
.
get
(
'
nickname
'
);
const
jid
=
this
.
get
(
'
jid
'
);
let
criteria
=
this
.
getDisplayName
();
criteria
=
!
criteria
.
includes
(
jid
)
?
criteria
.
concat
(
`
${
jid
}
`
)
:
criteria
;
criteria
=
!
criteria
.
includes
(
nick
)
?
criteria
.
concat
(
`
${
nick
}
`
)
:
criteria
;
return
criteria
.
toLowerCase
();
},
getDisplayName
()
{
// Gets overridden in converse-vcard where the fullname is may be returned
if
(
this
.
get
(
'
nickname
'
))
{
return
this
.
get
(
'
nickname
'
);
}
else
{
return
this
.
get
(
'
jid
'
);
}
},
getFullname
()
{
// Gets overridden in converse-vcard where the fullname may be returned
return
this
.
get
(
'
jid
'
);
},
/**
* Send a presence subscription request to this roster contact
* @private
* @method _converse.RosterContacts#subscribe
* @param { String } message - An optional message to explain the
* reason for the subscription request.
*/
subscribe
(
message
)
{
const
pres
=
$pres
({
to
:
this
.
get
(
'
jid
'
),
type
:
"
subscribe
"
});
if
(
message
&&
message
!==
""
)
{
pres
.
c
(
"
status
"
).
t
(
message
).
up
();
}
const
nick
=
_converse
.
xmppstatus
.
getNickname
()
||
_converse
.
xmppstatus
.
getFullname
();
if
(
nick
)
{
pres
.
c
(
'
nick
'
,
{
'
xmlns
'
:
Strophe
.
NS
.
NICK
}).
t
(
nick
).
up
();
}
api
.
send
(
pres
);
this
.
save
(
'
ask
'
,
"
subscribe
"
);
// ask === 'subscribe' Means we have asked to subscribe to them.
return
this
;
},
/**
* Upon receiving the presence stanza of type "subscribed",
* the user SHOULD acknowledge receipt of that subscription
* state notification by sending a presence stanza of type
* "subscribe" to the contact
* @private
* @method _converse.RosterContacts#ackSubscribe
*/
ackSubscribe
()
{
api
.
send
(
$pres
({
'
type
'
:
'
subscribe
'
,
'
to
'
:
this
.
get
(
'
jid
'
)
}));
},
/**
* Upon receiving the presence stanza of type "unsubscribed",
* the user SHOULD acknowledge receipt of that subscription state
* notification by sending a presence stanza of type "unsubscribe"
* this step lets the user's server know that it MUST no longer
* send notification of the subscription state change to the user.
* @private
* @method _converse.RosterContacts#ackUnsubscribe
* @param { String } jid - The Jabber ID of the user who is unsubscribing
*/
ackUnsubscribe
()
{
api
.
send
(
$pres
({
'
type
'
:
'
unsubscribe
'
,
'
to
'
:
this
.
get
(
'
jid
'
)}));
this
.
removeFromRoster
();
this
.
destroy
();
},
/**
* Unauthorize this contact's presence subscription
* @private
* @method _converse.RosterContacts#unauthorize
* @param { String } message - Optional message to send to the person being unauthorized
*/
unauthorize
(
message
)
{
_converse
.
rejectPresenceSubscription
(
this
.
get
(
'
jid
'
),
message
);
return
this
;
},
/**
* Authorize presence subscription
* @private
* @method _converse.RosterContacts#authorize
* @param { String } message - Optional message to send to the person being authorized
*/
authorize
(
message
)
{
const
pres
=
$pres
({
'
to
'
:
this
.
get
(
'
jid
'
),
'
type
'
:
"
subscribed
"
});
if
(
message
&&
message
!==
""
)
{
pres
.
c
(
"
status
"
).
t
(
message
);
}
api
.
send
(
pres
);
return
this
;
},
/**
* Instruct the XMPP server to remove this contact from our roster
* @private
* @method _converse.RosterContacts#
* @returns { Promise }
*/
removeFromRoster
()
{
const
iq
=
$iq
({
type
:
'
set
'
})
.
c
(
'
query
'
,
{
xmlns
:
Strophe
.
NS
.
ROSTER
})
.
c
(
'
item
'
,
{
jid
:
this
.
get
(
'
jid
'
),
subscription
:
"
remove
"
});
return
api
.
sendIQ
(
iq
);
}
});
export
default
RosterContact
;
src/headless/plugins/roster/contacts.js
0 → 100644
View file @
7f851208
This diff is collapsed.
Click to expand it.
src/headless/plugins/roster/group.js
0 → 100644
View file @
7f851208
import
{
Model
}
from
'
@converse/skeletor/src/model.js
'
;
import
{
__
}
from
'
i18n
'
;
import
{
_converse
}
from
"
@converse/headless/core
"
;
const
RosterGroup
=
Model
.
extend
({
initialize
(
attributes
)
{
this
.
set
(
Object
.
assign
({
description
:
__
(
'
Click to hide these contacts
'
),
state
:
_converse
.
OPENED
},
attributes
));
// Collection of contacts belonging to this group.
this
.
contacts
=
new
_converse
.
RosterContacts
();
}
});
export
default
RosterGroup
;
src/headless/plugins/roster/groups.js
0 → 100644
View file @
7f851208
import
RosterGroup
from
'
./group.js
'
;
import
{
Collection
}
from
"
@converse/skeletor/src/collection
"
;
import
{
_converse
}
from
"
@converse/headless/core
"
;
/**
* @class
*/
const
RosterGroups
=
Collection
.
extend
({
model
:
RosterGroup
,
comparator
(
a
,
b
)
{
const
HEADER_WEIGHTS
=
{};
HEADER_WEIGHTS
[
_converse
.
HEADER_UNREAD
]
=
0
;
HEADER_WEIGHTS
[
_converse
.
HEADER_REQUESTING_CONTACTS
]
=
1
;
HEADER_WEIGHTS
[
_converse
.
HEADER_CURRENT_CONTACTS
]
=
2
;
HEADER_WEIGHTS
[
_converse
.
HEADER_UNGROUPED
]
=
3
;
HEADER_WEIGHTS
[
_converse
.
HEADER_PENDING_CONTACTS
]
=
4
;
a
=
a
.
get
(
'
name
'
);
b
=
b
.
get
(
'
name
'
);
const
WEIGHTS
=
HEADER_WEIGHTS
;
const
special_groups
=
Object
.
keys
(
HEADER_WEIGHTS
);
const
a_is_special
=
special_groups
.
includes
(
a
);
const
b_is_special
=
special_groups
.
includes
(
b
);
if
(
!
a_is_special
&&
!
b_is_special
)
{
return
a
.
toLowerCase
()
<
b
.
toLowerCase
()
?
-
1
:
(
a
.
toLowerCase
()
>
b
.
toLowerCase
()
?
1
:
0
);
}
else
if
(
a_is_special
&&
b_is_special
)
{
return
WEIGHTS
[
a
]
<
WEIGHTS
[
b
]
?
-
1
:
(
WEIGHTS
[
a
]
>
WEIGHTS
[
b
]
?
1
:
0
);
}
else
if
(
!
a_is_special
&&
b_is_special
)
{
const
a_header
=
_converse
.
HEADER_CURRENT_CONTACTS
;
return
WEIGHTS
[
a_header
]
<
WEIGHTS
[
b
]
?
-
1
:
(
WEIGHTS
[
a_header
]
>
WEIGHTS
[
b
]
?
1
:
0
);
}
else
if
(
a_is_special
&&
!
b_is_special
)
{
const
b_header
=
_converse
.
HEADER_CURRENT_CONTACTS
;
return
WEIGHTS
[
a
]
<
WEIGHTS
[
b_header
]
?
-
1
:
(
WEIGHTS
[
a
]
>
WEIGHTS
[
b_header
]
?
1
:
0
);
}
},
/**
* Fetches all the roster groups from sessionStorage.
* @private
* @method _converse.RosterGroups#fetchRosterGroups
* @returns { Promise } - A promise which resolves once the groups have been fetched.
*/
fetchRosterGroups
()
{
return
new
Promise
(
success
=>
{
this
.
fetch
({
success
,
// We need to first have all groups before
// we can start positioning them, so we set
// 'silent' to true.
silent
:
true
,
});
});
}
});
export
default
RosterGroups
;
src/headless/plugins/roster/index.js
0 → 100644
View file @
7f851208
/**
* @module converse-roster
* @copyright The Converse.js contributors
* @license Mozilla Public License (MPLv2)
*/
import
"
@converse/headless/plugins/status
"
;
import
RosterContact
from
'
./contact.js
'
;
import
RosterContacts
from
'
./contacts.js
'
;
import
RosterGroup
from
'
./group.js
'
;
import
RosterGroups
from
'
./groups.js
'
;
import
invoke
from
'
lodash/invoke
'
;
import
log
from
"
@converse/headless/log
"
;
import
roster_api
from
'
./api.js
'
;
import
{
Presence
,
Presences
}
from
'
./presence.js
'
;
import
{
__
}
from
'
i18n
'
;
import
{
_converse
,
api
,
converse
}
from
"
@converse/headless/core
"
;
import
{
clearPresences
,
initRoster
,
updateUnreadCounter
}
from
'
./utils.js
'
;
const
{
$pres
}
=
converse
.
env
;
converse
.
plugins
.
add
(
'
converse-roster
'
,
{
dependencies
:
[
'
converse-status
'
],
initialize
()
{
/* The initialize function gets called as soon as the plugin is
* loaded by converse.js's plugin machinery.
*/
api
.
settings
.
extend
({
'
allow_contact_requests
'
:
true
,
'
auto_subscribe
'
:
false
,
'
synchronize_availability
'
:
true
,
});
api
.
promises
.
add
([
'
cachedRoster
'
,
'
roster
'
,
'
rosterContactsFetched
'
,
'
rosterGroupsFetched
'
,
'
rosterInitialized
'
,
]);
// API methods only available to plugins
Object
.
assign
(
_converse
.
api
,
roster_api
);
_converse
.
HEADER_CURRENT_CONTACTS
=
__
(
'
My contacts
'
);
_converse
.
HEADER_PENDING_CONTACTS
=
__
(
'
Pending contacts
'
);
_converse
.
HEADER_REQUESTING_CONTACTS
=
__
(
'
Contact requests
'
);
_converse
.
HEADER_UNGROUPED
=
__
(
'
Ungrouped
'
);
_converse
.
HEADER_UNREAD
=
__
(
'
New messages
'
);
_converse
.
registerPresenceHandler
=
function
()
{
_converse
.
unregisterPresenceHandler
();
_converse
.
presence_ref
=
_converse
.
connection
.
addHandler
(
presence
=>
{
_converse
.
roster
.
presenceHandler
(
presence
);
return
true
;
},
null
,
'
presence
'
,
null
);
};
/**
* Reject or cancel another user's subscription to our presence updates.
* @method rejectPresenceSubscription
* @private
* @memberOf _converse
* @param { String } jid - The Jabber ID of the user whose subscription is being canceled
* @param { String } message - An optional message to the user
*/
_converse
.
rejectPresenceSubscription
=
function
(
jid
,
message
)
{
const
pres
=
$pres
({
to
:
jid
,
type
:
"
unsubscribed
"
});
if
(
message
&&
message
!==
""
)
{
pres
.
c
(
"
status
"
).
t
(
message
);
}
api
.
send
(
pres
);
};
_converse
.
sendInitialPresence
=
function
()
{
if
(
_converse
.
send_initial_presence
)
{
api
.
user
.
presence
.
send
();
}
};
/**
* Fetch all the roster groups, and then the roster contacts.
* Emit an event after fetching is done in each case.
* @private
* @method _converse.populateRoster
* @param { Bool } ignore_cache - If set to to true, the local cache
* will be ignored it's guaranteed that the XMPP server
* will be queried for the roster.
*/
_converse
.
populateRoster
=
async
function
(
ignore_cache
=
false
)
{
if
(
ignore_cache
)
{
_converse
.
send_initial_presence
=
true
;
}
try
{
await
_converse
.
rostergroups
.
fetchRosterGroups
();
/**
* Triggered once roster groups have been fetched. Used by the
* `converse-rosterview.js` plugin to know when it can start alphabetically
* position roster groups.
* @event _converse#rosterGroupsFetched
* @example _converse.api.listen.on('rosterGroupsFetched', () => { ... });
* @example _converse.api.waitUntil('rosterGroupsFetched').then(() => { ... });
*/
api
.
trigger
(
'
rosterGroupsFetched
'
);
await
_converse
.
roster
.
fetchRosterContacts
();
api
.
trigger
(
'
rosterContactsFetched
'
);
}
catch
(
reason
)
{
log
.
error
(
reason
);
}
finally
{
_converse
.
sendInitialPresence
();
}
};
_converse
.
Presence
=
Presence
;
_converse
.
Presences
=
Presences
;
_converse
.
RosterContact
=
RosterContact
;
_converse
.
RosterContacts
=
RosterContacts
;
_converse
.
RosterGroup
=
RosterGroup
;
_converse
.
RosterGroups
=
RosterGroups
;
_converse
.
unregisterPresenceHandler
=
function
()
{
if
(
_converse
.
presence_ref
!==
undefined
)
{
_converse
.
connection
.
deleteHandler
(
_converse
.
presence_ref
);
delete
_converse
.
presence_ref
;
}
};
/******************** Event Handlers ********************/
api
.
listen
.
on
(
'
chatBoxesInitialized
'
,
()
=>
{
_converse
.
chatboxes
.
on
(
'
change:num_unread
'
,
updateUnreadCounter
);
_converse
.
chatboxes
.
on
(
'
add
'
,
chatbox
=>
{
if
(
chatbox
.
get
(
'
type
'
)
===
_converse
.
PRIVATE_CHAT_TYPE
)
{
chatbox
.
setRosterContact
(
chatbox
.
get
(
'
jid
'
));
}
});
});
api
.
listen
.
on
(
'
beforeTearDown
'
,
()
=>
_converse
.
unregisterPresenceHandler
());
api
.
waitUntil
(
'
rosterContactsFetched
'
).
then
(()
=>
{
_converse
.
roster
.
on
(
'
add
'
,
(
contact
)
=>
{
/* When a new contact is added, check if we already have a
* chatbox open for it, and if so attach it to the chatbox.
*/
const
chatbox
=
_converse
.
chatboxes
.
findWhere
({
'
jid
'
:
contact
.
get
(
'
jid
'
)});
if
(
chatbox
)
{
chatbox
.
setRosterContact
(
contact
.
get
(
'
jid
'
));
}
});
});
api
.
listen
.
on
(
'
streamResumptionFailed
'
,
()
=>
_converse
.
session
.
set
(
'
roster_cached
'
,
false
));
api
.
listen
.
on
(
'
clearSession
'
,
async
()
=>
{
await
clearPresences
();
if
(
_converse
.
shouldClearCache
())
{
if
(
_converse
.
rostergroups
)
{
await
_converse
.
rostergroups
.
clearStore
();
delete
_converse
.
rostergroups
;
}
if
(
_converse
.
roster
)
{
invoke
(
_converse
,
'
roster.data.destroy
'
);
await
_converse
.
roster
.
clearStore
();
delete
_converse
.
roster
;
}
}
});
api
.
listen
.
on
(
'
statusInitialized
'
,
async
reconnecting
=>
{
if
(
reconnecting
)
{
// When reconnecting and not resuming a previous session,
// we clear all cached presence data, since it might be stale
// and we'll receive new presence updates
!
_converse
.
connection
.
hasResumed
()
&&
await
clearPresences
();
}
else
{
_converse
.
presences
=
new
_converse
.
Presences
();
const
id
=
`converse.presences-
${
_converse
.
bare_jid
}
`
;
_converse
.
presences
.
browserStorage
=
_converse
.
createStore
(
id
,
"
session
"
);
// We might be continuing an existing session, so we fetch
// cached presence data.
_converse
.
presences
.
fetch
();
}
/**
* Triggered once the _converse.Presences collection has been
* initialized and its cached data fetched.
* Returns a boolean indicating whether this event has fired due to
* Converse having reconnected.
* @event _converse#presencesInitialized
* @type { bool }
* @example _converse.api.listen.on('presencesInitialized', reconnecting => { ... });
*/
api
.
trigger
(
'
presencesInitialized
'
,
reconnecting
);
});
api
.
listen
.
on
(
'
presencesInitialized
'
,
async
(
reconnecting
)
=>
{
if
(
reconnecting
)
{
/**
* Similar to `rosterInitialized`, but instead pertaining to reconnection.
* This event indicates that the roster and its groups are now again
* available after Converse.js has reconnected.
* @event _converse#rosterReadyAfterReconnection
* @example _converse.api.listen.on('rosterReadyAfterReconnection', () => { ... });
*/
api
.
trigger
(
'
rosterReadyAfterReconnection
'
);
}
else
{
await
initRoster
();
}
_converse
.
roster
.
onConnected
();
_converse
.
registerPresenceHandler
();
_converse
.
populateRoster
(
!
_converse
.
connection
.
restored
);
});
}
});
src/headless/plugins/roster/presence.js
0 → 100644
View file @
7f851208
import
isNaN
from
"
lodash/isNaN
"
;
import
{
Collection
}
from
"
@converse/skeletor/src/collection
"
;
import
{
Model
}
from
'
@converse/skeletor/src/model.js
'
;
import
{
_converse
,
converse
}
from
"
@converse/headless/core
"
;
const
{
Strophe
,
dayjs
,
sizzle
}
=
converse
.
env
;
export
const
Resource
=
Model
.
extend
({
'
idAttribute
'
:
'
name
'
});
export
const
Resources
=
Collection
.
extend
({
'
model
'
:
Resource
});
export
const
Presence
=
Model
.
extend
({
defaults
:
{
'
show
'
:
'
offline
'
},
initialize
()
{
this
.
resources
=
new
Resources
();
const
id
=
`converse.identities-
${
this
.
get
(
'
jid
'
)}
`
;
this
.
resources
.
browserStorage
=
_converse
.
createStore
(
id
,
"
session
"
);
this
.
listenTo
(
this
.
resources
,
'
update
'
,
this
.
onResourcesChanged
);
this
.
listenTo
(
this
.
resources
,
'
change
'
,
this
.
onResourcesChanged
);
},
onResourcesChanged
()
{
const
hpr
=
this
.
getHighestPriorityResource
();
const
show
=
hpr
?.
attributes
?.
show
||
'
offline
'
;
if
(
this
.
get
(
'
show
'
)
!==
show
)
{
this
.
save
({
'
show
'
:
show
});
}
},
/**
* Return the resource with the highest priority.
* If multiple resources have the same priority, take the latest one.
* @private
*/
getHighestPriorityResource
()
{
return
this
.
resources
.
sortBy
(
r
=>
`
${
r
.
get
(
'
priority
'
)}
-
${
r
.
get
(
'
timestamp
'
)}
`
).
reverse
()[
0
];
},
/**
* Adds a new resource and it's associated attributes as taken
* from the passed in presence stanza.
* Also updates the presence if the resource has higher priority (and is newer).
* @private
* @param { XMLElement } presence: The presence stanza
*/
addResource
(
presence
)
{
const
jid
=
presence
.
getAttribute
(
'
from
'
),
name
=
Strophe
.
getResourceFromJid
(
jid
),
delay
=
sizzle
(
`delay[xmlns="
${
Strophe
.
NS
.
DELAY
}
"]`
,
presence
).
pop
(),
priority
=
presence
.
querySelector
(
'
priority
'
)?.
textContent
??
0
,
resource
=
this
.
resources
.
get
(
name
),
settings
=
{
'
name
'
:
name
,
'
priority
'
:
isNaN
(
parseInt
(
priority
,
10
))
?
0
:
parseInt
(
priority
,
10
),
'
show
'
:
presence
.
querySelector
(
'
show
'
)?.
textContent
??
'
online
'
,
'
timestamp
'
:
delay
?
dayjs
(
delay
.
getAttribute
(
'
stamp
'
)).
toISOString
()
:
(
new
Date
()).
toISOString
()
};
if
(
resource
)
{
resource
.
save
(
settings
);
}
else
{
this
.
resources
.
create
(
settings
);
}
},
/**
* Remove the passed in resource from the resources map.
* Also redetermines the presence given that there's one less
* resource.
* @private
* @param { string } name: The resource name
*/
removeResource
(
name
)
{
const
resource
=
this
.
resources
.
get
(
name
);
if
(
resource
)
{
resource
.
destroy
();
}
}
});
export
const
Presences
=
Collection
.
extend
({
'
model
'
:
Presence
});
src/headless/plugins/roster/utils.js
0 → 100644
View file @
7f851208
import
{
_converse
,
api
}
from
"
@converse/headless/core
"
;
import
{
Model
}
from
'
@converse/skeletor/src/model.js
'
;
export
async
function
initRoster
()
{
// Initialize the Bakcbone collections that represent the contats
// roster and the roster groups.
await
api
.
waitUntil
(
'
VCardsInitialized
'
);
_converse
.
roster
=
new
_converse
.
RosterContacts
();
let
id
=
`converse.contacts-
${
_converse
.
bare_jid
}
`
;
_converse
.
roster
.
browserStorage
=
_converse
.
createStore
(
id
);
_converse
.
roster
.
data
=
new
Model
();
id
=
`converse-roster-model-
${
_converse
.
bare_jid
}
`
;
_converse
.
roster
.
data
.
id
=
id
;
_converse
.
roster
.
data
.
browserStorage
=
_converse
.
createStore
(
id
);
_converse
.
roster
.
data
.
fetch
();
id
=
`converse.roster.groups
${
_converse
.
bare_jid
}
`
;
_converse
.
rostergroups
=
new
_converse
.
RosterGroups
();
_converse
.
rostergroups
.
browserStorage
=
_converse
.
createStore
(
id
);
/**
* Triggered once the `_converse.RosterContacts` and `_converse.RosterGroups` have
* been created, but not yet populated with data.
* This event is useful when you want to create views for these collections.
* @event _converse#chatBoxMaximized
* @example _converse.api.listen.on('rosterInitialized', () => { ... });
* @example _converse.api.waitUntil('rosterInitialized').then(() => { ... });
*/
api
.
trigger
(
'
rosterInitialized
'
);
}
export
function
updateUnreadCounter
(
chatbox
)
{
const
contact
=
_converse
.
roster
&&
_converse
.
roster
.
findWhere
({
'
jid
'
:
chatbox
.
get
(
'
jid
'
)});
if
(
contact
!==
undefined
)
{
contact
.
save
({
'
num_unread
'
:
chatbox
.
get
(
'
num_unread
'
)});
}
}
export
async
function
clearPresences
()
{
await
_converse
.
presences
?.
clearStore
();
}
src/plugins/rosterview/index.js
View file @
7f851208
...
...
@@ -5,7 +5,7 @@
*/
import
"
../modal
"
;
import
"
@converse/headless/plugins/chatboxes
"
;
import
"
@converse/headless/plugins/roster
"
;
import
"
@converse/headless/plugins/roster
/index.js
"
;
import
"
modals/add-contact.js
"
;
import
RosterContactView
from
'
./contactview.js
'
;
import
RosterGroupView
from
'
./groupview.js
'
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment