All predicatesShow sourcechat.pl -- The SWISH collaboration backbone

We have three levels of identity as enumerated below. Note that these form a hierarchy: a particular user may be logged on using multiple browsers which in turn may have multiple SWISH windows opened.

  1. Any open SWISH window has an associated websocket, represented by the identifier returned by hub_add/3.
  2. Any browser, possibly having multiple open SWISH windows, is identified by a session cookie.
  3. The user may be logged in, either based on the cookie or on HTTP authentication.
Source start_chat(+Request)[private]
HTTP handler that establishes a websocket connection where a user gets an avatar and optionally a name.
Source check_flooding(+Session)[private]
See whether the client associated with a session is flooding us and if so, return a resource error.
Source accept_chat(+Session, +Options, +WebSocket)[private]
Create the websocket for the chat session. If the websocket was lost due to a network failure, the client will try to reconnect with a reconnect token. If this is successful we restore the old identity and WSID. If not, we add the websocket to the "hub" and send a a welcome message over the established websocket.
Source wsid_status(?WSID, ?Status)[private]
Source wsid_session(?WSID, ?SessionId)[private]
Source session_user(?Session, ?TmpUser)[private]
Source visitor_data(?TmpUser, ?UserData:dict)[private]
Source subscription(?WSID, ?Channel, ?SubChannel)[private]
These predicates represent our notion of visitors. Active modes:
Arguments:
WSID- is the identifier of the web socket. As we may have to reconnect lost connections, this is may be replaced.
Session- is the session identifier. This is used to connect SWISH actions to WSIDs.
TmpUser- is the ID with which we identify the user for this run. The value is a UUID and thus doesn't reveal the real identity of the user.
UserData- is a dict that holds information about the real user identity. This can be empty if no information is known about this user.
Status- is one of unload or lost(Time)
Channel- is an atom denoting a chat channel
SubChannel- is a related sub channel.
Source redis_key(+Which, -Server, -Key) is semidet[private]
Source redis_key_ro(+Which, -Server, -Key) is semidet[private]
Find the Redis server and key for a query. The redis_key_ro/3 variant returns the nearby Redis replica if it exists. This can only be used with read-only keys.
Source wsid_status(+WSID, -Status)[private]
Status is one of lost(Time) if we lost contact at Time or unload if the websocket was cleanly disconnected.

The Redis version keeps two keys per WSID as described below. Note that these keys only exist on temporary lost or disconnecting websockets.

Source wsid_status_del_unload(+WSID) is semidet[private]
True when WSID has status unload and this status is removed.
Source register_wsid_session(+WSID, +Session) is det[private]
Register WSID to belong to Session, i.e., the given websocket belongs to a Session on some browser. When using a Redis cluster, we also want to know the Redis consumer on which this session is active, such that we can relay messages to the proper SWISH server.

Redis data:

Source wsid_session(?WSID, ?Session) is nondet[private]
Source wsid_session(?WSID, ?Session, -Consumer) is nondet[private]
True when there is a known visitor WSID that is associated with the HTTP Session, uses Token for reconnecting and runs on a node identified by the Redis Consumer.
Source wsid_session_reclaim(+WSID, -Session) is semidet[private]
True when WSID was connected to Session and now no longer is.
Source wsid_session_reclaim_all(+WSID, +Session) is det[private]
Source current_wsid(?WSID) is nondet[private]
True when WSID is a (Redis) known WSID.
Source session_user(?Session, ?TmpUser:atom)[private]
Relate Session to a tmp user id. Info about the tmp user is maintained in visitor_data/2.
Source visitor_data(?Visitor, ?Data)[private]
Source subscription(?WSID, ?Channel, ?SubChannel)[private]
Requires both WSID -> Channel/SubChannel and backward relation. Redis:

channel:SubChannel --> set(WSID-Channel) subscription:WSID --> set(Channel-SubChannel)

