# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.
defmodule Antikythera.Test.WebsocketClient do
@moduledoc """
Websocket client for gear tests.
Prepare your client module with:
defmodule YourGear.Socket do
use Antikythera.Test.WebsocketClient
end
Then, call `YourGear.Socket.spawn_link("/ws")` to open Websocket connection at "ws(s)://<host_name>/ws" endpoint.
It will return pid of Websocket connection handling process if successful.
By default, it connects to the server specified by `Antikythera.Test.Config.base_url/0`,
and redirects received frame to the caller (test running) process, in `{frame, handler_pid}` form.
These default behavior can be overridable with two overridable functions:
- `base_url/0`
- `handle_received_frame/2`
"""
defmacro __using__(_) do
quote do
@behaviour :websocket_client
@default_base_url Antikythera.Test.Config.base_url()
|> String.replace_prefix("http://", "ws://")
|> String.replace_prefix("https://", "wss://")
def spawn_link(path, timeout \\ 5_000) do
url = String.to_charlist(base_url() <> path)
{:ok, pid} = :websocket_client.start_link(url, __MODULE__, [self()])
receive do
:connected -> pid
:disconnected -> raise "failed to establish websocket connection: disconnected"
after
timeout -> raise "failed to establish websocket connection: timeout"
end
end
def send_frame(pid, frame) do
:websocket_client.cast(pid, frame)
end
def send_json(pid, m) do
send_frame(pid, {:text, Poison.encode!(m)})
end
#
# callback implementations
#
@impl true
def init([pid]) do
{:once, %{caller: pid}}
end
@impl true
def onconnect(_wsreq, %{caller: pid} = state) do
send(pid, :connected)
{:ok, state}
end
@impl true
def ondisconnect(_reason, %{caller: pid} = state) do
send(pid, :disconnected)
{:close, :normal, state}
end
@impl true
def websocket_handle(frame, _conn, %{caller: pid} = state) do
handle_received_frame(frame, pid)
{:ok, state}
end
@impl true
def websocket_info(_msg, _conn, state) do
{:ok, state}
end
@impl true
def websocket_terminate(_reason, _conn, _state) do
:ok
end
#
# user overridables
#
def base_url(), do: @default_base_url
def handle_received_frame(frame, caller_pid), do: send(caller_pid, {frame, self()})
defoverridable base_url: 0, handle_received_frame: 2
end
end
end