--- /dev/null
+# Used by "mix format"
+[
+ inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
+]
--- /dev/null
+# 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").
+wampex-*.tar
+
--- /dev/null
+elixir 1.10.1
+erlang 22.2.6
--- /dev/null
+# Wampex
+
+**TODO: Add description**
+
+## Installation
+
+If [available in Hex](https://hex.pm/docs/publish), the package can be installed
+by adding `wampex` to your list of dependencies in `mix.exs`:
+
+```elixir
+def deps do
+ [
+ {:wampex, "~> 0.1.0"}
+ ]
+end
+```
+
+Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
+and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
+be found at [https://hexdocs.pm/wampex](https://hexdocs.pm/wampex).
+
--- /dev/null
+defmodule Wampex do
+ @moduledoc """
+ Documentation for Wampex.
+ """
+ alias Wampex.Session
+
+ def test do
+ url = "ws://localhost:18080/ws"
+ sess = %Session{url: url}
+ Session.start_link(sess)
+ end
+end
--- /dev/null
+defmodule Wampex.Serializer.JSON do
+ def data_type, do: :text
+ def serialize!(data), do: Jason.encode!(data)
+ def serialize(data), do: Jason.encode(data)
+ def deserialize!(binary), do: Jason.decode!(binary)
+ def deserialize(binary), do: Jason.decode(binary)
+end
--- /dev/null
+defmodule Wampex.Session do
+ use StatesLanguage, data: "priv/session.json"
+
+ alias StatesLanguage, as: SL
+ alias Wampex.Session, as: Sess
+ alias Wampex.Serializer.JSON
+ alias Wampex.Transport.WebSocket
+
+ defstruct [
+ :id,
+ :url,
+ :transport_pid,
+ :message,
+ request_id: 1,
+ transport: WebSocket,
+ protocol: "wamp.2.json",
+ serializer: JSON
+ ]
+
+ def get_id do
+ Enum.random(1..9_007_199_254_740_992)
+ end
+
+ def handle_resource(
+ "Hello",
+ _,
+ "Init",
+ %SL{data: %Sess{transport: tt, transport_pid: t}} = data
+ ) do
+ tt.send_message(
+ t,
+ [
+ 1,
+ "com.myrealm",
+ %{roles: %{publisher: %{}, subscriber: %{}, caller: %{}, callee: %{}}}
+ ]
+ )
+
+ {:ok, data, []}
+ end
+
+ def handle_resource(
+ "GoodBye",
+ _,
+ "GoodBye",
+ %SL{data: %Sess{transport: tt, transport_pid: t}} = data
+ ) do
+ tt.send_message(t, [6, %{}, "i.dont.know"])
+ {:ok, data, []}
+ end
+
+ def handle_resource("HandleMessage", _, _, data) do
+ Logger.info("Handling Message")
+ {:ok, data, []}
+ end
+
+ def handle_resource("Abort", _, _, data) do
+ Logger.info("Aborting...")
+ {:ok, data, []}
+ end
+
+ def handle_resource(
+ "InitTransport",
+ _,
+ "WaitForTransport",
+ %SL{data: %Sess{url: url, transport: t, serializer: s, protocol: p} = sess} = data
+ ) do
+ {:ok, pid} = t.start_link(url, self(), p, s)
+ {:ok, %SL{data | data: %Sess{sess | transport_pid: pid}}, []}
+ end
+
+ def handle_resource("Established", _, "Established", data) do
+ Logger.info("Session #{data.data.id} Established")
+ {:ok, data, []}
+ end
+
+ def handle_info({:set_session_id, id}, "Init", %SL{data: %Sess{} = sess} = data) do
+ {:ok, %SL{data | data: %Sess{sess | id: id}}, [{:next_event, :internal, :welcome}]}
+ end
+
+ def handle_info({:set_message, message}, "Established", %SL{data: %Sess{} = sess} = data) do
+ {:ok, %SL{data | data: %Sess{sess | message: message}},
+ [{:next_event, :internal, :message_received}]}
+ end
+end
--- /dev/null
+defmodule Wampex.Transport.WebSocket do
+ use WebSockex
+
+ alias WebSockex.Conn
+
+ require Logger
+
+ @header "Sec-WebSocket-Protocol"
+
+ def send_message(t, data) do
+ WebSockex.cast(t, {:send_message, data})
+ end
+
+ def connected?(t, resp) do
+ WebSockex.cast(t, {:connected, resp})
+ end
+
+ def start_link(
+ url,
+ session,
+ protocol,
+ serializer,
+ opts \\ []
+ ) do
+ url
+ |> Conn.new(extra_headers: [{@header, protocol}])
+ |> WebSockex.start_link(
+ __MODULE__,
+ %{session: session, serializer: serializer, connected: false},
+ opts
+ )
+ end
+
+ def handle_connect(%Conn{host: h, port: p, path: pa, query: q} = _conn, state) do
+ Logger.info("Connected to #{h}:#{p}#{pa}?#{q}")
+ send(state.session, {:connected, true})
+ {:ok, %{state | connected: true}}
+ end
+
+ def handle_cast({:send_message, data}, %{serializer: s} = state) do
+ {:reply, {s.data_type(), s.serialize!(data)}, state}
+ end
+
+ def handle_cast({:connected, resp}, state) do
+ send(resp, {:connected, state.connected})
+ {:ok, state}
+ end
+
+ def handle_frame({type, data}, %{serializer: s} = state) do
+ Logger.info("Received #{type} data #{data}")
+ data = s.deserialize!(data)
+ Logger.info("Deserialized #{inspect(data)}")
+ handle_message(data, state)
+ end
+
+ def handle_message([2, id, _dets], state) do
+ send(state.session, {:set_session_id, id})
+ {:ok, state}
+ end
+
+ def handle_message(message, state) do
+ send(state.session, {:set_message, message})
+ end
+end
--- /dev/null
+defmodule Wampex.MixProject do
+ use Mix.Project
+
+ def project do
+ [
+ app: :wampex,
+ version: "0.1.0",
+ elixir: "~> 1.9",
+ start_permanent: Mix.env() == :prod,
+ deps: deps()
+ ]
+ end
+
+ # Run "mix help compile.app" to learn about applications.
+ def application do
+ [
+ extra_applications: [:logger]
+ ]
+ end
+
+ # Run "mix help deps" to learn about dependencies.
+ defp deps do
+ [
+ {:jason, "~> 1.1"},
+ {:states_language, "~> 0.2"},
+ {:websockex, "~> 0.4"}
+ # {:dep_from_hexpm, "~> 0.3.0"},
+ # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
+ ]
+ end
+end
--- /dev/null
+%{
+ "conv_case": {:hex, :conv_case, "0.2.2", "5a98b74ab8f7ddbad670e5c7bb39ff280e60699aa3b25c7062ceccf48137433c", [:mix], [], "hexpm", "561c550ab6d55b2a4d4c14449e58c9957798613eb26ea182e14a962965377bca"},
+ "elixpath": {:hex, :elixpath, "0.1.0", "f860e931db7bda6856dc68145694ca429643cc068ef30d7ff6b4096d4357963e", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "30ce06079b41f1f5216ea2cd11605cfe4c82239628555cb3fde9f10055a6eb67"},
+ "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
+ "json_xema": {:hex, :json_xema, "0.4.0", "377446cd5c0e2cbba52b9d7ab67c05579e6d4a788335220215a8870eac821996", [:mix], [{:conv_case, "~> 0.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:xema, "~> 0.11", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "452724a5b2751cd69191edd3fd3da0c2194c164ebd49efd85f9abb64d6621b53"},
+ "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
+ "states_language": {:hex, :states_language, "0.2.8", "f9dfd3c0bd9a9d7bda25ef315f2d90944cd6b2022a7f3c403deb1d4ec451825e", [:mix], [{:elixpath, "~> 0.1.0", [hex: :elixpath, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:json_xema, "~> 0.4.0", [hex: :json_xema, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:xema, "~> 0.11.0", [hex: :xema, repo: "hexpm", optional: false]}], "hexpm", "a5231691e7cb37fe32dc7de54c2dc86d1d60e84c4f0379f3246e55be2a85ec78"},
+ "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
+ "websockex": {:hex, :websockex, "0.4.2", "9a3b7dc25655517ecd3f8ff7109a77fce94956096b942836cdcfbc7c86603ecc", [:mix], [], "hexpm", "803cd76e91544b56f0e655e36790be797fa6436db9224f7c303db9b9df2a3df4"},
+ "xema": {:hex, :xema, "0.11.0", "7b5118418633cffc27092110d02d4faeea938149dd3f6c64299e41e747067e80", [:mix], [{:conv_case, "~> 0.2.2", [hex: :conv_case, repo: "hexpm", optional: false]}, {:decimal, "~> 1.7", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "51491c9a953d65069d4b30aa2f70bc45ff99fd1bc3345bc72ce4e644d01ea14e"},
+}
--- /dev/null
+{
+ "Comment": "Session State Machine",
+ "StartAt": "WaitForTransport",
+ "States": {
+ "WaitForTransport": {
+ "Type": "Task",
+ "Resource": "InitTransport",
+ "TransitionEvent": "{:connected, true}",
+ "Next": "Init",
+ "Catch": [
+ {
+ "ErrorEquals": ["{:connected, false}"],
+ "Next": "Abort"
+ }
+ ]
+ },
+ "Init": {
+ "Type": "Task",
+ "Resource": "Hello",
+ "TransitionEvent": ":welcome",
+ "Next": "Established",
+ "Catch": [
+ {
+ "ErrorEquals": [":abort"],
+ "Next": "Abort"
+ }
+ ]
+ },
+ "Established": {
+ "Type": "Choice",
+ "Resource": "Established",
+ "Choices": [
+ {
+ "StringEquals": ":message_received",
+ "Next": "HandleMessage"
+ },
+ {
+ "StringEquals": ":good_bye",
+ "Next": "GoodBye"
+ }
+ ]
+
+ },
+ "HandleMessage": {
+ "Type": "Task",
+ "Resource": "HandleMessage",
+ "TransitionEvent": ":handled",
+ "Next": "Established"
+ },
+ "GoodBye": {
+ "Type": "Task",
+ "Resource": "GoodBye",
+ "End": true
+ },
+ "Abort": {
+ "Type": "Task",
+ "Resource": "Abort",
+ "End": true
+ }
+ }
+}
--- /dev/null
+ExUnit.start()
--- /dev/null
+defmodule WampexTest do
+ use ExUnit.Case
+ doctest Wampex
+
+ test "greets the world" do
+ assert Wampex.hello() == :world
+ end
+end