OvermindDL1

OvermindDL1

`elm-jsphoenix` release

Due to not being able to use elm-phoenix-socket for a few reasons including that it owns the websocket (can only have one, meaning legacy javascript cannot touch it, which does not work for me), heartbeat is not setup properly (I think that is now fixed, but it was a large issue at the time), and reconnection does not really work at all (elm-websocket’s fault I hear), I made my own library that lets Elm use Phoenix channels via the javascript interface, thus other javascript libraries that use the websocket can still work, it will have no heartbeat or reconnection issues, and I think I made a decent interface (although could probably still use some more helpers to ease some of the large connection structs and such). I am still very much in the phase of learning Elm so the code may not be Elm’ish, but it works well in my work-test-project.

The Github URL is at: GitHub - OvermindDL1/elm-jsphoenix: Elm Port wrapper around Phoenix Socket/Presence javascript library to allow compatibility with existing javascript websocket usage · GitHub

And the initial README.md of the project is the remainder of this post:

elm-jsphoenix

Elm Port wrapper around Phoenix Socket/Presence javascript library to allow compatibility with existing javascript websocket usage

Installation

Include the JSPhoenix.elm module however you wish.

Add the javascript file to your Phoenix brunch (or whatever you’ve replaced it with) build system to include in your Phoenix project.

In the javascript for your Phoenix project any Elm app that you instance just call the setup_phoenix_socket_ports from the setup_phoenix_socket_ports javascript module, such as by:

import socket from './socket'
import Elm from './elm' // Assuming your Elm mains compile to a ./elm.js in the same directory as your app.js
import setup_phoenix_socket_ports from 'setup_phoenix_socket_ports' // Or from where-ever


