Resources
--
-
- - Guides & Docs - -
- - Source - -
- - v1.6 Changelog - -
diff --git a/assets/js/app.js b/assets/js/app.js index 2ca06a5..c143f98 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -25,6 +25,7 @@ import "phoenix_html" import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" import topbar from "../vendor/topbar" +import socket from "./user_socket.js" let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, {params: {_csrf_token: csrfToken}}) diff --git a/assets/js/user_socket.js b/assets/js/user_socket.js new file mode 100644 index 0000000..3e0e94b --- /dev/null +++ b/assets/js/user_socket.js @@ -0,0 +1,85 @@ +// NOTE: The contents of this file will only be executed if +// you uncomment its entry in "assets/js/app.js". + +// Bring in Phoenix channels client library: +import {Socket} from "phoenix" + +// And connect to the path in "lib/chat_web/endpoint.ex". We pass the +// token for authentication. Read below how it should be used. +let socket = new Socket("/socket", {params: {token: window.userToken}}) + +// When you connect, you'll often need to authenticate the client. +// For example, imagine you have an authentication plug, `MyAuth`, +// which authenticates the session and assigns a `:current_user`. +// If the current user exists you can assign the user's token in +// the connection for use in the layout. +// +// In your "lib/chat_web/router.ex": +// +// pipeline :browser do +// ... +// plug MyAuth +// plug :put_user_token +// end +// +// defp put_user_token(conn, _) do +// if current_user = conn.assigns[:current_user] do +// token = Phoenix.Token.sign(conn, "user socket", current_user.id) +// assign(conn, :user_token, token) +// else +// conn +// end +// end +// +// Now you need to pass this token to JavaScript. You can do so +// inside a script tag in "lib/chat_web/templates/layout/app.html.heex": +// +// +// +// You will need to verify the user token in the "connect/3" function +// in "lib/chat_web/channels/user_socket.ex": +// +// def connect(%{"token" => token}, socket, _connect_info) do +// # max_age: 1209600 is equivalent to two weeks in seconds +// case Phoenix.Token.verify(socket, "user socket", token, max_age: 1_209_600) do +// {:ok, user_id} -> +// {:ok, assign(socket, :user, user_id)} +// +// {:error, reason} -> +// :error +// end +// end +// +// Finally, connect to the socket: +socket.connect() + +let channel = socket.channel('room:lobby', {}); // connect to chat "room" + +channel.on('shout', function (payload) { // listen to the 'shout' event + let li = document.createElement("li"); // create new list item DOM element + let name = payload.name || 'guest'; // get name from payload or set default + li.innerHTML = '' + name + ': ' + payload.message; // set li contents + ul.appendChild(li); // append to list +}); + +channel.join() + .receive("ok", resp => { console.log("Joined successfully", resp) }) + .receive("error", resp => { console.log("Unable to join", resp) }); + + +let ul = document.getElementById('msg-list'); // list of messages. +let name = document.getElementById('name'); // name of message sender +let msg = document.getElementById('msg'); // message input field + +// "listen" for the [Enter] keypress event to send a message: +msg.addEventListener('keypress', function (event) { + if (event.keyCode == 13 && msg.value.length > 0) { // don't sent empty msg. + channel.push('shout', { // send the message to the server on "shout" channel + name: name.value || "guest", // get value of "name" of person sending the message. Set guest as default + message: msg.value // get message text (value) from msg input field. + }); + msg.value = ''; // reset the message input field for next message. + } +}); + +export default socket diff --git a/lib/chat_web/channels/room_channel.ex b/lib/chat_web/channels/room_channel.ex new file mode 100644 index 0000000..99dfc7b --- /dev/null +++ b/lib/chat_web/channels/room_channel.ex @@ -0,0 +1,32 @@ +defmodule ChatWeb.RoomChannel do + use ChatWeb, :channel + + @impl true + def join("room:lobby", payload, socket) do + if authorized?(payload) do + {:ok, socket} + else + {:error, %{reason: "unauthorized"}} + end + end + + # Channels can be used in a request/response fashion + # by sending replies to requests from the client + @impl true + def handle_in("ping", payload, socket) do + {:reply, {:ok, payload}, socket} + end + + # It is also common to receive messages from the client and + # broadcast to everyone in the current topic (room:lobby). + @impl true + def handle_in("shout", payload, socket) do + broadcast(socket, "shout", payload) + {:noreply, socket} + end + + # Add authorization logic here as required. + defp authorized?(_payload) do + true + end +end diff --git a/lib/chat_web/channels/user_socket.ex b/lib/chat_web/channels/user_socket.ex new file mode 100644 index 0000000..58e9390 --- /dev/null +++ b/lib/chat_web/channels/user_socket.ex @@ -0,0 +1,41 @@ +defmodule ChatWeb.UserSocket do + use Phoenix.Socket + + # A Socket handler + # + # It's possible to control the websocket connection and + # assign values that can be accessed by your channel topics. + + ## Channels + + channel "room:lobby", ChatWeb.RoomChannel + + # Socket params are passed from the client and can + # be used to verify and authenticate a user. After + # verification, you can put default assigns into + # the socket that will be set for all channels, ie + # + # {:ok, assign(socket, :user_id, verified_user_id)} + # + # To deny connection, return `:error`. + # + # See `Phoenix.Token` documentation for examples in + # performing token verification on connect. + @impl true + def connect(_params, socket, _connect_info) do + {:ok, socket} + end + + # Socket id's are topics that allow you to identify all sockets for a given user: + # + # def id(socket), do: "user_socket:#{socket.assigns.user_id}" + # + # Would allow you to broadcast a "disconnect" event and terminate + # all active sockets and channels for a given user: + # + # Elixir.ChatWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) + # + # Returning `nil` makes this socket anonymous. + @impl true + def id(_socket), do: nil +end diff --git a/lib/chat_web/endpoint.ex b/lib/chat_web/endpoint.ex index b81eaac..2cf1bb1 100644 --- a/lib/chat_web/endpoint.ex +++ b/lib/chat_web/endpoint.ex @@ -12,6 +12,10 @@ defmodule ChatWeb.Endpoint do socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] + socket "/socket", ChatWeb.UserSocket, + websocket: true, + longpoll: false + # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phx.digest diff --git a/lib/chat_web/templates/layout/root.html.heex b/lib/chat_web/templates/layout/root.html.heex index bc9ad36..2956ae5 100644 --- a/lib/chat_web/templates/layout/root.html.heex +++ b/lib/chat_web/templates/layout/root.html.heex @@ -12,17 +12,9 @@
Peace of mind from prototype to production
-