Index: openacs-4/packages/notifications/notifications.info
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/notifications.info,v
diff -u -r1.62 -r1.63
--- openacs-4/packages/notifications/notifications.info 3 Sep 2024 15:37:39 -0000 1.62
+++ openacs-4/packages/notifications/notifications.info 19 Dec 2024 16:59:14 -0000 1.63
@@ -8,7 +8,7 @@
t
notifications
-
+
OpenACS
Email notifications management
2024-09-02
@@ -17,7 +17,7 @@
3
#notifications.Notifications#
-
+
Index: openacs-4/packages/notifications/tcl/apm-callback-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/apm-callback-procs.tcl,v
diff -u -r1.8 -r1.9
--- openacs-4/packages/notifications/tcl/apm-callback-procs.tcl 11 Sep 2024 06:15:52 -0000 1.8
+++ openacs-4/packages/notifications/tcl/apm-callback-procs.tcl 19 Dec 2024 16:59:14 -0000 1.9
@@ -23,6 +23,12 @@
# Register the service contract implementation with the notifications service
register_email_delivery_method -impl_id $impl_id
+ # Register sse delivery method service contract implementation
+ set impl_id [create_sse_delivery_method_impl]
+
+ # Register the service contract implementation with the notifications service
+ register_sse_delivery_method -impl_id $impl_id
+
# Create the notification type service contract
create_notification_type_contract
}
@@ -42,6 +48,12 @@
# Unregister email delivery method service contract implementation
delete_email_delivery_method_impl
+ # Delete the service contract implementation from the notifications service
+ unregister_sse_delivery_method
+
+ # Unregister sse delivery method service contract implementation
+ delete_sse_delivery_method_impl
+
# Delete the delivery method service contract
delete_delivery_method_contract
@@ -106,6 +118,17 @@
}
}
+ 5.10.1 6.0.0d1 {
+ db_transaction {
+
+ # Register sse delivery method service contract implementation
+ set impl_id [create_sse_delivery_method_impl]
+
+ # Register the service contract implementation with the notifications service
+ register_sse_delivery_method -impl_id $impl_id
+
+ }
+ }
}
}
@@ -183,6 +206,45 @@
-pretty_name "Email"
}
+ad_proc -private notification::apm::create_sse_delivery_method_impl {} {
+ Register the service contract implementation and return the impl_id
+
+ @return impl_id of the created implementation
+} {
+ return [acs_sc::impl::new_from_spec -spec {
+ contract_name "NotificationDeliveryMethod"
+ name "notification_sse"
+ owner "notifications"
+ aliases {
+ Send notification::sse::send
+ ScanReplies notification::sse::scan_replies
+ }
+ }]
+}
+
+ad_proc -private notification::apm::delete_sse_delivery_method_impl {
+ {-impl_name "notification_sse"}
+} {
+ Unregister the NotificationDeliveryMethod service contract implementation for sse.
+} {
+ acs_sc::impl::delete \
+ -contract_name "NotificationDeliveryMethod" \
+ -impl_name $impl_name
+}
+
+ad_proc -private notification::apm::register_sse_delivery_method {
+ -impl_id:required
+} {
+ Register the sse delivery method with the notifications service.
+
+ @param impl_id The ID of the NotificationDeliveryMethod service contract implementation.
+} {
+ notification::delivery::new \
+ -sc_impl_id $impl_id \
+ -short_name "sse" \
+ -pretty_name "Server-sent Events"
+}
+
ad_proc -private notification::apm::update_email_delivery_method_impl {
-impl_id:required
} {
Index: openacs-4/packages/notifications/tcl/notification-sse-procs.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/tcl/notification-sse-procs.tcl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/notifications/tcl/notification-sse-procs.tcl 19 Dec 2024 16:59:14 -0000 1.1
@@ -0,0 +1,245 @@
+ad_library {
+
+ Notifications Server Sent Events Delivery Method
+
+ @creation-date 2024-12-19
+ @author Antonio Pisano
+}
+
+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]
+ }
+
+ ad_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
+ }
+
+ ad_proc -public 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]
+ }
+ }
+ }
+
+ ad_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
+ }
+
+ ad_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
+ }
+
+ if { $content_html eq "" } {
+ set content $content_text
+ } else {
+ set content $content_html
+ }
+
+ #
+ # convert relative URLs to fully qualified URLs
+ #
+ set content [::ad_html_qualify_links $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:
Index: openacs-4/packages/notifications/www/sse/subscribe.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/notifications/www/sse/subscribe.tcl,v
diff -u
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ openacs-4/packages/notifications/www/sse/subscribe.tcl 19 Dec 2024 16:59:14 -0000 1.1
@@ -0,0 +1,10 @@
+ad_page_contract {
+
+ Open an EventSource on this URL to receive all SSE notifications
+ you have subscribed to.
+
+ @see notification::sse::send
+ @see https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events
+}
+
+::notification::sse::subscribe [ad_conn user_id]