Source subscribe(+WSID, +Channel) is det[private]
Source subscribe(+WSID, +Channel, +SubChannel) is det[private]
Subscript WSID to listen to messages on Channel/SubChannel.
Source unsubscribe(?WSID, ?Channel) is det[private]
Source unsubscribe(?WSID, ?Channel, ?SubChannel) is det[private]
Remove all matching subscriptions.
Source visitor(?WSID) is nondet[private]
Source visitor(?WSID, -Consumer) is nondet[private]
True when WSID should be considered an active visitor that is connected to the SWISH node identified by the Redis Consumer. This means
Source pending_visitor(+WSID, +Timeout) is semidet[private]
True if WSID is inactive. This means we lost the connection at least Timeout seconds ago.
Source wsid_visitor(?WSID, ?Visitor)[private]
True when WSID is associated with Visitor
Source existing_visitor(+WSID, +Session, -TmpUser, -UserData) is semidet[private]
True if we are dealing with an existing visitor for which we lost the connection.
Source create_visitor(+WSID, +Session, -TmpUser, -UserData, +Options)[private]
Create a new visitor when a new websocket is established. Options provides information we have about the user:
current_user_info(+Info)
Already logged in user with given information
avatar(Avatar)
Avatar remembered in the browser for this user.
anonymous_name(NickName)
Nick name remembered in the browser for this user.
anonymous_avatar(URL)
Avatar remembered in the browser for this user. Using the SVG avatars, this is `/icons/avatar.svg#NNN`, which NNN is a bitmask on the SVG to change its appearance,
Source random_key(+Len, -Key) is det[private]
Generate a random confirmation key
Source destroy_visitor(+WSID) is det[private]
The web socket WSID has been closed. We should not immediately destroy the temporary user as the browser may soon reconnect due to a page reload or re-establishing the web socket after a temporary network failure. We leave the destruction thereof to the session, but set the session timeout to a fairly short time.
To be done
- We should only inform clients that we have informed about this user.
Source update_session_timeout(+WSID) is det[private]
WSID was lost. If this is the only websocket on this session we reduce the session timeout to 15 minutes. This cooperates with accept_chat/3, which sets the session timeout to 100 days for as long as we have an active websocket connection.
Source gc_visitors[private]
Reclaim all visitors with whom we have lost the connection and the browser did not reclaim the session within session_lost_timeout seconds.

This also updates active_wsid/2 to reflect the current status.

Source reclaim_visitor(+WSID) is det[private]
Reclaim a WSID connection. If the user left gracefully, this is called immediately. If we lost the connection on an error, this is eventually called (indirectly) by do_gc_visitors/1.
Source create_session_user(+Session, -User, -UserData, +Options)[private]
Associate a user with the session. The user id is a UUID that is not associated with any persistent notion of a user. The destruction is left to the destruction of the session.
Source update_visitor_data(+TmpUser, +Data, +Reason) is det[private]
Update the user data for the visitor TmpUser to Data. This is rather complicated due to all the defaulting rules. Reason is one of:
To be done
- Create a more declarative description on where the various attributes must come from.
Source update_avatar_from_email(+Email, +DataIn, -Data)[private]
Update the avatar after a change of the known email. If the avatar comes from the profile, no action is needed. If Email has a gravatar, use that. Else use the know or a new generated avatar.
Source anonymise_user_data(TmpUser, Data)[private]
Create anonymous user profile.
Source set_visitor_data(+TmpUser, +Data, +Reason) is det[private]
Update the user data for the session user TmpUser and forward the changes.
Source inform_visitor_change(+TmpUser, +Reason) is det[private]
Inform browsers showing TmpUser that the visitor data has changed. The first clause deals with forwarding from HTTP requests, where we have the session and the second from websocket requests where we have the WSID.
Source sync_gazers(+WSID, +Files:list(atom)) is det[private]
A browser signals it has Files open. This happens when a SWISH instance is created as well as when a SWISH instance changes state, such as closing a tab, adding a tab, bringing a tab to the foreground, etc.
Source add_user_details(+Message, -Enriched) is det[private]
Add additional information to a message. Message must contain a uid field.
Source public_user_data(+UID, -Public:dict) is det[private]
True when Public provides the information we publically share about UID. This is currently the name and avatar.
Source get_visitor_data(-Data:dict, +Options) is det[private]
Optain data for a new visitor. Options include:
identity(+Identity)
Identity information provided by authenticate/2. Always present.
avatar(+URL)
Avatar provided by the user
nick_name(+Name)
Nick name provided by the user.

Data always contains an avatar key and optionally contains a name and email key. If the avatar is generated there is also a key avatar_generated with the value true.

bug
- This may check for avatar validity, which may take long. Possibly we should do this in a thread.
Source reply_avatar(+Request)[private]
HTTP handler for Noble Avatar images. Using create_avatar/2 re-creates avatars from the file name, so we can safely discard the avatar file store.

Not really. A new user gets a new avatar and this is based on whether or not the file exists. Probably we should maintain a db of handed out avatars and their last-use time stamp. How to do that? Current swish stats: 400K avatars, 3.2Gb data.

Source chat_broadcast(+Message) is det
Source chat_broadcast(+Message, +Channel) is det
Send Message to all known SWISH clients. Message is a valid JSON object, i.e., a dict or option list. When using Redis we send the message to the swish:chat pubsub channel and listening for swish:chat calls chat_broadcast_local/1,2 in each instance.
Arguments:
Channel- is either an atom or a term Channel/SubChannel, where both Channel and SubChannel are atoms.
Source subscribed(+Channel, +WSID) is semidet[private]
Source subscribed(+Channel, +SubChannel, +WSID) is semidet[private]
Filter used by hub_broadcast/3. WSID is always a locally known web and active socket.
Source send_friends(+WSID, +Message)[private]
Send Message to WSID and all its friends.
Source update_visitors(+Msg) is det[private]
Maintain notion of active users based on broadcasted (re)join and left messages. We sync every 5 minutes to compensate for possible missed users.
Source handle_message(+Message, +Room)[private]
Handle incoming messages. This handles messages from our websocket connections, i.e., this does not see messages on other (Redis) instances.
Source json_message(+Message, +WSID) is det[private]
Process a JSON message translated to a dict. The following messages are understood:
Source forbidden(+Message, +DocID, -Why) is semidet[private]
True if the chat Message about DocID must be forbidden, in which case Why is unified with a string indicating the reason. Currently:
To be done
- Call authorized/2 with all proper identity information.
Source block(+User, +Score, -Cummulative, -Count)[private]
Keep a count and cummulative score for a user.
Source chat_add_user_id(+WSID, +Message0, -Message) is det[private]
Decorate a message with the user credentials.
Source chat_about(+DocID, +Message) is det
Distribute a chat message about DocID.
Source chat_relay(+Message) is det[private]
Store and relay a chat message.
Source chat_enrich(+Message0, -Message) is det[private]
Add time and identifier to the chat message.
Source chat_send(+Message)[private]
Relay the chat message Message. If the message has a volatile property it is broadcasted, but not stored.
Source chat_event(+Event) is semidet[private]
Event happened inside SWISH. Currently triggered events:
updated(+File, +From, +To)
File was updated from hash From to hash To.
profile(+ProfileID)
Session was associated with user with profile ProfileID
logout(+ProfileID)
User logged out. If the login was based on HTTP authentication ProfileID equals http.
Source propagate_profile_change(+ProfileID, +Attribute, +Value)[private]
Trap external changes to the profile.
Source broadcast_event(+Event) is semidet[private]
If true, broadcast this event.
Source broadcast_event(+Event, +File, +WSID) is det[private]
Event happened that is related to File in WSID. Broadcast it to subscribed users as a notification. Always succeeds, also if the message cannot be delivered.
To be done
- Extend the structure to allow other browsers to act.
Source event_html(+Event, -HTML:string) is det[private]
Describe an event as an HTML message to be displayed in the client's notification area.
Source event_file(+Event, -File) is semidet[private]
True when Event is associated with File.
Source chat_to_profile(ProfileID, :HTML) is det
Send a HTML notification to users logged in using ProfileID.
Source notifications(+Options)//
The chat element is added to the navbar and managed by web/js/chat.js
Source broadcast_bell(+Options)//
Adds a bell to indicate central chat messages

Re-exported predicates

The following predicates are exported from this file while their implementation is defined in imported modules or non-module files loaded by this module.

Source chat_broadcast(+Message) is det
Source chat_broadcast(+Message, +Channel) is det
Send Message to all known SWISH clients. Message is a valid JSON object, i.e., a dict or option list. When using Redis we send the message to the swish:chat pubsub channel and listening for swish:chat calls chat_broadcast_local/1,2 in each instance.
Arguments:
Channel- is either an atom or a term Channel/SubChannel, where both Channel and SubChannel are atoms.