Push Notification Confusion

Hello,

I am a somewhat experienced self-hoster and I found Mobilizon while searching for an alternative event platform that I can run myself. I’ve worked through the setup process multiple times, have learned a lot. Have almost everything working that I would want to start out. Except I can’t seem to get push notifications to function at all. The instructions for generating keys and implementing the config is very basic and the server recognizes that it’s available on the front end.

I can post logs or the config I am using but neither stick out to me as a problem. I feel like I’m missing something here. All I’m doing is as instructed on this page per the documentation, then enabling notifications via the settings in my browser. Is there more to this that I’m just missing?

https://docs.joinmobilizon.org/administration/configure/push/

I was using release so I went ahead and performed a source install on an Arch VM. Everything seems to be up and running. I generated and added this no longer valid generated code to my runtime.exs…

config :web_push_encryption, :vapid_details,
  subject: "mailto:noreply@mydomain.tld",
  public_key: "BBG17BHw2bLbK-iZFl7sbD5ySt-4u8L-S86Y4hhxTpD7RxGxM8uu1bRTuThGi5Ii5D90k6tdEGzUA2KZvXdnbHM",
  private_key: "JRyFLZPMaF5XpWzsgyBBqvhkhJRIqtE-0CVr4WilxoA"

« Activate browser push notifications » then becomes an available option in my user preferences. Which, if there isn’t, it feels like there should be an initial « Notifications Enabled! » notification. Anyway, I’m not sure I can see any notifications being attempted in my logs. I enabled debug logs and have push notifications enabled for « A member requested to join one of my groups » wherein email notifications work perfectly fine. Here is the log when another user then requests to join my group.

Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] POST /api
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] {"args":{"author_id":5,"group_id":4,"inserted_at":"2024-01-25T21:21:23.406432Z","object_id":"0036cb3a-7fa5-4bbc-9c33-748725fac833","object_type":"member","op":"build_activity","subject":"member_request","subject_params":{"member_actor_federated_username":"notkj","member_actor_name":"NotKJ","member_role":"NOT_APPROVED"},"type":"member"},"id":25,"meta":{},"system_time":1706217683418861613,"max_attempts":1,"queue":"activity","worker":"Mobilizon.Service.Workers.ActivityBuilder","source":"oban","tags":[],"event":"job:start","attempt":1}
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] {"args":{"author_id":5,"group_id":4,"inserted_at":"2024-01-25T21:21:23.406432Z","object_id":"0036cb3a-7fa5-4bbc-9c33-748725fac833","object_type":"member","op":"build_activity","subject":"member_request","subject_params":{"member_actor_federated_username":"notkj","member_actor_name":"NotKJ","member_role":"NOT_APPROVED"},"type":"member"},"id":25,"meta":{},"state":"success","max_attempts":1,"queue":"activity","worker":"Mobilizon.Service.Workers.ActivityBuilder","source":"oban","tags":[],"event":"job:stop","attempt":1,"duration":14181,"queue_time":8358}
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [error] no function clause matching in Absinthe.Middleware.Telemetry.on_complete/2
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]:     (absinthe 1.7.6) lib/absinthe/middleware/telemetry.ex:37: Absinthe.Middleware.Telemetry.on_complete(#Absinthe.Resolution<[private: %{}, value: nil, state: :unresolved, path: [%Absinthe.Blueprint.Document.Field{name: "groupMembershipChanged", alias: nil, selections: [%Absinthe.Blueprint.Document.Field{name: "id", alias: nil, selections: [], arguments: [], argument_data: %{}, directives: [], flags: %{}, errors: [], source_location: %Absinthe.Blueprint.SourceLocation{line: 3, column: 5}, type_conditions: [], schema_node: %Absinthe.Type.Field{identifier: :id, name: "id", description: "Internal ID for this person", type: :id, deprecation: nil, args: %{}, config: nil, triggers: %{}, middleware: [{{Rajska.FieldAuthorization, :call}, [object: %Absinthe.Type.Object{identifier: :person, name: "Person", description: "Represents a person identity", fields: %{id: %Absinthe.Type.Field{identifier: :id, name: "id", description: "Internal ID for this person", type: :id, deprecation: nil, args: %{}, config: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, {Absinthe.Blueprint.Schema.FieldDefinition, {...}}}, triggers: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, {Absinthe.Blueprint.Schema.FieldDefinition, ...}}, middleware: [{:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, ...}], complexity: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, ...}, default_value: nil, __private__: [], ...}, local: %Absinthe.Type.Field{identifier: :local, name: "local", description: "If the actor is from this instance", type: :boolean, deprecation: nil, args: %{}, config: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, {Absinthe.Blueprint.Schema.FieldDefinition, ...}}, triggers: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, {...}}, middleware: [{:ref, ...}], complexity: {:ref, ...}, default_value: nil, ...}, name: %Absinthe.Type.Field{identifier: :name, name: "name", description: "The actor's displayed name", type: :string, deprecation: nil, args: %{}, config: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, {...}}, triggers: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, ...}, middleware: [{...}], complexity: {...}, ...}, suspended: %Absinthe.Type.Field{identifier: :suspended, name: "suspended", description: "If the actor is suspended", type: :boolean, deprecation: nil, args: %{}, config: {:ref, Mobilizon.GraphQL.Schema.Actors.PersonType, ...}, triggers: {:ref, ...}, middleware: [...], ...}, type: %Absinthe.Type.Field{identifier: :type, name: "type", description: "The type of Actor (Person, Group,…)", type: :actor_type, deprecation: nil, args: %{}, config: {:ref, ...}, triggers: {...}, ...}, user: %Absinthe.Type.Field{identifier: :user, name: "user", description: "The user this actor is associated to", type: :user, deprecation: nil, args: %{}, config: {...}, ...}, domain: %Absinthe.Type.Field{identifier: :domain, name: "domain", description: "The actor's domain if (null if it's this instance)", type: :string, deprecation: nil, args: %{}, ...}, url: %Absinthe.Type.Field{identifier: :url, name: "url", description: "The ActivityPub actor's URL", type: :string, deprecation: nil, ...}, preferred_username: %Absinthe.Type.Field{identifier: :preferred_username, name: "preferred_username", description: "The actor's preferred username", type: :string, ...}, banner: %Absinthe.Type.Field{identifier: :banner, name: "banner", description: "The actor's banner media", ...}, avatar: %Absinthe.Type.Field{identifier: :avatar, name: "avatar", ...}, participations: %Absinthe.Type.Field{identifier: :participations, ...}, summary: %Absinthe.Type.Field{...}, ...}, interfaces: [:action_log_object, :actor], __private__: [__absinthe_referenced__: true, meta: [authorize: :all, scope_field?: true]], definition: Mobilizon.GraphQL.Schema.Actors.PersonType, __reference__: %{module: Mobilizon.GraphQL.Schema.Actors.PersonType, location: %{line: 18, file: "/home/mobilizon/live/lib/graphql/schema/actors/person.ex"}}, is_type_of: nil}, field: :id]}, {Absinthe.Middleware.MapGet, :id}], complexity: nil, default_value: nil, __private__: [], definition: Mobilizon.GraphQL.Schema.Actors.PersonType, __reference__: %{module: Mobilizon.GraphQL.Schema.Actors.PersonType, location: %{line: 22, file: "/home/mobilizon/live/lib/graphql/schema/actors/person.ex"}}}, complexity: nil, parent_type: nil}, %Absinthe.Blueprint.Document.Field{name: "memberships", alias: nil, selections: [%Absinthe.Blueprint.Document.Field{name: "total", alias: nil, selections: [], arguments: [], argument_data: %{}, directives: [], flags: %{}, errors: [], source_location: %Absinthe.Blueprint.SourceLocation{line: 5, column: 7}, type_conditions: [], schema_node: %Absinthe.Type.Field{identifier: :total, name: "total", description: "The total number of elements in the list", type: :integer, deprecation: nil, args: %{}, config: nil, triggers: %{}, middleware: [{{Rajska.FieldAuthorization, :call}, [object: %Absinthe.Type.Object{identifier: :paginated_member_list, name: "PaginatedMemberList", description: "A paginated list of members", fields: %{total: %Absinthe.Type.Field{identifier: :total, name: "total", description: "The total number of elements in the list", type: :integer, deprecation: nil, args: %{}, config: {...}, ...}, elements: %Absinthe.Type.Field{identifier: :elements, name: "elements", description: "A list of members", type: %Absinthe.Type.List{of_type: :member}, deprecation: nil, args: %{}, ...}, __typename: %Absinthe.Type.Field{identifier: :__typename, name: "__typename", description: "The name of the object type currently being queried.", type: :string, deprecation: nil, ...}}, interfaces: [], __private__: [__absinthe_referenced__: true, meta: [authorize: :all]], definition: Mobilizon.GraphQL.Schema.Actors.MemberType, __reference__: %{module: Mobilizon.GraphQL.Schema.Actors.MemberType, location: %{line: 40, file: "/home/mobilizon/live/lib/graphql/schema/actors/member.ex"}}, is_type_of: nil}, field: :total]}, {Absinthe.Middleware.MapGet, :total}], complexity: nil, default_value: nil, __private__: [], definition: Mobilizon.GraphQL.Schema.Actors.MemberType, __reference__: %{module: Mobilizon.GraphQL.Schema.Actors.MemberType, location: %{line: 43, file: "/home/mobilizon/live/lib/graphql/schema/actors/member.ex"}}}, complexity: nil, parent_type: nil}, %Absinthe.Blueprint.Document.Field{name: "elements", alias: nil, selections: [%Absinthe.Blueprint.Document.Field{name: "id", alias: nil, selections: [], arguments: [], argument_data: %{}, directives: [], flags: %{}, errors: [], source_location: %Absinthe.Blueprint.SourceLocation{line: 7, column: 9}, type_conditions: [], schema_node: %Absinthe.Type.Field{identifier: :id, name: "id", description: "The member's ID", type: :id, deprecation: nil, args: %{}, config: nil, triggers: %{}, middleware: [{{Rajska.FieldAuthorization, :call}, [object: %Absinthe.Type.Object{identifier: :member, name: "Member", description: "Represents a member of a group", fields: %{id: %Absinthe.Type.Field{identifier: :id, name: "id", ...}, parent: %Absinthe.Type.Field{identifier: :parent, ...}, role: %Absinthe.Type.Field{...}, ...}, interfaces: [:activity_object], __private__: [__absinthe_referenced__: true, ...], definition: Mobilizon.GraphQL.Schema.Actors.MemberType, ...}, field: :id]}, {Absinthe.Middleware.MapGet, :id}], complexity: nil, default_value: nil, __private__: [], definition: Mobilizon.GraphQL.Schema.Actors.MemberType, __reference__: %{module: Mobilizon.GraphQL.Schema.Actors.MemberType, location: %{line: 15, file: "/home/mobilizon/live/lib/graphql/schema/actors/member.ex"}}}, complexity: nil, parent_type: nil}, %Absinthe.Blueprint.Document.Field{name: "role", alias: nil, selections: [], arguments: [], argument_data: %{}, directives: [], flags: %{}, errors: [], source_location: %Absinthe.Blueprint.SourceLocation{line: 8, column: 9}, type_conditions: [], schema_node: %Absinthe.Type.Field{identifier: :role, name: "role", description: "The role of this membership", type: :member_role_enum, deprecation: nil, args: %{}, con (truncated)
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] Sent 200 in 124ms
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] Sending https://mydomain.tld/member/0036cb3a-7fa5-4bbc-9c33-748725fac833 out via AP
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] POST /api
Jan 25 16:21:23 Arch-Mobilizon mobilizon[2987]: [info] Sent 200 in 9ms

Now I’m not sure if any of that even means anything as I see the same exact logging whether notifications are enabled on the instance or not. This would be my first attempt at using any kind of Elixir based app so I’m just not sure if I’m missing something or the documentation page is.