diff --git a/README.md b/README.md index 934d1a9..14cbd42 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,36 @@ response = activitysmith.notifications.send( ) ``` +## Rich Push Notifications with Media + +

+ Rich push notification with image +

+ +```ruby +response = activitysmith.notifications.send( + { + title: "Homepage ready", + message: "Your agent finished the redesign.", + media: "https://cdn.example.com/output/homepage-v2.png", + redirection: "https://github.com/acme/web/pull/482" + } +) +``` + +Send images, videos, or audio with your push notifications, press and hold to preview media directly from the notification, then tap through to open the linked content. + +

+ Rich push notification with audio +

+ +What will work: + +- direct image URL: `.jpg`, `.png`, `.gif`, etc. +- direct audio file URL: `.mp3`, `.m4a`, etc. +- direct video file URL: `.mp4`, `.mov`, etc. +- URL that responds with a proper media `Content-Type`, even if the path has no extension + ## Push Notification Redirection and Actions Push notification redirection and actions are optional and can be used to redirect the user to a specific URL when they tap the notification or to trigger a specific action when they long-press the notification. diff --git a/lib/activitysmith.rb b/lib/activitysmith.rb index 085acda..e13ce00 100644 --- a/lib/activitysmith.rb +++ b/lib/activitysmith.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "activitysmith/version" +require_relative "activitysmith/versioned_user_agent" require_relative "activitysmith/notifications" require_relative "activitysmith/live_activities" require_relative "activitysmith/client" diff --git a/lib/activitysmith/client.rb b/lib/activitysmith/client.rb index 24ab8cd..23e9506 100644 --- a/lib/activitysmith/client.rb +++ b/lib/activitysmith/client.rb @@ -11,6 +11,7 @@ def initialize(api_key:) config = OpenapiClient::Configuration.new config.access_token = api_key + config.user_agent = VersionedUserAgent.value if config.respond_to?(:user_agent=) api_client = OpenapiClient::ApiClient.new(config) @notifications = Notifications.new(OpenapiClient::PushNotificationsApi.new(api_client)) diff --git a/lib/activitysmith/notifications.rb b/lib/activitysmith/notifications.rb index ec7fa69..f875d68 100644 --- a/lib/activitysmith/notifications.rb +++ b/lib/activitysmith/notifications.rb @@ -7,12 +7,16 @@ def initialize(api) end def send(request, opts = {}) - @api.send_push_notification(normalize_channels_target(request), opts) + normalized = normalize_channels_target(request) + assert_valid_media_actions!(normalized) + @api.send_push_notification(normalized, opts) end # Backward-compatible alias. def send_push_notification(push_notification_request, opts = {}) - @api.send_push_notification(normalize_channels_target(push_notification_request), opts) + normalized = normalize_channels_target(push_notification_request) + assert_valid_media_actions!(normalized) + @api.send_push_notification(normalized, opts) end def method_missing(name, *args, &block) @@ -49,5 +53,29 @@ def normalize_channels(channels) [] end end + + def assert_valid_media_actions!(request) + media = request_value(request, :media) + actions = request_value(request, :actions) + has_media = media.is_a?(String) ? !media.strip.empty? : !media.nil? + has_actions = actions.respond_to?(:empty?) ? !actions.empty? : !actions.nil? + + return unless has_media && has_actions + + raise ArgumentError, "ActivitySmith: media cannot be combined with actions" + end + + def request_value(request, key) + if request.is_a?(Hash) + return request[key] if request.key?(key) + return request[key.to_s] if request.key?(key.to_s) + + return nil + end + + return request.public_send(key) if request.respond_to?(key) + + nil + end end end diff --git a/lib/activitysmith/versioned_user_agent.rb b/lib/activitysmith/versioned_user_agent.rb new file mode 100644 index 0000000..ee3cdc6 --- /dev/null +++ b/lib/activitysmith/versioned_user_agent.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module ActivitySmith + module VersionedUserAgent + def self.value + "activitysmith-ruby/#{ActivitySmith::VERSION}" + end + end +end diff --git a/test/resources_test.rb b/test/resources_test.rb index 0418301..bcfce38 100644 --- a/test/resources_test.rb +++ b/test/resources_test.rb @@ -83,6 +83,44 @@ def test_notifications_map_channels_to_target ) end + def test_notifications_preserve_media_and_redirection + api = FakePushApi.new + resource = ActivitySmith::Notifications.new(api) + + payload = { + title: "Voice Over Generated", + media: "https://cdn.activitysmith.com/voice_over.mp3", + redirection: "https://studio.acme.com/voice-overs/482/review" + } + + resource.send(payload) + + assert_equal( + [ + [:send_push_notification, payload, {}] + ], + api.calls + ) + end + + def test_notifications_reject_media_and_actions + api = FakePushApi.new + resource = ActivitySmith::Notifications.new(api) + + error = assert_raises(ArgumentError) do + resource.send( + { + title: "Voice Over Generated", + media: "https://cdn.activitysmith.com/voice_over.mp3", + actions: [{ title: "Open", type: "open_url", url: "https://example.com" }] + } + ) + end + + assert_equal "ActivitySmith: media cannot be combined with actions", error.message + assert_empty api.calls + end + def test_live_activities_short_and_legacy_methods api = FakeLiveApi.new resource = ActivitySmith::LiveActivities.new(api)