-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 717d3f4
Showing
12 changed files
with
910 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# Used by "mix format" | ||
[ | ||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# The directory Mix will write compiled artifacts to. | ||
/_build/ | ||
|
||
# If you run "mix test --cover", coverage assets end up here. | ||
/cover/ | ||
|
||
# The directory Mix downloads your dependencies sources to. | ||
/deps/ | ||
|
||
# Where third-party dependencies like ExDoc output generated docs. | ||
/doc/ | ||
|
||
# Ignore .fetch files in case you like to edit your project deps locally. | ||
/.fetch | ||
|
||
# If the VM crashes, it generates a dump, let's ignore it too. | ||
erl_crash.dump | ||
|
||
# Also ignore archive artifacts (built via "mix archive.build"). | ||
*.ez | ||
|
||
# Ignore package tarball (built via "mix hex.build"). | ||
minch-*.tar | ||
|
||
# Temporary files, for example, from tests. | ||
/tmp/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Minch | ||
|
||
A WebSocket client build around [`Mint.WebSocket`](https://github.com/elixir-mint/mint_web_socket). | ||
|
||
## Installation | ||
|
||
The package can be installed by adding `minch` to your list of dependencies in `mix.exs`: | ||
|
||
```elixir | ||
def deps do | ||
[ | ||
{:minch, "~> 0.1.0"} | ||
] | ||
end | ||
``` | ||
|
||
<!-- @moduledoc --> | ||
|
||
## Usage | ||
|
||
### Supervised client | ||
|
||
```elixir | ||
defmodule EchoClient do | ||
use MintSocket | ||
|
||
require Logger | ||
|
||
@impl true | ||
def init(_init_arg) do | ||
{:ok, %{connected?: false}} | ||
end | ||
|
||
@impl true | ||
def connect(_state) do | ||
url = "wss://ws.postman-echo.com/raw" | ||
headers = [{"authorization", "bearer: example"}] | ||
# don't do this in production | ||
options = [transport_opts: [{:verify, :verify_none}]] | ||
{url, headers, options} | ||
end | ||
|
||
@impl true | ||
def handle_connect(state) do | ||
Logger.info("connected") | ||
Process.send_after(self(), :produce, 5000) | ||
{:reply, {:text, "welcome"}, %{state | connected?: true}} | ||
end | ||
|
||
@impl true | ||
def handle_disconnect(reason, state) do | ||
Logger.warning("disconnected: #{inspect(reason)}") | ||
{:reconnect, 1000, %{state | connected?: false}} | ||
end | ||
|
||
@impl true | ||
def handle_info(:produce, state) do | ||
Process.send_after(self(), :produce, 5000) | ||
{:reply, {:text, DateTime.utc_now() |> DateTime.to_iso8601()}, state} | ||
end | ||
|
||
@impl true | ||
def handle_frame(frame, state) do | ||
Logger.info(inspect(frame)) | ||
{:ok, state} | ||
end | ||
end | ||
``` | ||
|
||
### Simple client | ||
|
||
```elixir | ||
url = "wss://ws.postman-echo.com/raw" | ||
headers = [] | ||
# don't do this in production | ||
options = [transport_opts: [{:verify, :verify_none}]] | ||
|
||
IO.puts("checking ping to #{url}...") | ||
|
||
case Minch.connect(url, headers, options) do | ||
{:ok, pid, ref} -> | ||
Minch.send_frame(pid, {:text, to_string(System.monotonic_time())}) | ||
|
||
case Minch.await_frame(ref) do | ||
{:text, start} -> | ||
ping = | ||
System.convert_time_unit( | ||
System.monotonic_time() - String.to_integer(start), | ||
:native, | ||
:millisecond | ||
) | ||
|
||
IO.puts("#{ping}ms") | ||
|
||
:timeout -> | ||
IO.puts("timeout") | ||
end | ||
|
||
Minch.close(pid) | ||
|
||
{:error, error} -> | ||
IO.puts("connection error: #{inspect(error)}") | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
defmodule Minch do | ||
@external_resource "README.md" | ||
@moduledoc """ | ||
A WebSocket client build around `Mint.WebSocket`. | ||
""" | ||
@moduledoc @moduledoc <> | ||
(File.read!(@external_resource) | ||
|> String.split("<!-- @moduledoc -->") | ||
|> List.last()) | ||
|
||
@type client :: GenServer.server() | ||
|
||
@callback init(init_arg :: term()) :: {:ok, state :: term()} | ||
|
||
@callback connect(state :: term()) :: | ||
url | ||
| {url, headers} | ||
| {url, headers, options} | ||
when url: String.t() | URI.t(), headers: Mint.Types.headers(), options: Keyword.t() | ||
|
||
@callback handle_info(msg :: :timeout | term(), state :: term()) :: | ||
{:noreply, new_state} | ||
| {:reply, frame :: Mint.WebSocket.frame(), new_state} | ||
| {:reconnect, new_state} | ||
when new_state: term() | ||
|
||
@callback handle_connect(state :: term()) :: | ||
{:ok, new_state} | ||
| {:reply, frame :: Mint.WebSocket.frame(), new_state} | ||
when new_state: term() | ||
|
||
@callback handle_disconnect(reason :: term(), state :: term()) :: | ||
{:ok, new_state} | ||
| {:reconnect, backoff :: pos_integer(), new_state} | ||
when new_state: term() | ||
|
||
@callback handle_frame(frame :: Mint.WebSocket.frame(), state :: term()) :: | ||
{:ok, new_state} | ||
| {:reply, frame :: Mint.WebSocket.frame(), new_state} | ||
when new_state: term() | ||
|
||
@callback terminate(reason, state :: term()) :: term() | ||
when reason: :normal | :shutdown | {:shutdown, term()} | term() | ||
|
||
@optional_callbacks connect: 1 | ||
|
||
defdelegate start_link(module, init_arg, options \\ []), to: Minch.Client | ||
|
||
defdelegate connect(url, headers \\ [], options \\ []), to: Minch.SimpleClient | ||
|
||
@spec close(client()) :: :ok | ||
def close(client) do | ||
GenServer.stop(client) | ||
end | ||
|
||
@spec send_frame(client(), Mint.WebSocket.frame()) :: :ok | {:error, Mint.WebSocket.error()} | ||
def send_frame(client, frame) do | ||
GenServer.call(client, {:send_frame, frame}) | ||
end | ||
|
||
@spec await_frame(Mint.Types.request_ref(), timeout()) :: Mint.WebSocket.frame() | :timeout | ||
def await_frame(ref, timeout \\ 5_000) do | ||
receive do | ||
{:frame, ^ref, frame} -> frame | ||
after | ||
timeout -> :timeout | ||
end | ||
end | ||
|
||
defmacro __using__(_) do | ||
quote do | ||
@behaviour Minch | ||
|
||
def child_spec(init_arg) do | ||
%{ | ||
id: __MODULE__, | ||
start: {__MODULE__, :start_link, [init_arg]}, | ||
restart: :transient | ||
} | ||
end | ||
|
||
def start_link(init_arg) do | ||
Minch.start_link(__MODULE__, init_arg, name: __MODULE__) | ||
end | ||
|
||
def init(init_arg) do | ||
{:connect, init_arg} | ||
end | ||
|
||
def terminate(_reason, _state) do | ||
:ok | ||
end | ||
|
||
def handle_info(_message, state) do | ||
{:noreply, state} | ||
end | ||
|
||
defoverridable child_spec: 1, | ||
start_link: 1, | ||
init: 1, | ||
terminate: 2, | ||
handle_info: 2 | ||
end | ||
end | ||
end |
Oops, something went wrong.