How to manage notifications

I am trying to understand how to listen to notifications emitted from my resource actions and pass it down to a React client using Channels. Currently this is what I've got:
# note.ex (resource)
...

pub_sub do
module NotedWeb.Endpoint

publish :create_note, "workspace"
end
# note.ex (resource)
...

pub_sub do
module NotedWeb.Endpoint

publish :create_note, "workspace"
end
# user_socket.ex
defmodule NotedWeb.UserSocket do
use Phoenix.Socket

channel "workspace", NotedWeb.WorkspaceChannel

@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
end
# user_socket.ex
defmodule NotedWeb.UserSocket do
use Phoenix.Socket

channel "workspace", NotedWeb.WorkspaceChannel

@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
end
# workspace_channel.ex
defmodule NotedWeb.WorkspaceChannel do
use NotedWeb, :channel

@impl true
def join("workspace", _payload, socket) do
{:ok, socket}
end
end
# workspace_channel.ex
defmodule NotedWeb.WorkspaceChannel do
use NotedWeb, :channel

@impl true
def join("workspace", _payload, socket) do
{:ok, socket}
end
end
I am able to successfully join to my channel from my React client. But I get this error when a new event is triggered from my resource:
[error] ** (Ash.Error.Unknown)
Bread Crumbs:
> Exception raised in: Noted.Workspace.Note.create_note

Unknown Error

* ** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for type Ash.Notifier.Notification (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

@derive {Jason.Encoder, only: [....]}
defstruct ...
[error] ** (Ash.Error.Unknown)
Bread Crumbs:
> Exception raised in: Noted.Workspace.Note.create_note

Unknown Error

* ** (Protocol.UndefinedError) protocol Jason.Encoder not implemented for type Ash.Notifier.Notification (a struct), Jason.Encoder protocol must always be explicitly implemented.

If you own the struct, you can derive the implementation specifying which fields should be encoded to JSON:

@derive {Jason.Encoder, only: [....]}
defstruct ...
The error is gone when I transform the notification
pub_sub do
module NotedWeb.Endpoint

transform fn notification ->
serialized_note = NotedWeb.Serializers.serialize_listed_note(notification.data)

%{note: serialized_note}
end

publish :create_note, "workspace"
end
pub_sub do
module NotedWeb.Endpoint

transform fn notification ->
serialized_note = NotedWeb.Serializers.serialize_listed_note(notification.data)

%{note: serialized_note}
end

publish :create_note, "workspace"
end
But I am still unsure of what is going on in here. How is the notification being sent to the client? I tried to inspect on the handle_out/2 callback:
# workspace_channel.ex
@impl true
def handle_out("create_note" = event, payload, socket) do
IO.inspect(event)
IO.inspect(payload)

{:noreply, socket}
end
# workspace_channel.ex
@impl true
def handle_out("create_note" = event, payload, socket) do
IO.inspect(event)
IO.inspect(payload)

{:noreply, socket}
end
But nothing seems to be happening there
Solution:
Okay, figured it out thanks to the realworld-phoenix-inertia-react project. Turns out I shouldn't have created a channel to connect from my client with the same topic the resources emit the events to. They should be different — that's what was causing the weird behavior and the "ghost" and uncontrolled sending of data through my channel. Now my action is publishing to the topic notes instead of workspace...
Jump to solution
4 Replies
ZachDaniel
ZachDaniel5mo ago
Hm...try not matching on a specific event name to get all outgoing messages? But the way you're doing it is generally right, to encore it in some way either in your channel or in the resource As for how it's getting to the channel that is mostly Phoenixs domain. We just call YourMod.broadcast(...)
Joan Gavelán
Joan GavelánOP5mo ago
Did it, and didn't work until I subscribed in the join callback
@impl true
def join("workspace", _payload, socket) do
NotedWeb.Endpoint.subscribe("workspace") # added this

{:ok, socket}
end
@impl true
def join("workspace", _payload, socket) do
NotedWeb.Endpoint.subscribe("workspace") # added this

{:ok, socket}
end
Now I am able to see the transformed notification printed to the terminal by the handle_out/3 callback
@impl true
def handle_out(event, payload, socket) do
IO.puts("Handle out")
IO.inspect(event, label: "Broadcast event")
IO.inspect(payload, label: "Broadcast payload")

{:noreply, socket}
end
@impl true
def handle_out(event, payload, socket) do
IO.puts("Handle out")
IO.inspect(event, label: "Broadcast event")
IO.inspect(payload, label: "Broadcast payload")

{:noreply, socket}
end
Handle out
[info] Sent 302 in 25ms
Broadcast event: "create_note"
Broadcast payload: %{
note: %{
id: "01973ddb-c506-7c2b-a368-5909dc690351",
title: "Note",
author: "Joan Gavelán",
content: "note",
can_update: true,
can_destroy: true
}
Handle out
[info] Sent 302 in 25ms
Broadcast event: "create_note"
Broadcast payload: %{
note: %{
id: "01973ddb-c506-7c2b-a368-5909dc690351",
title: "Note",
author: "Joan Gavelán",
content: "note",
can_update: true,
can_destroy: true
}
Still, this is very strange, this feels like it is just working, but why was it throwing a serialization error before, like it was being sent to the client without explicitly subscribing?
ZachDaniel
ZachDaniel5mo ago
The fact that it was failing in create_note ( the bread crumbs in the error) makes me think that it wasn't in any kind of receive or anything. Maybe a red herring? If it was happening in the client or in the channel/socket it wouldn't have any idea of what action triggered it
Solution
Joan Gavelán
Joan Gavelán5mo ago
Okay, figured it out thanks to the realworld-phoenix-inertia-react project. Turns out I shouldn't have created a channel to connect from my client with the same topic the resources emit the events to. They should be different — that's what was causing the weird behavior and the "ghost" and uncontrolled sending of data through my channel. Now my action is publishing to the topic notes instead of workspace
# note.ex
publish :create_note, "notes"
# note.ex
publish :create_note, "notes"
I am subscribing to that topic from within the join/3 channel callback
# workspace_channel.ex
@impl true
def join("workspace", _payload, socket) do
NotedWeb.Endpoint.subscribe("notes")

{:ok, socket}
end
# workspace_channel.ex
@impl true
def join("workspace", _payload, socket) do
NotedWeb.Endpoint.subscribe("notes")

{:ok, socket}
end
And listening for events in handle_info/2 callbacks
# workspace_channel.ex
@impl true
def handle_info(%{topic: "notes", event: "create_note"} = message, socket) do
push(socket, "note_created", message.payload.note)
{:noreply, socket}
end
# workspace_channel.ex
@impl true
def handle_info(%{topic: "notes", event: "create_note"} = message, socket) do
push(socket, "note_created", message.payload.note)
{:noreply, socket}
end
This works!

Did you find this page helpful?