basic chat stuff
This commit is contained in:
parent
d17c40c033
commit
be957afe44
|
@ -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}})
|
||||
|
|
|
@ -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":
|
||||
//
|
||||
// <script>window.userToken = "<%= assigns[:user_token] %>";</script>
|
||||
//
|
||||
// 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 = '<b>' + name + '</b>: ' + 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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -12,17 +12,9 @@
|
|||
<body>
|
||||
<header>
|
||||
<section class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
|
||||
<%= if function_exported?(Routes, :live_dashboard_path, 2) do %>
|
||||
<li><%= link "LiveDashboard", to: Routes.live_dashboard_path(@conn, :home) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<nav role="navigation">
|
||||
<h1 style="padding-top: 15px">Chat Example</h1>
|
||||
</nav>
|
||||
<a href="https://phoenixframework.org/" class="phx-logo">
|
||||
<img src={Routes.static_path(@conn, "/images/phoenix.png")} alt="Phoenix Framework Logo"/>
|
||||
</a>
|
||||
</section>
|
||||
</header>
|
||||
<%= @inner_content %>
|
||||
|
|
|
@ -1,41 +1,13 @@
|
|||
<section class="phx-hero">
|
||||
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
|
||||
<p>Peace of mind from prototype to production</p>
|
||||
</section>
|
||||
<!-- The list of messages will appear here: -->
|
||||
<ul id="msg-list" style="list-style: none; min-height:200px;">
|
||||
</ul>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-3" style="width: 20%; margin-left: 0;">
|
||||
<input type="text" id="name" class="form-control" placeholder="Your Name" style="border: 1px black solid; font-size: 1.3em;" autofocus>
|
||||
</div>
|
||||
<div class="col-xs-9" style="width: 100%; margin-left: 1%; ">
|
||||
<input type="text" id="msg" class="form-control" placeholder="Your Message" style="border: 1px black solid; font-size: 1.3em;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="row">
|
||||
<article class="column">
|
||||
<h2>Resources</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://hexdocs.pm/phoenix/overview.html">Guides & Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/phoenixframework/phoenix">Source</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://github.com/phoenixframework/phoenix/blob/v1.6/CHANGELOG.md">v1.6 Changelog</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="column">
|
||||
<h2>Help</h2>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://web.libera.chat/#elixir">#elixir on Libera Chat (IRC)</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://elixir-slackin.herokuapp.com/">Elixir on Slack</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://discord.gg/elixir">Elixir on Discord</a>
|
||||
</li>
|
||||
</ul>
|
||||
</article>
|
||||
</section>
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
defmodule ChatWeb.RoomChannelTest do
|
||||
use ChatWeb.ChannelCase
|
||||
|
||||
setup do
|
||||
{:ok, _, socket} =
|
||||
ChatWeb.UserSocket
|
||||
|> socket("user_id", %{some: :assign})
|
||||
|> subscribe_and_join(ChatWeb.RoomChannel, "room:lobby")
|
||||
|
||||
%{socket: socket}
|
||||
end
|
||||
|
||||
test "ping replies with status ok", %{socket: socket} do
|
||||
ref = push(socket, "ping", %{"hello" => "there"})
|
||||
assert_reply ref, :ok, %{"hello" => "there"}
|
||||
end
|
||||
|
||||
test "shout broadcasts to room:lobby", %{socket: socket} do
|
||||
push(socket, "shout", %{"hello" => "all"})
|
||||
assert_broadcast "shout", %{"hello" => "all"}
|
||||
end
|
||||
|
||||
test "broadcasts are pushed to the client", %{socket: socket} do
|
||||
broadcast_from!(socket, "broadcast", %{"some" => "data"})
|
||||
assert_push "broadcast", %{"some" => "data"}
|
||||
end
|
||||
end
|
|
@ -3,6 +3,6 @@ defmodule ChatWeb.PageControllerTest do
|
|||
|
||||
test "GET /", %{conn: conn} do
|
||||
conn = get(conn, "/")
|
||||
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
|
||||
assert html_response(conn, 200) =~ "Chat Example"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
defmodule ChatWeb.ChannelCase do
|
||||
@moduledoc """
|
||||
This module defines the test case to be used by
|
||||
channel tests.
|
||||
|
||||
Such tests rely on `Phoenix.ChannelTest` and also
|
||||
import other functionality to make it easier
|
||||
to build common data structures and query the data layer.
|
||||
|
||||
Finally, if the test case interacts with the database,
|
||||
we enable the SQL sandbox, so changes done to the database
|
||||
are reverted at the end of every test. If you are using
|
||||
PostgreSQL, you can even run database tests asynchronously
|
||||
by setting `use ChatWeb.ChannelCase, async: true`, although
|
||||
this option is not recommended for other databases.
|
||||
"""
|
||||
|
||||
use ExUnit.CaseTemplate
|
||||
|
||||
using do
|
||||
quote do
|
||||
# Import conveniences for testing with channels
|
||||
import Phoenix.ChannelTest
|
||||
import ChatWeb.ChannelCase
|
||||
|
||||
# The default endpoint for testing
|
||||
@endpoint ChatWeb.Endpoint
|
||||
end
|
||||
end
|
||||
|
||||
setup _tags do
|
||||
:ok
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue