mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(push): drop Fluxer badge on self-hosted instances (#3400)
This commit is contained in:
1 parent
be314788b3
commit
af7724dbf6
5 files changed
+104
-20
No files matched your search
@@ -28,6 +28,7 @@ load() ->
|
|||||||
env_config() ->
|
env_config() ->
|
||||||
#{
|
#{
|
||||||
<<"env">> => env_binary("FLUXER_ENV", <<"development">>),
|
<<"env">> => env_binary("FLUXER_ENV", <<"development">>),
|
||||||
|
<<"instance">> => #{<<"self_hosted">> => env_bool("FLUXER_SELF_HOSTED", false)},
|
||||||
<<"internal">> => env_internal_config(),
|
<<"internal">> => env_internal_config(),
|
||||||
<<"public">> => env_public_config(),
|
<<"public">> => env_public_config(),
|
||||||
<<"proxy">> => env_proxy_config(),
|
<<"proxy">> => env_proxy_config(),
|
||||||
@@ -147,9 +148,10 @@ build_config(RawConfig) ->
|
|||||||
Nats = get_map(RawConfig, [<<"services">>, <<"nats">>]),
|
Nats = get_map(RawConfig, [<<"services">>, <<"nats">>]),
|
||||||
Proxy = get_map(RawConfig, [<<"proxy">>]),
|
Proxy = get_map(RawConfig, [<<"proxy">>]),
|
||||||
Public = get_map(RawConfig, [<<"public">>]),
|
Public = get_map(RawConfig, [<<"public">>]),
|
||||||
|
Instance = get_map(RawConfig, [<<"instance">>]),
|
||||||
lists:foldl(fun maps:merge/2, #{}, [
|
lists:foldl(fun maps:merge/2, #{}, [
|
||||||
build_core_config(Service, Internal, Nats, Proxy),
|
build_core_config(Service, Internal, Nats, Proxy),
|
||||||
build_push_config(Service, Public),
|
build_push_config(Service, Public, Instance),
|
||||||
build_sharding_config(Service),
|
build_sharding_config(Service),
|
||||||
build_http_config(Service),
|
build_http_config(Service),
|
||||||
build_cluster_config(Service, Public),
|
build_cluster_config(Service, Public),
|
||||||
@@ -176,10 +178,11 @@ build_core_config(Service, Internal, Nats, Proxy) ->
|
|||||||
)
|
)
|
||||||
}.
|
}.
|
||||||
|
|
||||||
-spec build_push_config(map(), map()) -> config().
|
-spec build_push_config(map(), map(), map()) -> config().
|
||||||
build_push_config(Service, Public) ->
|
build_push_config(Service, Public, Instance) ->
|
||||||
#{
|
#{
|
||||||
push_enabled => get_bool(Service, <<"push_enabled">>, true),
|
push_enabled => get_bool(Service, <<"push_enabled">>, true),
|
||||||
|
self_hosted => get_bool(Instance, <<"self_hosted">>, false),
|
||||||
static_cdn_endpoint => public_endpoint(
|
static_cdn_endpoint => public_endpoint(
|
||||||
get_binary(Service, <<"static_cdn_endpoint">>, <<"http://localhost:8088">>), Public
|
get_binary(Service, <<"static_cdn_endpoint">>, <<"http://localhost:8088">>), Public
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ notification_fields(
|
|||||||
),
|
),
|
||||||
ChannelIdBin = integer_to_binary(ChannelId),
|
ChannelIdBin = integer_to_binary(ChannelId),
|
||||||
MessageIdBin = integer_to_binary(MessageId),
|
MessageIdBin = integer_to_binary(MessageId),
|
||||||
#{
|
with_badge(#{
|
||||||
<<"title">> => push_notification:build_notification_title(
|
<<"title">> => push_notification:build_notification_title(
|
||||||
AuthorName, MessageData, GuildId, GuildName, ChannelName
|
AuthorName, MessageData, GuildId, GuildName, ChannelName
|
||||||
),
|
),
|
||||||
@@ -239,14 +239,24 @@ notification_fields(
|
|||||||
MessageData, MarkdownContext
|
MessageData, MarkdownContext
|
||||||
),
|
),
|
||||||
<<"icon">> => push_notification_format:resolve_author_avatar_url(AuthorData),
|
<<"icon">> => push_notification_format:resolve_author_avatar_url(AuthorData),
|
||||||
<<"badge">> => push_utils:construct_static_asset_url(
|
|
||||||
<<"marketing/branding/symbol-white.svg">>
|
|
||||||
),
|
|
||||||
<<"tag">> => <<"channel:", ChannelIdBin/binary, ":", MessageIdBin/binary>>,
|
<<"tag">> => <<"channel:", ChannelIdBin/binary, ":", MessageIdBin/binary>>,
|
||||||
<<"notification_tag">> => <<"channel:", ChannelIdBin/binary>>,
|
<<"notification_tag">> => <<"channel:", ChannelIdBin/binary>>,
|
||||||
<<"url">> => push_notification_format:build_url(GuildId, ChannelId, MessageId),
|
<<"url">> => push_notification_format:build_url(GuildId, ChannelId, MessageId),
|
||||||
<<"image_url">> => nullable(push_notification_format:extract_image_url(MessageData))
|
<<"image_url">> => nullable(push_notification_format:extract_image_url(MessageData))
|
||||||
}.
|
}).
|
||||||
|
|
||||||
|
-spec with_badge(map()) -> map().
|
||||||
|
with_badge(Fields) ->
|
||||||
|
case fluxer_gateway_env:get(self_hosted) of
|
||||||
|
true ->
|
||||||
|
Fields;
|
||||||
|
_ ->
|
||||||
|
Fields#{
|
||||||
|
<<"badge">> => push_utils:construct_static_asset_url(
|
||||||
|
<<"marketing/branding/symbol-white.svg">>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
end.
|
||||||
|
|
||||||
-spec thread_job_fields(map(), binary() | undefined) -> map().
|
-spec thread_job_fields(map(), binary() | undefined) -> map().
|
||||||
thread_job_fields(MessageData, ChannelName) ->
|
thread_job_fields(MessageData, ChannelName) ->
|
||||||
@@ -329,9 +339,15 @@ outbox_job(Subject, Job, Body, Meta) ->
|
|||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
with_endpoint_env(Fun) ->
|
with_endpoint_env(Fun) ->
|
||||||
|
with_endpoint_env(false, Fun).
|
||||||
|
|
||||||
|
with_endpoint_env(SelfHosted, Fun) ->
|
||||||
ok = meck:new(fluxer_gateway_env, [passthrough, no_link]),
|
ok = meck:new(fluxer_gateway_env, [passthrough, no_link]),
|
||||||
try
|
try
|
||||||
ok = meck:expect(fluxer_gateway_env, get, fun endpoint_env_meck/1),
|
ok = meck:expect(fluxer_gateway_env, get, fun
|
||||||
|
(self_hosted) -> SelfHosted;
|
||||||
|
(Key) -> endpoint_env_meck(Key)
|
||||||
|
end),
|
||||||
Fun()
|
Fun()
|
||||||
after
|
after
|
||||||
meck:unload(fluxer_gateway_env)
|
meck:unload(fluxer_gateway_env)
|
||||||
@@ -397,7 +413,9 @@ encoded_message_job(MessageData, ChannelName) ->
|
|||||||
push_notification_format, resolve_author_avatar_url, fun(_) -> <<"icon">> end
|
push_notification_format, resolve_author_avatar_url, fun(_) -> <<"icon">> end
|
||||||
),
|
),
|
||||||
ok = meck:expect(push_utils, construct_static_asset_url, fun(_) -> <<"badge">> end),
|
ok = meck:expect(push_utils, construct_static_asset_url, fun(_) -> <<"badge">> end),
|
||||||
Job = message_job(MessageData, #{}, 5, 6, 7, <<"Guild">>, ChannelName),
|
Job = with_endpoint_env(fun() ->
|
||||||
|
message_job(MessageData, #{}, 5, 6, 7, <<"Guild">>, ChannelName)
|
||||||
|
end),
|
||||||
json:decode(iolist_to_binary(json:encode(Job)))
|
json:decode(iolist_to_binary(json:encode(Job)))
|
||||||
after
|
after
|
||||||
meck:unload(push_utils),
|
meck:unload(push_utils),
|
||||||
@@ -507,12 +525,34 @@ notification_fields_omit_sensitive_attachment_image_test() ->
|
|||||||
Fields = test_notification_fields(MessageData, 123, <<"Server">>, <<"general">>),
|
Fields = test_notification_fields(MessageData, 123, <<"Server">>, <<"general">>),
|
||||||
?assertEqual(null, maps:get(<<"image_url">>, Fields)).
|
?assertEqual(null, maps:get(<<"image_url">>, Fields)).
|
||||||
|
|
||||||
|
notification_fields_include_the_fluxer_badge_test() ->
|
||||||
|
MessageData = #{<<"content">> => <<"Hi">>, <<"mentions">> => []},
|
||||||
|
Fields = test_notification_fields(MessageData, 123, <<"Server">>, <<"general">>),
|
||||||
|
?assertEqual(
|
||||||
|
<<"https://static.example/marketing/branding/symbol-white.svg">>,
|
||||||
|
maps:get(<<"badge">>, Fields)
|
||||||
|
).
|
||||||
|
|
||||||
|
notification_fields_omit_the_badge_on_self_hosted_instances_test() ->
|
||||||
|
MessageData = #{<<"content">> => <<"Hi">>, <<"mentions">> => []},
|
||||||
|
Fields = test_notification_fields(
|
||||||
|
MessageData, 123, <<"Server">>, <<"general">>, #{}, true
|
||||||
|
),
|
||||||
|
?assertNot(maps:is_key(<<"badge">>, Fields)).
|
||||||
|
|
||||||
test_notification_fields(MessageData, GuildId, GuildName, ChannelName) ->
|
test_notification_fields(MessageData, GuildId, GuildName, ChannelName) ->
|
||||||
test_notification_fields(MessageData, GuildId, GuildName, ChannelName, #{}).
|
test_notification_fields(MessageData, GuildId, GuildName, ChannelName, #{}).
|
||||||
|
|
||||||
test_notification_fields(MessageData, GuildId, GuildName, ChannelName, MarkdownContext) ->
|
test_notification_fields(MessageData, GuildId, GuildName, ChannelName, MarkdownContext) ->
|
||||||
|
test_notification_fields(
|
||||||
|
MessageData, GuildId, GuildName, ChannelName, MarkdownContext, false
|
||||||
|
).
|
||||||
|
|
||||||
|
test_notification_fields(
|
||||||
|
MessageData, GuildId, GuildName, ChannelName, MarkdownContext, SelfHosted
|
||||||
|
) ->
|
||||||
Author = #{<<"id">> => <<"42">>, <<"username">> => <<"Alice">>, <<"avatar">> => null},
|
Author = #{<<"id">> => <<"42">>, <<"username">> => <<"Alice">>, <<"avatar">> => null},
|
||||||
with_endpoint_env(fun() ->
|
with_endpoint_env(SelfHosted, fun() ->
|
||||||
notification_fields(
|
notification_fields(
|
||||||
MessageData#{<<"author">> => Author},
|
MessageData#{<<"author">> => Author},
|
||||||
MarkdownContext,
|
MarkdownContext,
|
||||||
|
|||||||
@@ -74,6 +74,14 @@ blank_env_values_fall_back_to_the_defaults_test() ->
|
|||||||
end
|
end
|
||||||
).
|
).
|
||||||
|
|
||||||
|
self_hosted_follows_the_fluxer_self_hosted_env_test() ->
|
||||||
|
with_env("FLUXER_SELF_HOSTED", "", fun() ->
|
||||||
|
?assertEqual(false, maps:get(self_hosted, fluxer_gateway_config:load()))
|
||||||
|
end),
|
||||||
|
with_env("FLUXER_SELF_HOSTED", "true", fun() ->
|
||||||
|
?assertEqual(true, maps:get(self_hosted, fluxer_gateway_config:load()))
|
||||||
|
end).
|
||||||
|
|
||||||
pinned_guild_ids_reject_non_snowflakes_test() ->
|
pinned_guild_ids_reject_non_snowflakes_test() ->
|
||||||
with_env("FLUXER_GATEWAY_PINNED_GUILD_IDS", "1100000000000000001,ab", fun() ->
|
with_env("FLUXER_GATEWAY_PINNED_GUILD_IDS", "1100000000000000001,ab", fun() ->
|
||||||
?assertError({invalid_pinned_guild_id, "ab"}, fluxer_gateway_config:load())
|
?assertError({invalid_pinned_guild_id, "ab"}, fluxer_gateway_config:load())
|
||||||
|
|||||||
+11
-1
@@ -36,7 +36,8 @@ pub struct NotificationFields {
|
|||||||
pub title: String,
|
pub title: String,
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub icon: String,
|
pub icon: String,
|
||||||
pub badge: String,
|
#[serde(default)]
|
||||||
|
pub badge: Option<String>,
|
||||||
pub tag: String,
|
pub tag: String,
|
||||||
pub notification_tag: String,
|
pub notification_tag: String,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
@@ -165,6 +166,15 @@ mod tests {
|
|||||||
assert_eq!(job.channel_name, None);
|
assert_eq!(job.channel_name, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_job_without_a_badge_decodes() {
|
||||||
|
let job = decode_message(
|
||||||
|
br#"{"v":1,"guild_id":"5","channel_id":"6","message_id":"7","notification":{"title":"t","body":"b","icon":"i","tag":"x","notification_tag":"y","url":"/u"},"user_ids":["8"]}"#,
|
||||||
|
)
|
||||||
|
.expect("the job decodes");
|
||||||
|
assert_eq!(job.notification.badge, None);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_thread_job_decodes_its_optional_fields() {
|
fn a_thread_job_decodes_its_optional_fields() {
|
||||||
let job = decode_message(
|
let job = decode_message(
|
||||||
|
|||||||
@@ -65,27 +65,28 @@ pub fn web_push_message(job: &MessageJob, target_user_id: &str, badge_count: u32
|
|||||||
"has_media": image_url.is_some(),
|
"has_media": image_url.is_some(),
|
||||||
});
|
});
|
||||||
merge_thread_fields(&mut data, job);
|
merge_thread_fields(&mut data, job);
|
||||||
let notification = json!({
|
let badge = fields.badge.as_deref();
|
||||||
|
let mut notification = json!({
|
||||||
"title": fields.title,
|
"title": fields.title,
|
||||||
"body": fields.body,
|
"body": fields.body,
|
||||||
"icon": fields.icon,
|
"icon": fields.icon,
|
||||||
"badge": fields.badge,
|
|
||||||
"tag": fields.tag,
|
"tag": fields.tag,
|
||||||
"navigate": fields.url,
|
"navigate": fields.url,
|
||||||
"app_badge": badge_count.to_string(),
|
"app_badge": badge_count.to_string(),
|
||||||
"data": data,
|
"data": data,
|
||||||
});
|
});
|
||||||
|
insert_present(&mut notification, "badge", badge);
|
||||||
let mut envelope = json!({
|
let mut envelope = json!({
|
||||||
"web_push": WEB_PUSH_MARKER,
|
"web_push": WEB_PUSH_MARKER,
|
||||||
"notification": notification,
|
"notification": notification,
|
||||||
"title": fields.title,
|
"title": fields.title,
|
||||||
"body": fields.body,
|
"body": fields.body,
|
||||||
"icon": fields.icon,
|
"icon": fields.icon,
|
||||||
"badge": fields.badge,
|
|
||||||
"tag": fields.tag,
|
"tag": fields.tag,
|
||||||
"data": data,
|
"data": data,
|
||||||
});
|
});
|
||||||
merge_image_fields(&mut envelope, image_url);
|
insert_present(&mut envelope, "badge", badge);
|
||||||
|
insert_present(&mut envelope, "image_url", image_url);
|
||||||
envelope
|
envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,11 +618,11 @@ fn first_text<'a>(envelope: &'a Value, key: &str) -> Option<&'a str> {
|
|||||||
.or_else(|| envelope.get("notification")?.get(key).and_then(non_empty))
|
.or_else(|| envelope.get("notification")?.get(key).and_then(non_empty))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_image_fields(target: &mut Value, image_url: Option<&str>) {
|
fn insert_present(target: &mut Value, key: &str, value: Option<&str>) {
|
||||||
let (Some(image_url), Some(object)) = (image_url, target.as_object_mut()) else {
|
let (Some(value), Some(object)) = (value, target.as_object_mut()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
object.insert("image_url".to_owned(), image_url.into());
|
object.insert(key.to_owned(), value.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn non_empty(value: &Value) -> Option<&str> {
|
fn non_empty(value: &Value) -> Option<&str> {
|
||||||
@@ -651,7 +652,7 @@ mod tests {
|
|||||||
title: "Elias".to_owned(),
|
title: "Elias".to_owned(),
|
||||||
body: "see you tomorrow".to_owned(),
|
body: "see you tomorrow".to_owned(),
|
||||||
icon: "https://media.fluxer.app/avatars/1/a.webp".to_owned(),
|
icon: "https://media.fluxer.app/avatars/1/a.webp".to_owned(),
|
||||||
badge: "https://media.fluxer.app/badge.png".to_owned(),
|
badge: Some("https://media.fluxer.app/badge.png".to_owned()),
|
||||||
tag: format!("channel:{CHANNEL_ID}:{MESSAGE_ID}"),
|
tag: format!("channel:{CHANNEL_ID}:{MESSAGE_ID}"),
|
||||||
notification_tag: format!("channel:{CHANNEL_ID}"),
|
notification_tag: format!("channel:{CHANNEL_ID}"),
|
||||||
url: format!("/channels/@me/{CHANNEL_ID}"),
|
url: format!("/channels/@me/{CHANNEL_ID}"),
|
||||||
@@ -726,6 +727,28 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_message_with_a_badge_sends_it_in_both_places() {
|
||||||
|
let envelope = web_push_message(&message_job(None), USER_ID, 3);
|
||||||
|
assert_eq!(
|
||||||
|
envelope["badge"],
|
||||||
|
json!("https://media.fluxer.app/badge.png")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
envelope["notification"]["badge"],
|
||||||
|
json!("https://media.fluxer.app/badge.png")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_message_without_a_badge_leaves_the_key_out() {
|
||||||
|
let mut job = message_job(None);
|
||||||
|
job.notification.badge = None;
|
||||||
|
let envelope = web_push_message(&job, USER_ID, 3);
|
||||||
|
assert!(envelope.get("badge").is_none());
|
||||||
|
assert!(envelope["notification"].get("badge").is_none());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_message_outside_a_thread_carries_no_thread_keys() {
|
fn a_message_outside_a_thread_carries_no_thread_keys() {
|
||||||
let envelope = web_push_message(&message_job(None), USER_ID, 3);
|
let envelope = web_push_message(&message_job(None), USER_ID, 3);
|
||||||
|
|||||||
Reference in new issue
Block a user