Rendering shared avatars with privacy
If you’re just tuning in, I’ve been making a privacy-focused alternative to big tech social media (e.g. Facebook/Meta).
It’s been awhile since I’ve posted, Metamorphic has gone through several re-designs and evolutions as I seek to get things ready for our beta MMP.
So I’m excited to share a little snippet of how we render avatars in the app for people while preserving the privacy, security, and performance. As a solo developer, I do not want to imagine doing this without the help of Elixir and Phoenix (and LiveView).
Everyone’s avatars are asymmetrically encrypted in Metamorphic, so when someone decides to share an avatar with someone they have to do so in a similar fashion to public-key cryptography.
This is great for security, but the question quickly became: how do we handle the multiple object storage calls, decryption calls, and temporary storage of these shared avatars?
Our current solution
Thanks to the
enacl Elixir library, which relies on the incredible
libsodiumlibary, encryption and decryption is wicked fast and we don’t have to worry about too much there (at least for the time being).
I noticed that the real bottleneck was the requests to object storage and back. So, the first thing was to offload those requests into an asynchronous task which enables us to load up the page without waiting for the avatars to come in.
As the avatars come in we store the encrypted blob (it’s an encrypted blob that’s stored in object storage) in
ets which is an in-memory storage for Erlang (and thus Elixir). Then, each person is pulling each shared avatar out of
ets and decrypting it on the fly.
Here’s a gif to see how it works:
As you can see from the gif above, I wanted people to be able to drop into their People page without any delay from waiting for the avatars to load from object storage, and I also didn’t want the loading avatars to require a page refresh (or re-mount of the socket) that would interrupt what a person might be doing on the page.
To sum, this is what I wanted to achieve:
- quick page load
- no page interruptions
- preserve privacy and security
This is achieved by a helper function and a
handle_info function to handle the
Task.async/1 returns from the helper function:
handle_info/2 is only invoked when someone has signed back into their account and currently does not have any shared avatars loaded into their
Once the initial object storage calls are made, the shared avatars are simply pulled from
ets and decrypted on the fly.
Here is the helper function that we call with our avatar component in our page’s HTML:
We also handle other invalid arguments passed to
get_shared_avatar/3 and return
"”. And inside our
This enables showing the spinner if a particular avatar is loading (sometimes it’s so quick you don’t see the spinner). It’s also taking advantage of LiveView 0.18 with the new
:if syntax directly in the markup (inspired by Tailwind).
.spinner components are from the Petal Components library which is a great tool for any developer working with Elixir and Phoenix.
We also use the
index to determine whether to display a spinner or display the avatar (this makes it so that it doesn’t reload all the avatars every time a new asynchronous message comes through).
And with that we’ve essentially enabled a pretty seamless experience for people when sharing their avatars without sacrificing the security, privacy, or performance.
Let me know if you have questions or feedback, I began my work with Elixir as I began Metamorphic (learning in public) and I’m always re-applying what I learn to continually improve and evolve Metamorphic.
Also, if you haven’t signed up, sign up now for invite codes to our beta when it launches!