Skip to content

Commit

Permalink
Add support for Plug (#6)
Browse files Browse the repository at this point in the history
* Add support for Plug

* Update Storex application supervisor children spec

* Add separated test config

* Update GitHub Actions workflow

* Reorganize tests files, directories structure

* Reorganize tests files, directories structure

* Fix Plug handler stop result

* Handlers tests
  • Loading branch information
drozdzynski authored Apr 12, 2024
1 parent 147db84 commit ba8f44a
Show file tree
Hide file tree
Showing 30 changed files with 987 additions and 95 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
with:
otp-version: ${{matrix.otp}}
elixir-version: ${{matrix.elixir}}
- uses: actions/cache@v3
- uses: actions/cache@v4
with:
path: deps
key: ${{ runner.os }}-${{matrix.otp}}-${{matrix.elixir}}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# storex

## 0.3.0

- **[BREAKING]** Rename Cowbow handler module from `Storex.Socket.Handler` to `Storex.Handler.Cowboy`
- Add support for Plug based apps `plug Storex.Plug`
- Update Storex application supervisor children spec

## 0.2.5

- Fix diff of Date struct
- Rewrite tests from Hound to Wallaby

## 0.2.4

- Fix root state update
- Remove optional from jason dependency

## 0.2.3

- Fix reconnect of WebSocket on connection close

## 0.2.2

- Fix reconnect of WebSocket on connection close

## 0.2.1

- Typescript/Javascript improvements

## 0.2.0

- Dynamic registry declaration
- - Default registry on ETS
- Fix issue with a restart of Store when stopped on disconnect
- Update dependencies

## 0.1.0

- The only diff of the store state is being sent on each mutation.
- Subscriber of connection status
- Fixes in library
35 changes: 15 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Add **storex** to deps in `mix.exs`:

```elixir
defp deps do
[{:storex, "~> 0.2.5"}]
[{:storex, "~> 0.3.0"}]
end
```

Expand All @@ -30,33 +30,28 @@ Also you need to add **storex** to `package.json` dependencies:

### Add storex websocket handler

You need to add handler `Storex.Socket.Handler` to cowboy dispatch.
You need to add handler `Storex.Handler.Plug` or `Storex.Handler.Cowboy`.

**Phoenix:**
Example based on [Phoenix guides](https://hexdocs.pm/phoenix/Phoenix.Endpoint.Cowboy2Adapter.html)

```elixir
config :exampleapp, ExampleApp.Endpoint,
http: [
dispatch: [
{:_,
[
{"/storex", Storex.Socket.Handler, []},
{:_, Phoenix.Endpoint.Cowboy2Handler, {ExampleApp.Endpoint, []}}
]}
]
]
defmodule YourAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :your_app

plug Storex.Plug, path: "/storex"

# ...
end
```

**Cowboy:**
```elixir
:cowboy_router.compile([
{:_, [
# ...
{"/storex", Storex.Socket.Handler, []},
# ...
]}
])
{:_, [
# ...
{"/storex", Storex.Socket.Handler, []},
# ...
]}
])
```

### Create store
Expand Down
6 changes: 0 additions & 6 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,3 @@ import Config
config :storex,
session_id_library: Nanoid,
registry: Storex.Registry.ETS

config :wallaby,
otp_app: :storex,
chromedriver: [
# headless: false
]
7 changes: 7 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Config

config :wallaby,
otp_app: :storex,
chromedriver: [
# headless: false
]
7 changes: 3 additions & 4 deletions lib/storex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ defmodule Storex do
import Supervisor.Spec, warn: false

children = [
worker(Storex.Registry.ETS, []),
supervisor(Storex.Supervisor, [])
{Storex.Registry.ETS, []},
{Storex.Supervisor, []}
]

Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
Expand All @@ -16,9 +16,8 @@ defmodule Storex do
@doc """
Mutate store from elixir.
Call mutation callback in store synchronously:
```elixir
Call mutation callback in store synchronously.
Storex.mutate("d9ez7fgkp96", "ExampleApp.Store", "reload", ["user_id"])
```
"""
Expand Down
15 changes: 12 additions & 3 deletions lib/storex/socket/handler.ex → lib/storex/handler/cowboy.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
defmodule Storex.Socket.Handler do
defmodule Storex.Handler.Cowboy do
@moduledoc false

@behaviour :cowboy_websocket

alias Storex.Socket

def init(request, _state) do
Expand Down Expand Up @@ -36,6 +34,7 @@ defmodule Storex.Socket.Handler do
try do
:erlang.binary_to_term(frame)
|> Socket.message_handle(state)
|> map_response()
rescue
ArgumentError -> {:reply, {:close, 1007, "Payload is malformed."}, state}
end
Expand All @@ -46,6 +45,7 @@ defmodule Storex.Socket.Handler do
|> case do
{:ok, message} ->
Socket.message_handle(message, state)
|> map_response()

{:error, _} ->
{:reply, {:close, 1007, "Payload is malformed."}, state}
Expand All @@ -63,9 +63,18 @@ defmodule Storex.Socket.Handler do
}
}
|> Socket.message_handle(state)
|> map_response()
end

def websocket_info(_info, state) do
{:ok, state}
end

defp map_response({:text, message, state}) do
{:reply, {:text, message}, state}
end

