notification-sse-procs.tcl

Notifications Server Sent Events Delivery Method

Location:
packages/notifications/tcl/notification-sse-procs.tcl
Created:
2024-12-19
Author:
Antonio Pisano

Procedures in this file

Detailed information

notification::sse::channels (private)

 notification::sse::channels to_user_id
Parameters:
to_user_id (required)
Returns:
list of channels

Partial Call Graph (max 5 caller/called nodes):
notification::sse::send notification::sse::send (public) notification::sse::channels notification::sse::channels notification::sse::send->notification::sse::channels

Testcases:
No testcase defined.

notification::sse::scan_replies (private)

 notification::sse::scan_replies

Scan for replies

Partial Call Graph (max 5 caller/called nodes):
AcsSc.notificationdeliverymethod.scanreplies.notification_sse AcsSc.notificationdeliverymethod.scanreplies.notification_sse (private) notification::sse::scan_replies notification::sse::scan_replies AcsSc.notificationdeliverymethod.scanreplies.notification_sse->notification::sse::scan_replies

Testcases:
No testcase defined.

notification::sse::send (public)

 notification::sse::send from_user_id to_user_id reply_object_id \
    notification_type_id subject content_text content_html file_ids

Send the notification.

Parameters:
from_user_id (required)
to_user_id (required)
reply_object_id (required)
notification_type_id (required)
subject (required)
content_text (required)
content_html (required)
file_ids (required)

Partial Call Graph (max 5 caller/called nodes):
AcsSc.notificationdeliverymethod.send.notification_sse AcsSc.notificationdeliverymethod.send.notification_sse (private) notification::sse::send notification::sse::send AcsSc.notificationdeliverymethod.send.notification_sse->notification::sse::send acs_object::get acs_object::get (public) notification::sse::send->acs_object::get acs_user::get acs_user::get (public) notification::sse::send->acs_user::get ad_html_qualify_links ad_html_qualify_links (public) notification::sse::send->ad_html_qualify_links ad_html_to_text ad_html_to_text (public) notification::sse::send->ad_html_to_text db_list_of_ns_sets db_list_of_ns_sets (public) notification::sse::send->db_list_of_ns_sets

Testcases:
No testcase defined.

notification::sse::subscribe (private)

 notification::sse::subscribe subscription_id

Subscribe the current connection channel to notifications. This will detach the connection channel from the thread and abort the script.

Parameters:
subscription_id (required)

Partial Call Graph (max 5 caller/called nodes):
packages/notifications/www/sse/subscribe.tcl packages/notifications/ www/sse/subscribe.tcl notification::sse::subscribe notification::sse::subscribe packages/notifications/www/sse/subscribe.tcl->notification::sse::subscribe ad_script_abort ad_script_abort (public) notification::sse::subscribe->ad_script_abort

Testcases:
No testcase defined.

notification::sse::unsubscribe (private)

 notification::sse::unsubscribe channel subscription_id

Unsubscribe a channel from notifications.

Parameters:
channel (required)
subscription_id (required)

Partial Call Graph (max 5 caller/called nodes):
notification::sse::send notification::sse::send (public) notification::sse::unsubscribe notification::sse::unsubscribe notification::sse::send->notification::sse::unsubscribe notification::sweep::cleanup_sse_subscriptions notification::sweep::cleanup_sse_subscriptions (private) notification::sweep::cleanup_sse_subscriptions->notification::sse::unsubscribe

Testcases:
No testcase defined.
[ hide source ] | [ make this the default ]

Content File Source

ad_library {

    Notifications Server Sent Events Delivery Method

    @creation-date 2024-12-19
    @author Antonio Pisano <antonio@elettrotecnica.it>
}

namespace eval notification::sse {

    #
    # Needed to update subscribers concurrently.
    #
    if {![nsv_exists ::notification::sse subscription_mutex]} {
        nsv_set ::notification::sse subscription_mutex \
            [ns_mutex create ::notification::sse_subscription]
    }

    d_proc -private subscribe {
        subscription_id
    } {
        Subscribe the current connection channel to notifications.

        This will detach the connection channel from the thread and
        abort the script.
    } {
        set channel [ns_connchan detach]
        ns_connchan write $channel [string cat \
                                        "HTTP/1.1 200 OK\r\n" \
                                        "Cache-Control: no-cache\r\n" \
                                        "X-Accel-Buffering': no\r\n" \
                                        "Content-type: text/event-stream\r\n" \
                                        "\r\n"]
        nsv_lappend \
            ::notification::sse \
            channels-$subscription_id \
            $channel
        ad_script_abort
    }

    d_proc -private unsubscribe {
        channel
        subscription_id
    } {
        Unsubscribe a channel from notifications.
    } {
        ns_log notice \
            notification::sse::unsubscribe \
            $subscription_id \
            $channel

        if {[nsv_get ::notification::sse channels-$subscription_id channels]} {
            ns_mutex eval [nsv_get ::notification::sse subscription_mutex] {
                set idx [lsearch -exact $channels $channel]
                nsv_set ::notification::sse channels-$subscription_id \
                    [lreplace $channels $idx $idx]
            }
        }
    }