let socket_setup_once = false
socket.onOpen(() => {
  $(document).ready(function(){
    if(socket_setup_once) return
    socket_setup_once = true

    // Here setup all your usual javascript that also needs to use the websocket

    let app = Elm.Main.embed(document.querySelector('#my-elm-container'))
    setup_phoenix_socket_ports(app, socket)
  }
}

Even if you do not have any other javascript code using the socket this still ensures full compatibility of your Elm app with the Phoenix socket interface, heartbeats, recovery, and all.

Usage

The bindings are not absolutely complete to the javascript code, but rather a useful interface is exposed to Elm within its type system.

This is a brand new project and the documentation is not entirely complete, however the code is short and should be readable.

General usage is as follows

Connect to a channel, specify presence sync ports and some message ports and how to use:

import JSPhoenix exposing (ChannelEventMsg, ChanExitCB)

-- Currently this next line does not work due to Elm bugs...
--type alias RoomSyncState = List (JSPhoenix.PresenceObject {} {online_at : JSPhoenix.TimexDateTime, nick : String})
-- So instead you have to do this mess for each of your presence sync object types because of Elm port bugs:
type alias RoomSyncMeta =
  { phx_ref : String -- Until Elm bug is fixed, phx_ref should 'generally' always be here
  , loc : String
  , online_at : JSPhoenix.TimexDateTime
  , nick : String
  }

type alias RoomSyncState =
  List ( String, { metas : List RoomSyncMeta } ) -- Until Elm bug is fixed 'metas' must *always* be here with a List of records


-- Same bug issue as above, you should be able to do:
--type alias RoomSyncEvent = JSPhoenix.PresenceObject {} {online_at : JSPhoenix.TimexDateTime, nick : String}
-- Instead you have to do this mess for each presence sync event type
type alias RoomSyncEvent =
  { id : String
  , old : Maybe { String, List RoomSyncMeta }
  , new : List ( String, List RoomSyncMeta )
  }

-- Custom messages, whatever fits your Phoenix app:
type alias RoomMessage =
  { room_id : Int
  , uid : Int
  , inserted_at : JSPhoenix.TimexDateTime
  , updated_at : JSPhoenix.TimexDateTime
  , nick : String
  , msg : String
  }

type alias RoomMessages =
  { msgs : List RoomMessage }

port onRoomConnect : (ChannelEventMsg {} Int -> msg) -> Sub msg
port onRoomInfo : (ChannelEventMsg {} Int -> msg) -> Sub msg
port onRoomMsgsInit : (ChannelEventMsg RoomMessages Int -> msg) -> Sub msg
port onRoomMsgsAdd : (ChannelEventMsg RoomMessages Int -> msg) -> Sub msg
port onRoomSyncState : (ChannelEventMsg RoomSyncState Int -> msg) -> Sub msg
port onRoomSyncJoin : (ChannelEventMsg RoomSyncEvent Int -> msg) -> Sub msg
port onRoomSyncLeave : (ChannelEventMsg RoomSyncEvent Int -> msg) -> Sub msg

connect_room rid =
  JSPhoenix.connect
      { topic = room_id_to_topic rid
      , timeout_ms = Nothing -- Just 10000 -- Default value is 10000 if Nothing is used
      , chanCloseCB = Nothing
      , chanErrorCB = Nothing
      , syncState = Just { portName = "onRoomSyncState", cb_data = (int rid) }
      , syncJoin = Just { portName = "onRoomSyncJoin", cb_data = (int rid) }
      , syncLeave = Just { portName = "onRoomSyncLeave", cb_data = (int rid) }
      , joinData = null
      , joinEvents =
          [ { portName = "onRoomConnect", msgID = "ok", cb_data = (int rid) }
          ]
      , onPorts =
          [ { portName = "onRoomInfo", msgID = "room:info", cb_data = (int rid) }
          , { portName = "onRoomMsgsInit", msgID = "msgs:init", cb_data = (int rid) }
          , { portName = "onRoomMsgsAdd", msgID = "msgs:add", cb_data = (int rid) }
          ]
      }

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
  case msg of
    -- ... other messages

    MyConnectMessage rid ->
      ( model
      , connect_room rid -- You can use the JSPhoenix.connect like any normal command
      )

subscriptions : Model -> Sub Msg
subscriptions model =
  Sub.batch -- Subscribe to your port events to get their messages
    [ onRoomConnect (\{ msg, cb_data } -> MyRoomConnectMsg msg cb_data) -- Example to show you the structure of the data
    , onRoomInfo MyRoomInfoMsg
    , onRoomMsgsInit MyRoomMsgsInitMsg
    , onRoomMsgsAdd MyRoomMsgsAddMsg
    , onRoomSyncState MyRoomSyncStateMsg
    , onRoomSyncJoin MyRoomSyncJoinMsg
    , onRoomSyncLeave MyRoomSyncLeaveMsg
    ]

Where Next?

Popular in Discussions Top

blackode
Elixir Upgrading is so Simple in Ubuntu and It worked for me Ubuntu 16.04 git clone https://github.com/elixir-lang/elixir.git cd elixir...
New
griffinbyatt
Sobelow Sobelow is a security-focused static analysis tool for the Phoenix framework. For security researchers, it is a useful tool for g...
New
crabonature
I’m still quite new to Elixir. As I understand we got in Elixir “multi guards” as convention to simplify one large guard with or’s?: de...
New
AstonJ
If so I (and hopefully others!) might have some tips for you :slight_smile: But first, please say which area you’re finding most challen...
New
jeramyRR
This is an interesting article to read. Elixir’s performance, like usual, is excellent. However, it seems like the high CPU usage is co...
New
fireproofsocks
This is more of a general question, but I’m wondering how other people in the community think about the pattern matching in function sign...
New
opsb
We’re considering our architecture from a viewpoint of scaling our traffic heavily over the next 6 months. Our current deployment is runn...
New
Rustixir
Hi everyone, im working on find best language/framework/system for high concurrency, high performance and stable performance after wor...
New
laiboonh
Hi all, I am trying to convince my team to use liveview over the current react. What are some of the points where one should consider us...
New
jswny
I would like to better understand what the advantages/disadvantages of umbrella applications are compared to structuring your app as as s...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New

We're in Beta

About us Mission Statement