defp map_response({:close, code, message, state}) do
{:reply, {:close, code, message}, state}
end
end
67 changes: 67 additions & 0 deletions lib/storex/handler/plug.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
defmodule Storex.Handler.Plug do
@moduledoc false

alias Storex.Socket

def init(_) do
session = Application.get_env(:storex, :session_id_library, Nanoid).generate()
pid = self()

Storex.Registry.register_session(session, pid)

{:ok, %{session: session, pid: pid}}
end

def terminate(_reason, _req, %{session: session}) do
Storex.Registry.session_stores(session)
|> Enum.each(fn {session, store, _} ->
Storex.Supervisor.remove_store(session, store)
end)

Storex.Registry.unregister_session(session)

:ok
end

def terminate(_, _) do
:ok
end

def handle_in({message, [opcode: :text]}, state) do
Jason.decode(message, keys: :atoms)
|> case do
{:ok, message} ->
Socket.message_handle(message, state)
|> map_response()

{:error, _} ->
{:stop, "Payload is malformed.", 1007, state}
end
end

def handle_info({:mutate, store, mutation, data}, %{session: session} = state) do
%{
type: "mutation",
session: session,
store: store,
data: %{
data: data,
name: mutation
}
}
|> Socket.message_handle(state)
|> map_response()
end

def handle_info(_info, state) do
{:ok, state}
end

defp map_response({:text, message, state}) do
{:push, {:text, message}, state}
end

defp map_response({:close, code, message, state}) do
{:stop, :normal, {code, message}, state}
end
end
42 changes: 42 additions & 0 deletions lib/storex/plug.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
defmodule Storex.Plug do
import Plug.Conn

@moduledoc """
Add Storex to your Plug application, to handle WebSocket connections.
Example for Phoenix Endpoint:
```elixir
defmodule YourAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :your_app
plug Storex.Plug
# ...
end
```
## Options
- `:path` - The path to mount the Storex handler. Default is `"/storex"`.
"""

@doc false
def init(options \\ []) do
[
path: Keyword.get(options, :path, "/storex")
]
end

@doc false
def call(%{method: "GET", request_path: path} = conn, path: path) do
conn
|> WebSockAdapter.upgrade(Storex.Handler.Plug, [], timeout: 60_000)
|> halt()
end

@doc false
def call(conn, _) do
conn
end
end
2 changes: 1 addition & 1 deletion lib/storex/registries/ets.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule Storex.Registry.ETS do
@registry :storex_registry

@doc false
def start_link do
def start_link(_) do
GenServer.start_link(__MODULE__, nil, name: @registry)
end

Expand Down
11 changes: 5 additions & 6 deletions lib/storex/socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ defmodule Storex.Socket do
Map.put(message, :type, "pong")
|> Jason.encode!()

{:reply, {:text, message}, state}
{:text, message, state}
end

def message_handle(%{store: nil}, state) do
{:reply, {:close, 4000, "Store is not set."}, state}
{:close, 4000, "Store is not set.", state}
end

def message_handle(%{type: "join"} = message, state) do
Expand All @@ -35,11 +35,10 @@ defmodule Storex.Socket do
|> Map.put(:session, state.session)
|> Jason.encode!()

{:reply, {:text, message}, state}
{:text, message, state}

_ ->
{:reply, {:close, 4001, "Store '#{message.store}' is not defined or can't be compiled."},
state}
{:close, 4001, "Store '#{message.store}' is not defined or can't be compiled.", state}
end
end

Expand Down Expand Up @@ -80,7 +79,7 @@ defmodule Storex.Socket do
}
end
|> Jason.encode!()
|> (&{:reply, {:text, &1}, state}).()
|> (&{:text, &1, state}).()
end

defp safe_concat(store) do
Expand Down
2 changes: 1 addition & 1 deletion lib/storex/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Storex.Supervisor do

use DynamicSupervisor

def start_link() do
def start_link(_) do
DynamicSupervisor.start_link(__MODULE__, [], name: __MODULE__)
end

Expand Down
12 changes: 8 additions & 4 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ defmodule Storex.MixProject do
]
end

defp elixirc_paths(:test), do: ["lib", "test/browser_test", "test/stores"]
defp elixirc_paths(:test), do: ["lib", "test/fixtures", "test/storex", "test/support"]
defp elixirc_paths(_), do: ["lib"]

def application do
Expand All @@ -42,7 +42,8 @@ defmodule Storex.MixProject do

defp deps do
[
{:cowboy, "~> 2.9"},
{:websock_adapter, "~> 0.5.6"},
{:plug, "~> 1.15"},
{:nanoid, "~> 2.0"},
{:jason, "~> 1.4"},

Expand All @@ -51,8 +52,11 @@ defmodule Storex.MixProject do
{:dialyxir, "~> 1.4", only: [:dev], runtime: false},

# Tests
{:ssl_verify_fun, "~> 1.1", manager: :rebar3, override: true},
{:wallaby, "~> 0.30.0", runtime: false, only: :test}
{:ssl_verify_fun, "~> 1.1", only: :test, manager: :rebar3, override: true},
{:wallaby, "~> 0.30.0", runtime: false, only: :test},
{:cowboy, "~> 2.9", only: :test},
{:bandit, "~> 1.4", only: :test},
{:plug_cowboy, "~> 2.0", only: :test}
]
end

Expand Down
Loading

0 comments on commit ba8f44a

Please sign in to comment.