    d_proc -private channels {
        to_user_id
    } {
        @return list of channels
    } {
        if {![nsv_get ::notification::sse channels-$to_user_id channels]} {
            set channels [list]
        }

        return $channels
    }

    d_proc -public send {
        from_user_id
        to_user_id
        reply_object_id
        notification_type_id
        subject
        content_text
        content_html
        file_ids
    } {
        Send the notification.
    } {
        set channels [::notification::sse::channels $to_user_id]

        if {[llength $channels] == 0} {
            #
            # Nobody listening. We are done.
            #
            return
        }

        #
        # convert relative URLs to fully qualified URLs
        #
        set content [::ad_html_qualify_links $content_html]

        #
        # We currently use the Notification web api to display SSE
        # notifications, which does not support HTML markup.
        #
        set content [::ad_html_to_text $content]


        set user_locale [::lang::user::site_wide_locale -user_id $to_user_id]
        if { $user_locale eq "" } {
            set user_locale [::lang::system::site_wide_locale]
        }

        set subject [::lang::util::localize $subject $user_locale]
        set content [::lang::util::localize $content $user_locale]

        set from_user [::acs_user::get -user_id $from_user_id]
        set from_user [dict filter $from_user key user_id first_names last_name email]

        set to_user [::acs_user::get -user_id $to_user_id]
        set to_user [dict filter $to_user key user_id first_names last_name email]

        set reply_object [::acs_object::get -object_id $reply_object_id]
        set reply_object [dict filter $reply_object key object_id title package_id object_type]

        set notification_type [ns_set array [lindex [db_list_of_ns_sets get_notif_type {
            select type_id, short_name, pretty_name, description
            from notification_types
            where type_id = :notification_type_id
        }] 0]]

        #
        # We do not expand files right now the same as other entities,
        # but we may in the future.
        #

        #
        # Serialize message as JSON
        #
        dom createNodeCmd -jsonType NUMBER textNode jsonNumber
        dom createNodeCmd -jsonType STRING textNode jsonString

        dom createNodeCmd -jsonType NONE elementNode first_names
        dom createNodeCmd -jsonType NONE elementNode last_name
        dom createNodeCmd -jsonType NONE elementNode user_id
        dom createNodeCmd -jsonType NONE elementNode email

        dom createNodeCmd -jsonType NONE elementNode object_id
        dom createNodeCmd -jsonType NONE elementNode title
        dom createNodeCmd -jsonType NONE elementNode package_id
        dom createNodeCmd -jsonType NONE elementNode object_type

        dom createNodeCmd -jsonType NONE elementNode type_id
        dom createNodeCmd -jsonType NONE elementNode short_name
        dom createNodeCmd -jsonType NONE elementNode pretty_name
        dom createNodeCmd -jsonType NONE elementNode description

        dom createNodeCmd -jsonType NONE elementNode from_user
        dom createNodeCmd -jsonType NONE elementNode to_user
        dom createNodeCmd -jsonType NONE elementNode reply_object
        dom createNodeCmd -jsonType NONE elementNode notification_type

        dom createNodeCmd -jsonType NONE elementNode subject
        dom createNodeCmd -jsonType NONE elementNode content
        dom createNodeCmd -jsonType ARRAY elementNode file_ids

        set resultJSON [dom createDocumentNode]
        $resultJSON appendFromScript {
            from_user {
                user_id {
                    jsonNumber [dict get $from_user user_id]
                }
                foreach key {first_names last_name email} {
                    $key {
                        jsonString [dict get $from_user $key]
                    }
                }
            }
            to_user {
                user_id {
                    jsonNumber [dict get $to_user user_id]
                }
                foreach key {first_names last_name email} {
                    $key {
                        jsonString [dict get $to_user $key]
                    }
                }
            }
            reply_object {
                foreach key {object_id package_id} {
                    $key {
                        jsonNumber [dict get $reply_object $key]
                    }
                }
                foreach key {title object_type} {
                    $key {
                        jsonString [dict get $reply_object $key]
                    }
                }
            }
            notification_type {
                type_id {
                    jsonNumber [dict get $notification_type type_id]
                }
                foreach key {short_name pretty_name description} {
                    $key {
                        jsonString [dict get $notification_type $key]
                    }
                }
            }
            subject {
                jsonString $subject
            }
            content {
                jsonString $content
            }
            file_ids [lmap file_id $file_ids {
                jsonNumber $file_id
            }]
        }

        set message [$resultJSON asJSON]

        foreach channel $channels {
            try {
                ns_connchan write $channel [string cat "data: " $message "\n\n"]
            } on error {errmsg} {
                ::notification::sse::unsubscribe $channel $to_user_id
            }
        }

        return $message
    }

    ad_proc -private scan_replies {} {
        Scan for replies
    } {
        #
        # A noop because there is no reply with SSE.
        #
    }

}

# Local variables:
#    mode: tcl
#    tcl-indent-level: 4
#    indent-tabs-mode: nil
# End: