├── nerves_kiosk
├── test
│ ├── test_helper.exs
│ └── nerves_kiosk_test.exs
├── rel
│ ├── plugins
│ │ └── .gitignore
│ ├── vm.args
│ └── config.exs
├── .formatter.exs
├── lib
│ ├── nerves_kiosk.ex
│ └── nerves_kiosk
│ │ └── application.ex
├── .gitignore
├── rootfs_overlay
│ └── etc
│ │ └── iex.exs
├── mix.lock.host
├── README.md
├── mix.exs
├── config
│ └── config.exs
└── mix.lock.rpi3
└── phx_kiosk
├── test
├── test_helper.exs
├── phx_kiosk_web
│ ├── views
│ │ ├── page_view_test.exs
│ │ ├── layout_view_test.exs
│ │ └── error_view_test.exs
│ ├── controllers
│ │ └── page_controller_test.exs
│ └── channels
│ │ └── home_channel_test.exs
└── support
│ ├── channel_case.ex
│ └── conn_case.ex
├── lib
├── phx_kiosk_web
│ ├── views
│ │ ├── page_view.ex
│ │ ├── layout_view.ex
│ │ ├── error_view.ex
│ │ └── error_helpers.ex
│ ├── controllers
│ │ └── page_controller.ex
│ ├── templates
│ │ ├── page
│ │ │ └── index.html.eex
│ │ └── layout
│ │ │ └── app.html.eex
│ ├── router.ex
│ ├── gettext.ex
│ ├── channels
│ │ ├── user_socket.ex
│ │ └── home_channel.ex
│ └── endpoint.ex
├── phx_kiosk.ex
├── phx_kiosk
│ ├── backlight.ex
│ └── application.ex
└── phx_kiosk_web.ex
├── priv
├── static
│ ├── favicon.ico
│ ├── images
│ │ └── phoenix.png
│ ├── robots.txt
│ └── js
│ │ ├── app.js
│ │ └── phoenix.js
└── gettext
│ ├── en
│ └── LC_MESSAGES
│ │ └── errors.po
│ └── errors.pot
├── config
├── test.exs
├── config.exs
├── dev.exs
└── prod.exs
├── .gitignore
├── README.md
├── mix.exs
└── mix.lock
/nerves_kiosk/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/phx_kiosk/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
3 |
--------------------------------------------------------------------------------
/nerves_kiosk/rel/plugins/.gitignore:
--------------------------------------------------------------------------------
1 | *.*
2 | !*.exs
3 | !.gitignore
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/views/page_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.PageView do
2 | use PhxKioskWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/nerves_kiosk/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/views/layout_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.LayoutView do
2 | use PhxKioskWeb, :view
3 | end
4 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobileoverlord/training_kiosk/HEAD/phx_kiosk/priv/static/favicon.ico
--------------------------------------------------------------------------------
/phx_kiosk/priv/static/images/phoenix.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mobileoverlord/training_kiosk/HEAD/phx_kiosk/priv/static/images/phoenix.png
--------------------------------------------------------------------------------
/phx_kiosk/test/phx_kiosk_web/views/page_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.PageViewTest do
2 | use PhxKioskWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/phx_kiosk/test/phx_kiosk_web/views/layout_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.LayoutViewTest do
2 | use PhxKioskWeb.ConnCase, async: true
3 | end
4 |
--------------------------------------------------------------------------------
/nerves_kiosk/test/nerves_kiosk_test.exs:
--------------------------------------------------------------------------------
1 | defmodule NervesKioskTest do
2 | use ExUnit.Case
3 | doctest NervesKiosk
4 |
5 | test "greets the world" do
6 | assert NervesKiosk.hello() == :world
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.PageController do
2 | use PhxKioskWeb, :controller
3 |
4 | def index(conn, _params) do
5 | render conn, "index.html"
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/phx_kiosk/test/phx_kiosk_web/controllers/page_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.PageControllerTest do
2 | use PhxKioskWeb.ConnCase
3 |
4 | test "GET /", %{conn: conn} do
5 | conn = get conn, "/"
6 | assert html_response(conn, 200) =~ "Welcome to Phoenix!"
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
Screen brightness
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKiosk do
2 | @moduledoc """
3 | PhxKiosk keeps the contexts that define your domain
4 | and business logic.
5 |
6 | Contexts are also responsible for managing your data, regardless
7 | if it comes from the database, an external API or others.
8 | """
9 | end
10 |
--------------------------------------------------------------------------------
/phx_kiosk/config/test.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # We don't run a server during test. If one is required,
4 | # you can enable the server option below.
5 | config :phx_kiosk, PhxKioskWeb.Endpoint,
6 | http: [port: 4001],
7 | server: false
8 |
9 | # Print only warnings and errors during test
10 | config :logger, level: :warn
11 |
--------------------------------------------------------------------------------
/nerves_kiosk/lib/nerves_kiosk.ex:
--------------------------------------------------------------------------------
1 | defmodule NervesKiosk do
2 | @moduledoc """
3 | Documentation for NervesKiosk.
4 | """
5 |
6 | @doc """
7 | Hello world.
8 |
9 | ## Examples
10 |
11 | iex> NervesKiosk.hello
12 | :world
13 |
14 | """
15 |
16 | require Logger
17 |
18 | def hello do
19 | Logger.debug("Hello")
20 | :world
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 |
--------------------------------------------------------------------------------
/phx_kiosk/.gitignore:
--------------------------------------------------------------------------------
1 | # App artifacts
2 | /_build
3 | /db
4 | /deps
5 | /*.ez
6 |
7 | # Generated on crash by the VM
8 | erl_crash.dump
9 |
10 | # Files matching config/*.secret.exs pattern contain sensitive
11 | # data and you should not commit them into version control.
12 | #
13 | # Alternatively, you may comment the line below and commit the
14 | # secrets files as long as you replace their contents by environment
15 | # variables.
16 | /config/*.secret.exs
17 |
--------------------------------------------------------------------------------
/nerves_kiosk/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
--------------------------------------------------------------------------------
/phx_kiosk/test/phx_kiosk_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.ErrorViewTest do
2 | use PhxKioskWeb.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(PhxKioskWeb.ErrorView, "404.html", []) ==
9 | "Not Found"
10 | end
11 |
12 | test "renders 500.html" do
13 | assert render_to_string(PhxKioskWeb.ErrorView, "500.html", []) ==
14 | "Internal Server Error"
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/nerves_kiosk/rootfs_overlay/etc/iex.exs:
--------------------------------------------------------------------------------
1 | # Pull in Nerves-specific helpers to the IEx session
2 | use Nerves.Runtime.Helpers
3 |
4 | if RingLogger in Application.get_env(:logger, :backends, []) do
5 | IO.puts """
6 | RingLogger is collecting log messages from Elixir and Linux. To see the
7 | messages, either attach the current IEx session to the logger:
8 |
9 | RingLogger.attach
10 |
11 | or tail the log:
12 |
13 | RingLogger.tail
14 | """
15 | end
16 |
17 | # Be careful when adding to this file. Nearly any error can crash the VM and
18 | # cause a reboot.
19 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/views/error_view.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.ErrorView do
2 | use PhxKioskWeb, :view
3 |
4 | # If you want to customize a particular status code
5 | # for a certain format, you may uncomment below.
6 | # def render("500.html", _assigns) do
7 | # "Internal Server Error"
8 | # end
9 |
10 | # By default, Phoenix returns the status message from
11 | # the template name. For example, "404.html" becomes
12 | # "Not Found".
13 | def template_not_found(template, _assigns) do
14 | Phoenix.Controller.status_message_from_template(template)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/phx_kiosk/README.md:
--------------------------------------------------------------------------------
1 | # PhxKiosk
2 |
3 | To start your Phoenix server:
4 |
5 | * Install dependencies with `mix deps.get`
6 | * Start Phoenix endpoint with `mix phx.server`
7 |
8 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
9 |
10 | Ready to run in production? Please [check our deployment guides](http://www.phoenixframework.org/docs/deployment).
11 |
12 | ## Learn more
13 |
14 | * Official website: http://www.phoenixframework.org/
15 | * Guides: http://phoenixframework.org/docs/overview
16 | * Docs: https://hexdocs.pm/phoenix
17 | * Mailing list: http://groups.google.com/group/phoenix-talk
18 | * Source: https://github.com/phoenixframework/phoenix
19 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/router.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.Router do
2 | use PhxKioskWeb, :router
3 |
4 | pipeline :browser do
5 | plug :accepts, ["html"]
6 | plug :fetch_session
7 | plug :fetch_flash
8 | plug :protect_from_forgery
9 | plug :put_secure_browser_headers
10 | end
11 |
12 | pipeline :api do
13 | plug :accepts, ["json"]
14 | end
15 |
16 | forward "/wobserver", Wobserver.Web.Router
17 |
18 | scope "/", PhxKioskWeb do
19 | pipe_through :browser # Use the default browser stack
20 |
21 | get "/", PageController, :index
22 | end
23 |
24 | # Other scopes may use custom stacks.
25 | # scope "/api", PhxKioskWeb do
26 | # pipe_through :api
27 | # end
28 | end
29 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | import PhxKioskWeb.Gettext
9 |
10 | # Simple translation
11 | gettext "Here is the string to translate"
12 |
13 | # Plural translation
14 | ngettext "Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3
17 |
18 | # Domain-based translation
19 | dgettext "errors", "Here is the error message to translate"
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext, otp_app: :phx_kiosk
24 | end
25 |
--------------------------------------------------------------------------------
/phx_kiosk/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with channels
21 | use Phoenix.ChannelTest
22 |
23 | # The default endpoint for testing
24 | @endpoint PhxKioskWeb.Endpoint
25 | end
26 | end
27 |
28 |
29 | setup _tags do
30 | :ok
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/phx_kiosk/test/phx_kiosk_web/channels/home_channel_test.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.HomeChannelTest do
2 | use PhxKioskWeb.ChannelCase
3 |
4 | alias PhxKioskWeb.HomeChannel
5 |
6 | setup do
7 | {:ok, _, socket} =
8 | socket("user_id", %{some: :assign})
9 | |> subscribe_and_join(HomeChannel, "home:lobby")
10 |
11 | {:ok, socket: socket}
12 | end
13 |
14 | test "ping replies with status ok", %{socket: socket} do
15 | ref = push socket, "ping", %{"hello" => "there"}
16 | assert_reply ref, :ok, %{"hello" => "there"}
17 | end
18 |
19 | test "shout broadcasts to home:lobby", %{socket: socket} do
20 | push socket, "shout", %{"hello" => "all"}
21 | assert_broadcast "shout", %{"hello" => "all"}
22 | end
23 |
24 | test "broadcasts are pushed to the client", %{socket: socket} do
25 | broadcast_from! socket, "broadcast", %{"some" => "data"}
26 | assert_push "broadcast", %{"some" => "data"}
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/phx_kiosk/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Configures the endpoint
9 | config :phx_kiosk, PhxKioskWeb.Endpoint,
10 | url: [host: "localhost"],
11 | secret_key_base: "75vCrMyEnY4PeWnxH/oESUhaMv85gL6uc97xqiCdfRAFaZXUjDjaKeIQKbnaydWO",
12 | render_errors: [view: PhxKioskWeb.ErrorView, accepts: ~w(html json)],
13 | pubsub: [name: PhxKiosk.PubSub,
14 | adapter: Phoenix.PubSub.PG2]
15 |
16 | # Configures Elixir's Logger
17 | config :logger, :console,
18 | format: "$time $metadata[$level] $message\n",
19 | metadata: [:user_id]
20 |
21 | # Import environment specific config. This must remain at the bottom
22 | # of this file so it overrides the configuration defined above.
23 | import_config "#{Mix.env}.exs"
24 |
--------------------------------------------------------------------------------
/nerves_kiosk/mix.lock.host:
--------------------------------------------------------------------------------
1 | %{
2 | "artificery": {:hex, :artificery, "0.2.6", "f602909757263f7897130cbd006b0e40514a541b148d366ad65b89236b93497a", [:mix], [], "hexpm"},
3 | "distillery": {:hex, :distillery, "2.0.9", "1e03e1bd97c0cd1187ce5882143fcc44812aa54d8c61b093801fab053bf07c98", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
4 | "nerves": {:hex, :nerves, "1.3.0", "473b95afcd7a7211d33ec1406291a64f2bf4980862f93123e55b21946ae7a2f7", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
5 | "ring_logger": {:hex, :ring_logger, "0.4.1", "db972365bfda705288d7629e80af5704a1aafdbe9da842712c3cdd587639c72e", [:mix], [], "hexpm"},
6 | "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"},
7 | }
8 |
--------------------------------------------------------------------------------
/phx_kiosk/test/support/conn_case.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.ConnCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | tests that require setting up a connection.
5 |
6 | Such tests rely on `Phoenix.ConnTest` and also
7 | import other functionality to make it easier
8 | to build common datastructures and query the data layer.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | using do
19 | quote do
20 | # Import conveniences for testing with connections
21 | use Phoenix.ConnTest
22 | import PhxKioskWeb.Router.Helpers
23 |
24 | # The default endpoint for testing
25 | @endpoint PhxKioskWeb.Endpoint
26 | end
27 | end
28 |
29 |
30 | setup _tags do
31 | {:ok, conn: Phoenix.ConnTest.build_conn()}
32 | end
33 |
34 | end
35 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk/backlight.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKiosk.Backlight do
2 | use GenServer
3 |
4 | @brightness_file "/sys/class/backlight/rpi_backlight/brightness"
5 |
6 | #Public API
7 |
8 | def start_link(opts) do
9 | GenServer.start_link(__MODULE__, opts, name: __MODULE__)
10 | end
11 |
12 | def set_brightness(value) when value >= 0 and value <= 255 do
13 | GenServer.call(__MODULE__, {:set_brightness, value})
14 | end
15 |
16 | def set_brightness(_value) do
17 | {:error, "Value must be >= 0 and <= 255"}
18 | end
19 |
20 | def brightness() do
21 | GenServer.call(__MODULE__, :brightness)
22 | end
23 |
24 | #GenServer Callbacks
25 |
26 | def init(_) do
27 | {:ok, 255}
28 | end
29 |
30 | def handle_call(:brightness, _from, brightness) do
31 | {:reply, brightness, brightness}
32 | end
33 |
34 | def handle_call({:set_brightness, value}, _from, _brightness) do
35 | if File.exists?(@brightness_file) do
36 | value = value |> round() |> to_string()
37 | File.write(@brightness_file, value)
38 | end
39 | {:reply, value, value}
40 | end
41 |
42 | end
43 |
44 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk/application.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKiosk.Application do
2 | use Application
3 |
4 | # See https://hexdocs.pm/elixir/Application.html
5 | # for more information on OTP Applications
6 | def start(_type, _args) do
7 | import Supervisor.Spec
8 |
9 | # Define workers and child supervisors to be supervised
10 | children = [
11 | # Start the endpoint when the application starts
12 | supervisor(PhxKioskWeb.Endpoint, []),
13 | # Start your own worker by calling: PhxKiosk.Worker.start_link(arg1, arg2, arg3)
14 | # worker(PhxKiosk.Worker, [arg1, arg2, arg3]),
15 | PhxKiosk.Backlight
16 | ]
17 |
18 | # See https://hexdocs.pm/elixir/Supervisor.html
19 | # for other strategies and supported options
20 | opts = [strategy: :one_for_one, name: PhxKiosk.Supervisor]
21 | Supervisor.start_link(children, opts)
22 | end
23 |
24 | # Tell Phoenix to update the endpoint configuration
25 | # whenever the application is updated.
26 | def config_change(changed, _new, removed) do
27 | PhxKioskWeb.Endpoint.config_change(changed, removed)
28 | :ok
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/nerves_kiosk/rel/vm.args:
--------------------------------------------------------------------------------
1 | ## Add custom options here
2 |
3 | ## Distributed Erlang Options
4 | ## The cookie needs to be configured prior to vm boot for
5 | ## for read only filesystem.
6 |
7 | # -name nerves_kiosk@0.0.0.0
8 | -setcookie training
9 |
10 | ## Use Ctrl-C to interrupt the current shell rather than invoking the emulator's
11 | ## break handler and possibly exiting the VM.
12 | +Bc
13 |
14 | ## Save the shell history between reboots
15 | ## See http://erlang.org/doc/man/kernel_app.html for additional options
16 | -kernel shell_history enabled
17 |
18 | ## Enable heartbeat monitoring of the Erlang runtime system
19 | #-heart -env HEART_BEAT_TIMEOUT 30
20 |
21 | ## Start the Elixir shell
22 |
23 | -noshell
24 | -user Elixir.IEx.CLI
25 |
26 | ## Enable colors in the shell
27 | -elixir ansi_enabled true
28 |
29 | ## Options added after -extra are interpreted as plain arguments and can be
30 | ## retrieved using :init.get_plain_arguments(). Options before the "--" are
31 | ## interpreted by Elixir and anything afterwards is left around for other IEx
32 | ## and user applications.
33 | -extra --no-halt
34 | --
35 | --dot-iex /etc/iex.exs
36 |
37 |
--------------------------------------------------------------------------------
/nerves_kiosk/rel/config.exs:
--------------------------------------------------------------------------------
1 | use Mix.Releases.Config,
2 | # This sets the default release built by `mix release`
3 | default_release: :default,
4 | # This sets the default environment used by `mix release`
5 | default_environment: :dev
6 |
7 | # For a full list of config options for both releases
8 | # and environments, visit https://hexdocs.pm/distillery/configuration.html
9 |
10 |
11 | # You may define one or more environments in this file,
12 | # an environment's settings will override those of a release
13 | # when building in that environment, this combination of release
14 | # and environment configuration is called a profile
15 |
16 | environment :dev do
17 | set cookie: :"teshD.P:nRAuI|fe>Gw&T?N4r@mf*,m4yu7Y;BT~.L/9XYIDIsH5,C~`I:VQa:Ej"
18 | end
19 |
20 | environment :prod do
21 | set cookie: :"teshD.P:nRAuI|fe>Gw&T?N4r@mf*,m4yu7Y;BT~.L/9XYIDIsH5,C~`I:VQa:Ej"
22 | end
23 |
24 | # You may define one or more releases in this file.
25 | # If you have not set a default release, or selected one
26 | # when running `mix release`, the first release in the file
27 | # will be used by default
28 |
29 | release :nerves_kiosk do
30 | set version: current_version(:nerves_kiosk)
31 | plugin Nerves
32 | plugin Shoehorn
33 | end
34 |
35 |
--------------------------------------------------------------------------------
/phx_kiosk/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule PhxKiosk.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :phx_kiosk,
7 | version: "0.0.1",
8 | elixir: "~> 1.4",
9 | elixirc_paths: elixirc_paths(Mix.env),
10 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
11 | start_permanent: Mix.env == :prod,
12 | deps: deps()
13 | ]
14 | end
15 |
16 | # Configuration for the OTP application.
17 | #
18 | # Type `mix help compile.app` for more information.
19 | def application do
20 | [
21 | mod: {PhxKiosk.Application, []},
22 | extra_applications: [:logger, :runtime_tools]
23 | ]
24 | end
25 |
26 | # Specifies which paths to compile per environment.
27 | defp elixirc_paths(:test), do: ["lib", "test/support"]
28 | defp elixirc_paths(_), do: ["lib"]
29 |
30 | # Specifies your project dependencies.
31 | #
32 | # Type `mix help deps` for examples and options.
33 | defp deps do
34 | [
35 | {:phoenix, "~> 1.3.4"},
36 | {:phoenix_pubsub, "~> 1.0"},
37 | {:phoenix_html, "~> 2.10"},
38 | {:phoenix_live_reload, "~> 1.0", only: :dev},
39 | {:gettext, "~> 0.11"},
40 | {:cowboy, "~> 1.0"},
41 | {:wobserver, "~> 0.1"}
42 | ]
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/nerves_kiosk/README.md:
--------------------------------------------------------------------------------
1 | # NervesKiosk
2 |
3 | **TODO: Add description**
4 |
5 | ## Targets
6 |
7 | Nerves applications produce images for hardware targets based on the
8 | `MIX_TARGET` environment variable. If `MIX_TARGET` is unset, `mix` builds an
9 | image that runs on the host (e.g., your laptop). This is useful for executing
10 | logic tests, running utilities, and debugging. Other targets are represented by
11 | a short name like `rpi3` that maps to a Nerves system image for that platform.
12 | All of this logic is in the generated `mix.exs` and may be customized. For more
13 | information about targets see:
14 |
15 | https://hexdocs.pm/nerves/targets.html#content
16 |
17 | ## Getting Started
18 |
19 | To start your Nerves app:
20 | * `export MIX_TARGET=my_target` or prefix every command with
21 | `MIX_TARGET=my_target`. For example, `MIX_TARGET=rpi3`
22 | * Install dependencies with `mix deps.get`
23 | * Create firmware with `mix firmware`
24 | * Burn to an SD card with `mix firmware.burn`
25 |
26 | ## Learn more
27 |
28 | * Official docs: https://hexdocs.pm/nerves/getting-started.html
29 | * Official website: http://www.nerves-project.org/
30 | * Discussion Slack elixir-lang #nerves ([Invite](https://elixir-slackin.herokuapp.com/))
31 | * Source: https://github.com/nerves-project/nerves
32 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Hello PhxKiosk!
11 | ">
12 |
13 |
14 |
15 |
16 |
24 |
25 |
<%= get_flash(@conn, :info) %>
26 |
<%= get_flash(@conn, :error) %>
27 |
28 |
29 | <%= render @view_module, @view_template, assigns %>
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", PhxKioskWeb.RoomChannel
6 | channel "home:lobby", PhxKioskWeb.HomeChannel
7 |
8 |
9 | ## Transports
10 | transport :websocket, Phoenix.Transports.WebSocket
11 | # transport :longpoll, Phoenix.Transports.LongPoll
12 |
13 | # Socket params are passed from the client and can
14 | # be used to verify and authenticate a user. After
15 | # verification, you can put default assigns into
16 | # the socket that will be set for all channels, ie
17 | #
18 | # {:ok, assign(socket, :user_id, verified_user_id)}
19 | #
20 | # To deny connection, return `:error`.
21 | #
22 | # See `Phoenix.Token` documentation for examples in
23 | # performing token verification on connect.
24 | def connect(_params, socket) do
25 | {:ok, socket}
26 | end
27 |
28 | # Socket id's are topics that allow you to identify all sockets for a given user:
29 | #
30 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}"
31 | #
32 | # Would allow you to broadcast a "disconnect" event and terminate
33 | # all active sockets and channels for a given user:
34 | #
35 | # PhxKioskWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
36 | #
37 | # Returning `nil` makes this socket anonymous.
38 | def id(_socket), do: nil
39 | end
40 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/views/error_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.ErrorHelpers do
2 | @moduledoc """
3 | Conveniences for translating and building error messages.
4 | """
5 |
6 | use Phoenix.HTML
7 |
8 | @doc """
9 | Generates tag for inlined form input errors.
10 | """
11 | def error_tag(form, field) do
12 | Enum.map(Keyword.get_values(form.errors, field), fn (error) ->
13 | content_tag :span, translate_error(error), class: "help-block"
14 | end)
15 | end
16 |
17 | @doc """
18 | Translates an error message using gettext.
19 | """
20 | def translate_error({msg, opts}) do
21 | # When using gettext, we typically pass the strings we want
22 | # to translate as a static argument:
23 | #
24 | # # Translate "is invalid" in the "errors" domain
25 | # dgettext "errors", "is invalid"
26 | #
27 | # # Translate the number of files with plural rules
28 | # dngettext "errors", "1 file", "%{count} files", count
29 | #
30 | # Because the error messages we show in our forms and APIs
31 | # are defined inside Ecto, we need to translate them dynamically.
32 | # This requires us to call the Gettext module passing our gettext
33 | # backend as first argument.
34 | #
35 | # Note we use the "errors" domain, which means translations
36 | # should be written to the errors.po file. The :count option is
37 | # set by Ecto and indicates we should also apply plural rules.
38 | if count = opts[:count] do
39 | Gettext.dngettext(PhxKioskWeb.Gettext, "errors", msg, msg, count, opts)
40 | else
41 | Gettext.dgettext(PhxKioskWeb.Gettext, "errors", msg, opts)
42 | end
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/nerves_kiosk/lib/nerves_kiosk/application.ex:
--------------------------------------------------------------------------------
1 | defmodule NervesKiosk.Application do
2 | # See https://hexdocs.pm/elixir/Application.html
3 | # for more information on OTP Applications
4 | @moduledoc false
5 |
6 | @target Mix.Project.config()[:target]
7 |
8 | use Application
9 |
10 | def start(_type, _args) do
11 | # See https://hexdocs.pm/elixir/Supervisor.html
12 | # for other strategies and supported options
13 |
14 | platform_init(@target)
15 |
16 | webengine_kiosk_opts =
17 | Application.get_all_env(:webengine_kiosk)
18 |
19 | children = [
20 | {WebengineKiosk, {webengine_kiosk_opts, [name: Display]}}
21 | | children(@target)]
22 |
23 | opts = [strategy: :one_for_one, name: NervesKiosk.Supervisor]
24 | Supervisor.start_link(children, opts)
25 | end
26 |
27 | # List all child processes to be supervised
28 | def children("host") do
29 | [
30 | # Starts a worker by calling: NervesKiosk.Worker.start_link(arg)
31 | # {NervesKiosk.Worker, arg},
32 | ]
33 | end
34 |
35 | def children(_target) do
36 | [
37 | # Starts a worker by calling: NervesKiosk.Worker.start_link(arg)
38 | # {NervesKiosk.Worker, arg},
39 | ]
40 | end
41 |
42 | defp platform_init("host"), do: :ok
43 |
44 | defp platform_init(_target) do
45 | # Initialize udev
46 | :os.cmd('udevd -d');
47 | :os.cmd('udevadm trigger --type=subsystems --action=add');
48 | :os.cmd('udevadm trigger --type=devices --action=add');
49 | :os.cmd('udevadm settle --timeout=30');
50 | # Workaround a known bug with HTML5 canvas and rpi gpu
51 | System.put_env("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-gpu")
52 | System.put_env("QTWEBENGINE_REMOTE_DEBUGGING", "9222")
53 | MuonTrap.Daemon.start_link("socat",["tcp-listen:9223,fork", "tcp:localhost:9222"])
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/channels/home_channel.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.HomeChannel do
2 | use PhxKioskWeb, :channel
3 |
4 | require Logger
5 |
6 | @from_range 1..100
7 | @to_range 15..255
8 |
9 | def join("home:lobby", payload, socket) do
10 | if authorized?(payload) do
11 | send(self(), :after_join)
12 | {:ok, socket}
13 | else
14 | {:error, %{reason: "unauthorized"}}
15 | end
16 | end
17 |
18 | def handle_in("drawLine", params, socket) do
19 | broadcast! socket, "drawLine", params
20 | {:noreply, socket}
21 | end
22 |
23 | def handle_in("brightness", %{"value" => value} = payload, socket) do
24 | Logger.debug("Brightness: #{inspect value}")
25 | broadcast socket, "brightness", payload
26 |
27 | map_range(@from_range, @to_range, value)
28 | |> PhxKiosk.Backlight.set_brightness()
29 | {:noreply, socket}
30 | end
31 |
32 | # Channels can be used in a request/response fashion
33 | # by sending replies to requests from the client
34 | def handle_in("ping", payload, socket) do
35 | {:reply, {:ok, payload}, socket}
36 | end
37 |
38 | # It is also common to receive messages from the client and
39 | # broadcast to everyone in the current topic (home:lobby).
40 | def handle_in("shout", payload, socket) do
41 | broadcast socket, "shout", payload
42 | {:noreply, socket}
43 | end
44 |
45 | def handle_info(:after_join, socket) do
46 | brightness =
47 | map_range(@to_range, @from_range, PhxKiosk.Backlight.brightness())
48 | push(socket, "brightness", %{value: brightness})
49 | {:noreply, socket}
50 | end
51 |
52 | defp map_range(a1 .. a2, b1 .. b2, s) do
53 | b1 + (s - a1) * (b2 - b1) / (a2 - a1)
54 | end
55 |
56 | # Add authorization logic here as required.
57 | defp authorized?(_payload) do
58 | true
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/phx_kiosk/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :phx_kiosk, PhxKioskWeb.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: []
15 |
16 | # ## SSL Support
17 | #
18 | # In order to use HTTPS in development, a self-signed
19 | # certificate can be generated by running the following
20 | # command from your terminal:
21 | #
22 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem
23 | #
24 | # The `http:` config above can be replaced with:
25 | #
26 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"],
27 | #
28 | # If desired, both `http:` and `https:` keys can be
29 | # configured to run both http and https servers on
30 | # different ports.
31 |
32 | # Watch static and templates for browser reloading.
33 | config :phx_kiosk, PhxKioskWeb.Endpoint,
34 | live_reload: [
35 | patterns: [
36 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
37 | ~r{priv/gettext/.*(po)$},
38 | ~r{lib/phx_kiosk_web/views/.*(ex)$},
39 | ~r{lib/phx_kiosk_web/channels/.*(ex)$},
40 | ~r{lib/phx_kiosk_web/templates/.*(eex)$}
41 | ]
42 | ]
43 |
44 | # Do not include metadata nor timestamps in development logs
45 | config :logger, :console, format: "[$level] $message\n"
46 |
47 | # Set a higher stacktrace during development. Avoid configuring such
48 | # in production as building large stacktraces may be expensive.
49 | config :phoenix, :stacktrace_depth, 20
50 |
--------------------------------------------------------------------------------
/nerves_kiosk/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule NervesKiosk.MixProject do
2 | use Mix.Project
3 |
4 | @target System.get_env("MIX_TARGET") || "host"
5 |
6 | def project do
7 | [
8 | app: :nerves_kiosk,
9 | version: "0.1.0",
10 | elixir: "~> 1.6",
11 | target: @target,
12 | archives: [nerves_bootstrap: "~> 1.0"],
13 | deps_path: "deps/#{@target}",
14 | build_path: "_build/#{@target}",
15 | lockfile: "mix.lock.#{@target}",
16 | start_permanent: Mix.env() == :prod,
17 | aliases: [loadconfig: [&bootstrap/1]],
18 | deps: deps()
19 | ]
20 | end
21 |
22 | # Starting nerves_bootstrap adds the required aliases to Mix.Project.config()
23 | # Aliases are only added if MIX_TARGET is set.
24 | def bootstrap(args) do
25 | Application.start(:nerves_bootstrap)
26 | Mix.Task.run("loadconfig", args)
27 | end
28 |
29 | # Run "mix help compile.app" to learn about applications.
30 | def application do
31 | [
32 | mod: {NervesKiosk.Application, []},
33 | extra_applications: [:logger, :runtime_tools]
34 | ]
35 | end
36 |
37 | # Run "mix help deps" to learn about dependencies.
38 | defp deps do
39 | [
40 | {:nerves, "~> 1.3", runtime: false},
41 | {:shoehorn, "~> 0.4"},
42 | {:ring_logger, "~> 0.4"},
43 | {:webengine_kiosk, "~> 0.1"},
44 | {:phx_kiosk, path: "../phx_kiosk"}
45 | ] ++ deps(@target)
46 | end
47 |
48 | # Specify target specific dependencies
49 | defp deps("host"), do: []
50 |
51 | defp deps(target) do
52 | [
53 | {:nerves_runtime, "~> 0.6"},
54 | {:nerves_init_gadget, "~> 0.4"},
55 | {:nerves_time, "~> 0.2"}
56 | ] ++ system(target)
57 | end
58 |
59 | defp system("rpi3"), do: [{:kiosk_system_rpi3, "~> 1.0", runtime: false}]
60 | defp system(target), do: Mix.raise("Unknown MIX_TARGET: #{target}")
61 | end
62 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use PhxKioskWeb, :controller
9 | use PhxKioskWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: PhxKioskWeb
23 | import Plug.Conn
24 | import PhxKioskWeb.Router.Helpers
25 | import PhxKioskWeb.Gettext
26 | end
27 | end
28 |
29 | def view do
30 | quote do
31 | use Phoenix.View, root: "lib/phx_kiosk_web/templates",
32 | namespace: PhxKioskWeb
33 |
34 | # Import convenience functions from controllers
35 | import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
36 |
37 | # Use all HTML functionality (forms, tags, etc)
38 | use Phoenix.HTML
39 |
40 | import PhxKioskWeb.Router.Helpers
41 | import PhxKioskWeb.ErrorHelpers
42 | import PhxKioskWeb.Gettext
43 | end
44 | end
45 |
46 | def router do
47 | quote do
48 | use Phoenix.Router
49 | import Plug.Conn
50 | import Phoenix.Controller
51 | end
52 | end
53 |
54 | def channel do
55 | quote do
56 | use Phoenix.Channel
57 | import PhxKioskWeb.Gettext
58 | end
59 | end
60 |
61 | @doc """
62 | When used, dispatch to the appropriate controller/view/etc.
63 | """
64 | defmacro __using__(which) when is_atom(which) do
65 | apply(__MODULE__, which, [])
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/phx_kiosk/lib/phx_kiosk_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule PhxKioskWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :phx_kiosk
3 |
4 | socket "/socket", PhxKioskWeb.UserSocket
5 | socket "/wobserver", Wobserver.Web.PhoenixSocket
6 |
7 | # Serve at "/" the static files from "priv/static" directory.
8 | #
9 | # You should set gzip to true if you are running phoenix.digest
10 | # when deploying your static files in production.
11 | plug Plug.Static,
12 | at: "/", from: :phx_kiosk, gzip: false,
13 | only: ~w(css fonts images js favicon.ico robots.txt)
14 |
15 | # Code reloading can be explicitly enabled under the
16 | # :code_reloader configuration of your endpoint.
17 | if code_reloading? do
18 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
19 | plug Phoenix.LiveReloader
20 | plug Phoenix.CodeReloader
21 | end
22 |
23 | plug Plug.Logger
24 |
25 | plug Plug.Parsers,
26 | parsers: [:urlencoded, :multipart, :json],
27 | pass: ["*/*"],
28 | json_decoder: Poison
29 |
30 | plug Plug.MethodOverride
31 | plug Plug.Head
32 |
33 | # The session will be stored in the cookie and signed,
34 | # this means its contents can be read but not tampered with.
35 | # Set :encryption_salt if you would also like to encrypt it.
36 | plug Plug.Session,
37 | store: :cookie,
38 | key: "_phx_kiosk_key",
39 | signing_salt: "JdtSB36j"
40 |
41 | plug PhxKioskWeb.Router
42 |
43 | @doc """
44 | Callback invoked for dynamically configuring the endpoint.
45 |
46 | It receives the endpoint configuration and checks if
47 | configuration should be loaded from the system environment.
48 | """
49 | def init(_key, config) do
50 | if config[:load_from_system_env] do
51 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
52 | {:ok, Keyword.put(config, :http, [:inet6, port: port])}
53 | else
54 | {:ok, config}
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/phx_kiosk/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we often load configuration from external
4 | # sources, such as your system environment. For this reason,
5 | # you won't find the :http configuration below, but set inside
6 | # PhxKioskWeb.Endpoint.init/2 when load_from_system_env is
7 | # true. Any dynamic configuration should be done there.
8 | #
9 | # Don't forget to configure the url host to something meaningful,
10 | # Phoenix uses this information when generating URLs.
11 | #
12 | # Finally, we also include the path to a cache manifest
13 | # containing the digested version of static files. This
14 | # manifest is generated by the mix phx.digest task
15 | # which you typically run after static files are built.
16 | config :phx_kiosk, PhxKioskWeb.Endpoint,
17 | load_from_system_env: true,
18 | url: [host: "example.com", port: 80],
19 | cache_static_manifest: "priv/static/cache_manifest.json"
20 |
21 | # Do not print debug messages in production
22 | config :logger, level: :info
23 |
24 | # ## SSL Support
25 | #
26 | # To get SSL working, you will need to add the `https` key
27 | # to the previous section and set your `:url` port to 443:
28 | #
29 | # config :phx_kiosk, PhxKioskWeb.Endpoint,
30 | # ...
31 | # url: [host: "example.com", port: 443],
32 | # https: [:inet6,
33 | # port: 443,
34 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
35 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
36 | #
37 | # Where those two env variables return an absolute path to
38 | # the key and cert in disk or a relative path inside priv,
39 | # for example "priv/ssl/server.key".
40 | #
41 | # We also recommend setting `force_ssl`, ensuring no data is
42 | # ever sent via http, always redirecting to https:
43 | #
44 | # config :phx_kiosk, PhxKioskWeb.Endpoint,
45 | # force_ssl: [hsts: true]
46 | #
47 | # Check `Plug.SSL` for all available options in `force_ssl`.
48 |
49 | # ## Using releases
50 | #
51 | # If you are doing OTP releases, you need to instruct Phoenix
52 | # to start the server for all endpoints:
53 | #
54 | # config :phoenix, :serve_endpoints, true
55 | #
56 | # Alternatively, you can configure exactly which server to
57 | # start per endpoint:
58 | #
59 | # config :phx_kiosk, PhxKioskWeb.Endpoint, server: true
60 | #
61 |
62 | # Finally import the config/prod.secret.exs
63 | # which should be versioned separately.
64 | import_config "prod.secret.exs"
65 |
--------------------------------------------------------------------------------
/nerves_kiosk/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | #
4 | # This configuration file is loaded before any dependency and
5 | # is restricted to this project.
6 | use Mix.Config
7 |
8 | # Customize non-Elixir parts of the firmware. See
9 | # https://hexdocs.pm/nerves/advanced-configuration.html for details.
10 | config :nerves, :firmware, rootfs_overlay: "rootfs_overlay"
11 |
12 | # Use shoehorn to start the main application. See the shoehorn
13 | # docs for separating out critical OTP applications such as those
14 | # involved with firmware updates.
15 | config :shoehorn,
16 | init: [:nerves_runtime, :nerves_init_gadget],
17 | app: Mix.Project.config()[:app]
18 |
19 | # Use Ringlogger as the logger backend and remove :console.
20 | # See https://hexdocs.pm/ring_logger/readme.html for more information on
21 | # configuring ring_logger.
22 |
23 | config :logger, backends: [RingLogger]
24 |
25 | # Authorize the device to receive firmware using your public key.
26 | # See https://hexdocs.pm/nerves_firmware_ssh/readme.html for more information
27 | # on configuring nerves_firmware_ssh.
28 |
29 | key = Path.join(System.user_home!, ".ssh/id_rsa.pub")
30 | unless File.exists?(key), do:
31 | Mix.raise("No SSH Keys found. Please generate an ssh key")
32 |
33 | config :nerves_firmware_ssh,
34 | authorized_keys: [
35 | File.read!(key)
36 | ]
37 |
38 | # Configure nerves_init_gadget.
39 | # See https://hexdocs.pm/nerves_init_gadget/readme.html for more information.
40 |
41 | config :nerves_init_gadget,
42 | ifname: "eth0",
43 | address_method: :dhcpd,
44 | mdns_domain: "nerves.local",
45 | node_name: "kiosk",
46 | node_host: :mdns_domain,
47 | ssh_console_port: 22
48 |
49 | config :webengine_kiosk,
50 | uid: "kiosk",
51 | gid: "kiosk",
52 | data_dir: "/root/kiosk",
53 | homepage: "http://localhost"
54 |
55 | config :phx_kiosk, PhxKioskWeb.Endpoint,
56 | url: [host: "localhost"],
57 | http: [port: 80],
58 | secret_key_base: "123456",
59 | root: Path.dirname(__DIR__),
60 | server: true,
61 | render_errors: [view: PhxKioskWeb.ErrorView,
62 | accepts: ~w(html json)],
63 | pubsub: [name: Nerves.PubSub, adapter: Phoenix.PubSub.PG2],
64 | code_reloader: false,
65 | check_origin: false
66 |
67 | # Import target specific config. This must remain at the bottom
68 | # of this file so it overrides the configuration defined above.
69 | # Uncomment to use target specific configurations
70 |
71 | # import_config "#{Mix.Project.config[:target]}.exs"
72 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/static/js/app.js:
--------------------------------------------------------------------------------
1 | // for phoenix_html support, including form and button helpers
2 | // copy the following scripts into your javascript bundle:
3 | // * https://raw.githubusercontent.com/phoenixframework/phoenix_html/v2.10.0/priv/static/phoenix_html.js
4 |
5 | var socket = null;
6 | var channel = null;
7 |
8 | socket = new Phoenix.Socket("/socket");
9 | socket.connect();
10 |
11 | channel = socket.channel("home:lobby")
12 | channel.join()
13 | .receive("ok", resp =>
14 | { console.log("Joined successfully", resp) })
15 | .receive("error", resp =>
16 | { console.log("Unable to join", resp) })
17 |
18 | var brightnessSlider = document.getElementById("brightnessRange");
19 |
20 | channel.on("brightness", payload => {
21 | brightnessSlider.value = payload.value;
22 | })
23 |
24 | brightnessSlider.oninput = function() {
25 | channel.push("brightness", {value: parseInt(this.value)})
26 | console.log("Brightness:" + this.value);
27 | }
28 |
29 | var canvas = document.getElementById("draw-canvas");
30 | var ctx = canvas.getContext("2d");
31 | function drawLine(from, to) {
32 | // Send a message to draw a line (we'll draw when we receive it)
33 | channel.push("drawLine", {line: [from, to]})
34 | }
35 | // Draw whatever we receive
36 | channel.on("drawLine", payload => {
37 | var from = payload.line[0];
38 | var to = payload.line[1];
39 | ctx.moveTo(from.x, from.y);
40 | ctx.lineTo(to.x, to.y);
41 | ctx.stroke();
42 | })
43 |
44 | var lastPoints = {};
45 | function moveTo(identifier, point) {
46 | lastPoints[identifier] = point;
47 | }
48 | function lineTo(identifier, point) {
49 | if (lastPoints[identifier]) {
50 | drawLine(lastPoints[identifier], point);
51 | }
52 | lastPoints[identifier] = point;
53 | }
54 | function getPos(client) {
55 | var rect = canvas.getBoundingClientRect();
56 | return {
57 | x: client.clientX - rect.left,
58 | y: client.clientY - rect.top
59 | };
60 | }
61 | function haltEventBefore(handler) {
62 | return function(event) {
63 | event.stopPropagation();
64 | event.preventDefault();
65 | handler(event);
66 | }
67 | }
68 | // ----------------
69 | // Touch Handling
70 | // ----------------
71 | var mouseDown = false;
72 | canvas.addEventListener('mousedown', haltEventBefore(function(event) {
73 | mouseDown = true;
74 | moveTo("mouse", getPos(event));
75 | }));
76 | document.documentElement.addEventListener('mouseup', function(event) {
77 | mouseDown = false;
78 | });
79 | canvas.addEventListener('mousemove', haltEventBefore(function(event) {
80 | if (!mouseDown) return;
81 | lineTo("mouse", getPos(event));
82 | }));
83 | canvas.addEventListener('mouseleave', haltEventBefore(function(event) {
84 | if (!mouseDown) return;
85 | lineTo("mouse", getPos(event));
86 | }));
87 | canvas.addEventListener('mouseenter', haltEventBefore(function(event) {
88 | if (!mouseDown) return;
89 | moveTo("mouse", getPos(event));
90 | }));
91 | // ----------------
92 | // Touch Handling
93 | // ----------------
94 | function handleTouchesWith(func) {
95 | return haltEventBefore(function(event) {
96 | for (var i = 0; i < event.changedTouches.length; i++) {
97 | var touch = event.changedTouches[i];
98 | func(touch.identifier, getPos(touch));
99 | }
100 | });
101 | };
102 | canvas.addEventListener('touchstart', handleTouchesWith(moveTo));
103 | canvas.addEventListener('touchmove', handleTouchesWith(lineTo));
104 | canvas.addEventListener('touchend', handleTouchesWith(lineTo));
105 | canvas.addEventListener('touchcancel', handleTouchesWith(moveTo));
106 |
--------------------------------------------------------------------------------
/phx_kiosk/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
3 | "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
4 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
5 | "file_system": {:hex, :file_system, "0.2.6", "fd4dc3af89b9ab1dc8ccbcc214a0e60c41f34be251d9307920748a14bf41f1d3", [:mix], [], "hexpm"},
6 | "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
7 | "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
8 | "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
9 | "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
10 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
11 | "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
12 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
13 | "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
14 | "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
15 | "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
16 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.5", "8d4c9b1ef9ca82deee6deb5a038d6d8d7b34b9bb909d99784a49332e0d15b3dc", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
17 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.0", "d55e25ff1ff8ea2f9964638366dfd6e361c52dedfd50019353598d11d4441d14", [:mix], [], "hexpm"},
18 | "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
19 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
20 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
21 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
22 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
23 | "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
24 | "wobserver": {:hex, :wobserver, "0.1.8", "3ed5ea55478627f0593800ab83919b71f41fd426ec344e71cf1d975e6d2065b8", [:mix], [{:cowboy, "~> 1.1", [hex: :cowboy, repo: "hexpm", optional: false]}, {:httpoison, "~> 0.11 or ~> 0.12", [hex: :httpoison, repo: "hexpm", optional: false]}, {:plug, "~> 1.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.0 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.2", [hex: :websocket_client, repo: "hexpm", optional: false]}], "hexpm"},
25 | }
26 |
--------------------------------------------------------------------------------
/nerves_kiosk/mix.lock.rpi3:
--------------------------------------------------------------------------------
1 | %{
2 | "artificery": {:hex, :artificery, "0.2.6", "f602909757263f7897130cbd006b0e40514a541b148d366ad65b89236b93497a", [:mix], [], "hexpm"},
3 | "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
4 | "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
5 | "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"},
6 | "distillery": {:hex, :distillery, "2.0.9", "1e03e1bd97c0cd1187ce5882143fcc44812aa54d8c61b093801fab053bf07c98", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"},
7 | "dns": {:hex, :dns, "2.1.0", "4777fe07ae3060c1d5d75024f05c26d7e11fa701d48a6edb9fc305d24cd12c8c", [:mix], [{:socket, "~> 0.3.13", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"},
8 | "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"},
9 | "gettext": {:hex, :gettext, "0.16.0", "4a7e90408cef5f1bf57c5a39e2db8c372a906031cc9b1466e963101cb927dafc", [:mix], [], "hexpm"},
10 | "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
11 | "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
12 | "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
13 | "kiosk_system_rpi3": {:hex, :kiosk_system_rpi3, "1.3.2", "4a369cb9ca296070d634033c81b1ce1529594183aca983b0258180e2e6a814e9", [:mix], [{:nerves, "~> 1.3", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.4.5", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
14 | "mdns": {:hex, :mdns, "1.0.2", "c8228dd44d3fdd55e9842cb7111c9145f2eeaa8b7adac75012ee0e250962215e", [:mix], [{:dns, "~> 2.0", [hex: :dns, repo: "hexpm", optional: false]}], "hexpm"},
15 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
16 | "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"},
17 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
18 | "muontrap": {:hex, :muontrap, "0.4.0", "f3c48f5e2cbb89b6406d28e488fbd0da1ce0ca00af332860913999befca9688a", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
19 | "nerves": {:hex, :nerves, "1.3.0", "473b95afcd7a7211d33ec1406291a64f2bf4980862f93123e55b21946ae7a2f7", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
20 | "nerves_firmware_ssh": {:hex, :nerves_firmware_ssh, "0.3.3", "79c42303ddbfd89ae6f5b4b19a4397a6188df21ca0e7a6573c2399e081fb7d25", [:mix], [{:nerves_runtime, "~> 0.4", [hex: :nerves_runtime, repo: "hexpm", optional: false]}], "hexpm"},
21 | "nerves_init_gadget": {:hex, :nerves_init_gadget, "0.5.0", "bace803e1ac3c0ded139eee60dcbfe9ac69b125d9ce724a7098801819d360523", [:mix], [{:mdns, "~> 1.0", [hex: :mdns, repo: "hexpm", optional: false]}, {:nerves_firmware_ssh, "~> 0.2", [hex: :nerves_firmware_ssh, repo: "hexpm", optional: false]}, {:nerves_network, "~> 0.3", [hex: :nerves_network, repo: "hexpm", optional: false]}, {:nerves_runtime, "~> 0.3", [hex: :nerves_runtime, repo: "hexpm", optional: false]}, {:one_dhcpd, "~> 0.1", [hex: :one_dhcpd, repo: "hexpm", optional: false]}, {:ring_logger, "~> 0.4", [hex: :ring_logger, repo: "hexpm", optional: false]}], "hexpm"},
22 | "nerves_network": {:hex, :nerves_network, "0.3.7", "200767191b1ded5a61cddbacd3efdce92442cc055bdc37c20ca8c7cb1d964098", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nerves_network_interface, "~> 0.4.4", [hex: :nerves_network_interface, repo: "hexpm", optional: false]}, {:nerves_wpa_supplicant, "~> 0.3", [hex: :nerves_wpa_supplicant, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.7", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"},
23 | "nerves_network_interface": {:hex, :nerves_network_interface, "0.4.4", "200b1a84bc1a7fdeaf3a1e0e2d4e9b33e240b034e73f39372768d43f8690bae0", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
24 | "nerves_runtime": {:hex, :nerves_runtime, "0.6.5", "7b43e555be567252b60fd927efe295881e79328fc19b03a1b69dbe4fbf78249f", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.5", [hex: :system_registry, repo: "hexpm", optional: false]}], "hexpm"},
25 | "nerves_system_br": {:hex, :nerves_system_br, "1.4.5", "6c154b372c318522026c0bad7f6146c6d36fe3a1c09b57975489f7b7e824c142", [:mix], [], "hexpm"},
26 | "nerves_system_linter": {:hex, :nerves_system_linter, "0.3.0", "84e0f63c8ac196b16b77608bbe7df66dcf352845c4e4fb394bffd2b572025413", [:mix], [], "hexpm"},
27 | "nerves_system_rpi3": {:hex, :nerves_system_rpi3, "1.4.1", "d2c1b5b2a4a18e55593b310167b0319011aa587ff37ab9b552315ef85a3fe563", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_system_br, "1.4.5", [hex: :nerves_system_br, repo: "hexpm", optional: false]}, {:nerves_system_linter, "~> 0.3.0", [hex: :nerves_system_linter, repo: "hexpm", optional: false]}, {:nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", [hex: :nerves_toolchain_arm_unknown_linux_gnueabihf, repo: "hexpm", optional: false]}], "hexpm"},
28 | "nerves_toolchain_arm_unknown_linux_gnueabihf": {:hex, :nerves_toolchain_arm_unknown_linux_gnueabihf, "1.1.0", "ca466a656f8653346a8551a35743f7c41046f3d53e945723e970cb4a7811e617", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}, {:nerves_toolchain_ctng, "~> 1.5.0", [hex: :nerves_toolchain_ctng, repo: "hexpm", optional: false]}], "hexpm"},
29 | "nerves_toolchain_ctng": {:hex, :nerves_toolchain_ctng, "1.5.0", "34b8f5664858ff6ce09730b26221441398acd1fa361b8c6d744d9ec18238c16b", [:mix], [{:nerves, "~> 1.0", [hex: :nerves, repo: "hexpm", optional: false]}], "hexpm"},
30 | "nerves_wpa_supplicant": {:hex, :nerves_wpa_supplicant, "0.3.3", "91b8505c023e891561ac6c64b9955450aa67fb16b523b18db5eeb4b4bcf30dc7", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
31 | "one_dhcpd": {:hex, :one_dhcpd, "0.2.0", "18eb8ce7101ad7b79e67f3d7ee7f648f42e02b8fa4c1cb3f24f403bf6860f81d", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"},
32 | "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
33 | "phoenix": {:hex, :phoenix, "1.3.4", "aaa1b55e5523083a877bcbe9886d9ee180bf2c8754905323493c2ac325903dc5", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
34 | "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
35 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.0", "d55e25ff1ff8ea2f9964638366dfd6e361c52dedfd50019353598d11d4441d14", [:mix], [], "hexpm"},
36 | "plug": {:hex, :plug, "1.6.2", "e06a7bd2bb6de5145da0dd950070110dce88045351224bd98e84edfdaaf5ffee", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"},
37 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
38 | "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"},
39 | "ring_logger": {:hex, :ring_logger, "0.4.1", "db972365bfda705288d7629e80af5704a1aafdbe9da842712c3cdd587639c72e", [:mix], [], "hexpm"},
40 | "shoehorn": {:hex, :shoehorn, "0.4.0", "f3830e22e1c58b502e8c436623804c4eb6ed15f5d0bdbacdeb448cddf4795951", [:mix], [{:distillery, "~> 2.0", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"},
41 | "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm"},
42 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
43 | "system_registry": {:hex, :system_registry, "0.8.0", "09240347628b001433d18279a2759ef7237ba7361239890d8c599cca9a2fbbc2", [:mix], [], "hexpm"},
44 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
45 | "webengine_kiosk": {:hex, :webengine_kiosk, "0.2.2", "36086dfce530940fc3513e8109e9b40a846150d3d1c687d73728769781aada6b", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:system_registry, "~> 0.8", [hex: :system_registry, repo: "hexpm", optional: true]}], "hexpm"},
46 | "websocket_client": {:hex, :websocket_client, "1.3.0", "2275d7daaa1cdacebf2068891c9844b15f4fdc3de3ec2602420c2fb486db59b6", [:rebar3], [], "hexpm"},
47 | "wobserver": {:hex, :wobserver, "0.1.8", "3ed5ea55478627f0593800ab83919b71f41fd426ec344e71cf1d975e6d2065b8", [:mix], [{:cowboy, "~> 1.1", [hex: :cowboy, repo: "hexpm", optional: false]}, {:httpoison, "~> 0.11 or ~> 0.12", [hex: :httpoison, repo: "hexpm", optional: false]}, {:plug, "~> 1.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 2.0 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}, {:websocket_client, "~> 1.2", [hex: :websocket_client, repo: "hexpm", optional: false]}], "hexpm"},
48 | }
49 |
--------------------------------------------------------------------------------
/phx_kiosk/priv/static/js/phoenix.js:
--------------------------------------------------------------------------------
1 | (function (global, factory) {
2 | typeof exports === 'object' ? factory(exports) :
3 | typeof define === 'function' && define.amd ? define(['exports'], factory) :
4 | factory(global.Phoenix = global.Phoenix || {});
5 | }(this, (function (exports) {
6 | "use strict";
7 |
8 | Object.defineProperty(exports, "__esModule", {
9 | value: true
10 | });
11 |
12 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
13 |
14 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
15 |
16 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
17 |
18 | function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
19 |
20 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
21 |
22 | /**
23 | * Phoenix Channels JavaScript client
24 | *
25 | * ## Socket Connection
26 | *
27 | * A single connection is established to the server and
28 | * channels are multiplexed over the connection.
29 | * Connect to the server using the `Socket` class:
30 | *
31 | * ```javascript
32 | * let socket = new Socket("/socket", {params: {userToken: "123"}})
33 | * socket.connect()
34 | * ```
35 | *
36 | * The `Socket` constructor takes the mount point of the socket,
37 | * the authentication params, as well as options that can be found in
38 | * the Socket docs, such as configuring the `LongPoll` transport, and
39 | * heartbeat.
40 | *
41 | * ## Channels
42 | *
43 | * Channels are isolated, concurrent processes on the server that
44 | * subscribe to topics and broker events between the client and server.
45 | * To join a channel, you must provide the topic, and channel params for
46 | * authorization. Here's an example chat room example where `"new_msg"`
47 | * events are listened for, messages are pushed to the server, and
48 | * the channel is joined with ok/error/timeout matches:
49 | *
50 | * ```javascript
51 | * let channel = socket.channel("room:123", {token: roomToken})
52 | * channel.on("new_msg", msg => console.log("Got message", msg) )
53 | * $input.onEnter( e => {
54 | * channel.push("new_msg", {body: e.target.val}, 10000)
55 | * .receive("ok", (msg) => console.log("created message", msg) )
56 | * .receive("error", (reasons) => console.log("create failed", reasons) )
57 | * .receive("timeout", () => console.log("Networking issue...") )
58 | * })
59 | * channel.join()
60 | * .receive("ok", ({messages}) => console.log("catching up", messages) )
61 | * .receive("error", ({reason}) => console.log("failed join", reason) )
62 | * .receive("timeout", () => console.log("Networking issue. Still waiting...") )
63 | *```
64 | *
65 | * ## Joining
66 | *
67 | * Creating a channel with `socket.channel(topic, params)`, binds the params to
68 | * `channel.params`, which are sent up on `channel.join()`.
69 | * Subsequent rejoins will send up the modified params for
70 | * updating authorization params, or passing up last_message_id information.
71 | * Successful joins receive an "ok" status, while unsuccessful joins
72 | * receive "error".
73 | *
74 | * ## Duplicate Join Subscriptions
75 | *
76 | * While the client may join any number of topics on any number of channels,
77 | * the client may only hold a single subscription for each unique topic at any
78 | * given time. When attempting to create a duplicate subscription,
79 | * the server will close the existing channel, log a warning, and
80 | * spawn a new channel for the topic. The client will have their
81 | * `channel.onClose` callbacks fired for the existing channel, and the new
82 | * channel join will have its receive hooks processed as normal.
83 | *
84 | * ## Pushing Messages
85 | *
86 | * From the previous example, we can see that pushing messages to the server
87 | * can be done with `channel.push(eventName, payload)` and we can optionally
88 | * receive responses from the push. Additionally, we can use
89 | * `receive("timeout", callback)` to abort waiting for our other `receive` hooks
90 | * and take action after some period of waiting. The default timeout is 5000ms.
91 | *
92 | *
93 | * ## Socket Hooks
94 | *
95 | * Lifecycle events of the multiplexed connection can be hooked into via
96 | * `socket.onError()` and `socket.onClose()` events, ie:
97 | *
98 | * ```javascript
99 | * socket.onError( () => console.log("there was an error with the connection!") )
100 | * socket.onClose( () => console.log("the connection dropped") )
101 | * ```
102 | *
103 | *
104 | * ## Channel Hooks
105 | *
106 | * For each joined channel, you can bind to `onError` and `onClose` events
107 | * to monitor the channel lifecycle, ie:
108 | *
109 | * ```javascript
110 | * channel.onError( () => console.log("there was an error!") )
111 | * channel.onClose( () => console.log("the channel has gone away gracefully") )
112 | * ```
113 | *
114 | * ### onError hooks
115 | *
116 | * `onError` hooks are invoked if the socket connection drops, or the channel
117 | * crashes on the server. In either case, a channel rejoin is attempted
118 | * automatically in an exponential backoff manner.
119 | *
120 | * ### onClose hooks
121 | *
122 | * `onClose` hooks are invoked only in two cases. 1) the channel explicitly
123 | * closed on the server, or 2). The client explicitly closed, by calling
124 | * `channel.leave()`
125 | *
126 | *
127 | * ## Presence
128 | *
129 | * The `Presence` object provides features for syncing presence information
130 | * from the server with the client and handling presences joining and leaving.
131 | *
132 | * ### Syncing initial state from the server
133 | *
134 | * `Presence.syncState` is used to sync the list of presences on the server
135 | * with the client's state. An optional `onJoin` and `onLeave` callback can
136 | * be provided to react to changes in the client's local presences across
137 | * disconnects and reconnects with the server.
138 | *
139 | * `Presence.syncDiff` is used to sync a diff of presence join and leave
140 | * events from the server, as they happen. Like `syncState`, `syncDiff`
141 | * accepts optional `onJoin` and `onLeave` callbacks to react to a user
142 | * joining or leaving from a device.
143 | *
144 | * ### Listing Presences
145 | *
146 | * `Presence.list` is used to return a list of presence information
147 | * based on the local state of metadata. By default, all presence
148 | * metadata is returned, but a `listBy` function can be supplied to
149 | * allow the client to select which metadata to use for a given presence.
150 | * For example, you may have a user online from different devices with
151 | * a metadata status of "online", but they have set themselves to "away"
152 | * on another device. In this case, the app may choose to use the "away"
153 | * status for what appears on the UI. The example below defines a `listBy`
154 | * function which prioritizes the first metadata which was registered for
155 | * each user. This could be the first tab they opened, or the first device
156 | * they came online from:
157 | *
158 | * ```javascript
159 | * let state = {}
160 | * state = Presence.syncState(state, stateFromServer)
161 | * let listBy = (id, {metas: [first, ...rest]}) => {
162 | * first.count = rest.length + 1 // count of this user's presences
163 | * first.id = id
164 | * return first
165 | * }
166 | * let onlineUsers = Presence.list(state, listBy)
167 | * ```
168 | *
169 | *
170 | * ### Example Usage
171 | *```javascript
172 | * // detect if user has joined for the 1st time or from another tab/device
173 | * let onJoin = (id, current, newPres) => {
174 | * if(!current){
175 | * console.log("user has entered for the first time", newPres)
176 | * } else {
177 | * console.log("user additional presence", newPres)
178 | * }
179 | * }
180 | * // detect if user has left from all tabs/devices, or is still present
181 | * let onLeave = (id, current, leftPres) => {
182 | * if(current.metas.length === 0){
183 | * console.log("user has left from all devices", leftPres)
184 | * } else {
185 | * console.log("user left from a device", leftPres)
186 | * }
187 | * }
188 | * let presences = {} // client's initial empty presence state
189 | * // receive initial presence data from server, sent after join
190 | * myChannel.on("presence_state", state => {
191 | * presences = Presence.syncState(presences, state, onJoin, onLeave)
192 | * displayUsers(Presence.list(presences))
193 | * })
194 | * // receive "presence_diff" from server, containing join/leave events
195 | * myChannel.on("presence_diff", diff => {
196 | * presences = Presence.syncDiff(presences, diff, onJoin, onLeave)
197 | * this.setState({users: Presence.list(room.presences, listBy)})
198 | * })
199 | * ```
200 | * @module phoenix
201 | */
202 |
203 | var VSN = "2.0.0";
204 | var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
205 | var DEFAULT_TIMEOUT = 10000;
206 | var WS_CLOSE_NORMAL = 1000;
207 | var CHANNEL_STATES = {
208 | closed: "closed",
209 | errored: "errored",
210 | joined: "joined",
211 | joining: "joining",
212 | leaving: "leaving"
213 | };
214 | var CHANNEL_EVENTS = {
215 | close: "phx_close",
216 | error: "phx_error",
217 | join: "phx_join",
218 | reply: "phx_reply",
219 | leave: "phx_leave"
220 | };
221 | var CHANNEL_LIFECYCLE_EVENTS = [CHANNEL_EVENTS.close, CHANNEL_EVENTS.error, CHANNEL_EVENTS.join, CHANNEL_EVENTS.reply, CHANNEL_EVENTS.leave];
222 | var TRANSPORTS = {
223 | longpoll: "longpoll",
224 | websocket: "websocket"
225 | };
226 |
227 | /**
228 | * Initializes the Push
229 | * @param {Channel} channel - The Channel
230 | * @param {string} event - The event, for example `"phx_join"`
231 | * @param {Object} payload - The payload, for example `{user_id: 123}`
232 | * @param {number} timeout - The push timeout in milliseconds
233 | */
234 |
235 | var Push = function () {
236 | function Push(channel, event, payload, timeout) {
237 | _classCallCheck(this, Push);
238 |
239 | this.channel = channel;
240 | this.event = event;
241 | this.payload = payload || {};
242 | this.receivedResp = null;
243 | this.timeout = timeout;
244 | this.timeoutTimer = null;
245 | this.recHooks = [];
246 | this.sent = false;
247 | }
248 |
249 | /**
250 | *
251 | * @param {number} timeout
252 | */
253 |
254 |
255 | _createClass(Push, [{
256 | key: "resend",
257 | value: function resend(timeout) {
258 | this.timeout = timeout;
259 | this.reset();
260 | this.send();
261 | }
262 |
263 | /**
264 | *
265 | */
266 |
267 | }, {
268 | key: "send",
269 | value: function send() {
270 | if (this.hasReceived("timeout")) {
271 | return;
272 | }
273 | this.startTimeout();
274 | this.sent = true;
275 | this.channel.socket.push({
276 | topic: this.channel.topic,
277 | event: this.event,
278 | payload: this.payload,
279 | ref: this.ref,
280 | join_ref: this.channel.joinRef()
281 | });
282 | }
283 |
284 | /**
285 | *
286 | * @param {*} status
287 | * @param {*} callback
288 | */
289 |
290 | }, {
291 | key: "receive",
292 | value: function receive(status, callback) {
293 | if (this.hasReceived(status)) {
294 | callback(this.receivedResp.response);
295 | }
296 |
297 | this.recHooks.push({ status: status, callback: callback });
298 | return this;
299 | }
300 |
301 | // private
302 |
303 | }, {
304 | key: "reset",
305 | value: function reset() {
306 | this.cancelRefEvent();
307 | this.ref = null;
308 | this.refEvent = null;
309 | this.receivedResp = null;
310 | this.sent = false;
311 | }
312 | }, {
313 | key: "matchReceive",
314 | value: function matchReceive(_ref) {
315 | var status = _ref.status,
316 | response = _ref.response,
317 | ref = _ref.ref;
318 |
319 | this.recHooks.filter(function (h) {
320 | return h.status === status;
321 | }).forEach(function (h) {
322 | return h.callback(response);
323 | });
324 | }
325 | }, {
326 | key: "cancelRefEvent",
327 | value: function cancelRefEvent() {
328 | if (!this.refEvent) {
329 | return;
330 | }
331 | this.channel.off(this.refEvent);
332 | }
333 | }, {
334 | key: "cancelTimeout",
335 | value: function cancelTimeout() {
336 | clearTimeout(this.timeoutTimer);
337 | this.timeoutTimer = null;
338 | }
339 | }, {
340 | key: "startTimeout",
341 | value: function startTimeout() {
342 | var _this = this;
343 |
344 | if (this.timeoutTimer) {
345 | this.cancelTimeout();
346 | }
347 | this.ref = this.channel.socket.makeRef();
348 | this.refEvent = this.channel.replyEventName(this.ref);
349 |
350 | this.channel.on(this.refEvent, function (payload) {
351 | _this.cancelRefEvent();
352 | _this.cancelTimeout();
353 | _this.receivedResp = payload;
354 | _this.matchReceive(payload);
355 | });
356 |
357 | this.timeoutTimer = setTimeout(function () {
358 | _this.trigger("timeout", {});
359 | }, this.timeout);
360 | }
361 | }, {
362 | key: "hasReceived",
363 | value: function hasReceived(status) {
364 | return this.receivedResp && this.receivedResp.status === status;
365 | }
366 | }, {
367 | key: "trigger",
368 | value: function trigger(status, response) {
369 | this.channel.trigger(this.refEvent, { status: status, response: response });
370 | }
371 | }]);
372 |
373 | return Push;
374 | }();
375 |
376 | /**
377 | *
378 | * @param {string} topic
379 | * @param {Object} params
380 | * @param {Socket} socket
381 | */
382 |
383 |
384 | var Channel = exports.Channel = function () {
385 | function Channel(topic, params, socket) {
386 | var _this2 = this;
387 |
388 | _classCallCheck(this, Channel);
389 |
390 | this.state = CHANNEL_STATES.closed;
391 | this.topic = topic;
392 | this.params = params || {};
393 | this.socket = socket;
394 | this.bindings = [];
395 | this.timeout = this.socket.timeout;
396 | this.joinedOnce = false;
397 | this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
398 | this.pushBuffer = [];
399 | this.rejoinTimer = new Timer(function () {
400 | return _this2.rejoinUntilConnected();
401 | }, this.socket.reconnectAfterMs);
402 | this.joinPush.receive("ok", function () {
403 | _this2.state = CHANNEL_STATES.joined;
404 | _this2.rejoinTimer.reset();
405 | _this2.pushBuffer.forEach(function (pushEvent) {
406 | return pushEvent.send();
407 | });
408 | _this2.pushBuffer = [];
409 | });
410 | this.onClose(function () {
411 | _this2.rejoinTimer.reset();
412 | _this2.socket.log("channel", "close " + _this2.topic + " " + _this2.joinRef());
413 | _this2.state = CHANNEL_STATES.closed;
414 | _this2.socket.remove(_this2);
415 | });
416 | this.onError(function (reason) {
417 | if (_this2.isLeaving() || _this2.isClosed()) {
418 | return;
419 | }
420 | _this2.socket.log("channel", "error " + _this2.topic, reason);
421 | _this2.state = CHANNEL_STATES.errored;
422 | _this2.rejoinTimer.scheduleTimeout();
423 | });
424 | this.joinPush.receive("timeout", function () {
425 | if (!_this2.isJoining()) {
426 | return;
427 | }
428 | _this2.socket.log("channel", "timeout " + _this2.topic + " (" + _this2.joinRef() + ")", _this2.joinPush.timeout);
429 | var leavePush = new Push(_this2, CHANNEL_EVENTS.leave, {}, _this2.timeout);
430 | leavePush.send();
431 | _this2.state = CHANNEL_STATES.errored;
432 | _this2.joinPush.reset();
433 | _this2.rejoinTimer.scheduleTimeout();
434 | });
435 | this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
436 | _this2.trigger(_this2.replyEventName(ref), payload);
437 | });
438 | }
439 |
440 | _createClass(Channel, [{
441 | key: "rejoinUntilConnected",
442 | value: function rejoinUntilConnected() {
443 | this.rejoinTimer.scheduleTimeout();
444 | if (this.socket.isConnected()) {
445 | this.rejoin();
446 | }
447 | }
448 | }, {
449 | key: "join",
450 | value: function join() {
451 | var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
452 |
453 | if (this.joinedOnce) {
454 | throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
455 | } else {
456 | this.joinedOnce = true;
457 | this.rejoin(timeout);
458 | return this.joinPush;
459 | }
460 | }
461 | }, {
462 | key: "onClose",
463 | value: function onClose(callback) {
464 | this.on(CHANNEL_EVENTS.close, callback);
465 | }
466 | }, {
467 | key: "onError",
468 | value: function onError(callback) {
469 | this.on(CHANNEL_EVENTS.error, function (reason) {
470 | return callback(reason);
471 | });
472 | }
473 | }, {
474 | key: "on",
475 | value: function on(event, callback) {
476 | this.bindings.push({ event: event, callback: callback });
477 | }
478 | }, {
479 | key: "off",
480 | value: function off(event) {
481 | this.bindings = this.bindings.filter(function (bind) {
482 | return bind.event !== event;
483 | });
484 | }
485 | }, {
486 | key: "canPush",
487 | value: function canPush() {
488 | return this.socket.isConnected() && this.isJoined();
489 | }
490 | }, {
491 | key: "push",
492 | value: function push(event, payload) {
493 | var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.timeout;
494 |
495 | if (!this.joinedOnce) {
496 | throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
497 | }
498 | var pushEvent = new Push(this, event, payload, timeout);
499 | if (this.canPush()) {
500 | pushEvent.send();
501 | } else {
502 | pushEvent.startTimeout();
503 | this.pushBuffer.push(pushEvent);
504 | }
505 |
506 | return pushEvent;
507 | }
508 |
509 | /** Leaves the channel
510 | *
511 | * Unsubscribes from server events, and
512 | * instructs channel to terminate on server
513 | *
514 | * Triggers onClose() hooks
515 | *
516 | * To receive leave acknowledgements, use the a `receive`
517 | * hook to bind to the server ack, ie:
518 | *
519 | * ```javascript
520 | * channel.leave().receive("ok", () => alert("left!") )
521 | * ```
522 | */
523 |
524 | }, {
525 | key: "leave",
526 | value: function leave() {
527 | var _this3 = this;
528 |
529 | var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
530 |
531 | this.state = CHANNEL_STATES.leaving;
532 | var onClose = function onClose() {
533 | _this3.socket.log("channel", "leave " + _this3.topic);
534 | _this3.trigger(CHANNEL_EVENTS.close, "leave");
535 | };
536 | var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
537 | leavePush.receive("ok", function () {
538 | return onClose();
539 | }).receive("timeout", function () {
540 | return onClose();
541 | });
542 | leavePush.send();
543 | if (!this.canPush()) {
544 | leavePush.trigger("ok", {});
545 | }
546 |
547 | return leavePush;
548 | }
549 |
550 | /**
551 | * Overridable message hook
552 | *
553 | * Receives all events for specialized message handling
554 | * before dispatching to the channel callbacks.
555 | *
556 | * Must return the payload, modified or unmodified
557 | */
558 |
559 | }, {
560 | key: "onMessage",
561 | value: function onMessage(event, payload, ref) {
562 | return payload;
563 | }
564 |
565 | // private
566 |
567 | }, {
568 | key: "isMember",
569 | value: function isMember(topic, event, payload, joinRef) {
570 | if (this.topic !== topic) {
571 | return false;
572 | }
573 | var isLifecycleEvent = CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0;
574 |
575 | if (joinRef && isLifecycleEvent && joinRef !== this.joinRef()) {
576 | this.socket.log("channel", "dropping outdated message", { topic: topic, event: event, payload: payload, joinRef: joinRef });
577 | return false;
578 | } else {
579 | return true;
580 | }
581 | }
582 | }, {
583 | key: "joinRef",
584 | value: function joinRef() {
585 | return this.joinPush.ref;
586 | }
587 | }, {
588 | key: "sendJoin",
589 | value: function sendJoin(timeout) {
590 | this.state = CHANNEL_STATES.joining;
591 | this.joinPush.resend(timeout);
592 | }
593 | }, {
594 | key: "rejoin",
595 | value: function rejoin() {
596 | var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
597 | if (this.isLeaving()) {
598 | return;
599 | }
600 | this.sendJoin(timeout);
601 | }
602 | }, {
603 | key: "trigger",
604 | value: function trigger(event, payload, ref, joinRef) {
605 | var _this4 = this;
606 |
607 | var handledPayload = this.onMessage(event, payload, ref, joinRef);
608 | if (payload && !handledPayload) {
609 | throw "channel onMessage callbacks must return the payload, modified or unmodified";
610 | }
611 |
612 | this.bindings.filter(function (bind) {
613 | return bind.event === event;
614 | }).map(function (bind) {
615 | return bind.callback(handledPayload, ref, joinRef || _this4.joinRef());
616 | });
617 | }
618 | }, {
619 | key: "replyEventName",
620 | value: function replyEventName(ref) {
621 | return "chan_reply_" + ref;
622 | }
623 | }, {
624 | key: "isClosed",
625 | value: function isClosed() {
626 | return this.state === CHANNEL_STATES.closed;
627 | }
628 | }, {
629 | key: "isErrored",
630 | value: function isErrored() {
631 | return this.state === CHANNEL_STATES.errored;
632 | }
633 | }, {
634 | key: "isJoined",
635 | value: function isJoined() {
636 | return this.state === CHANNEL_STATES.joined;
637 | }
638 | }, {
639 | key: "isJoining",
640 | value: function isJoining() {
641 | return this.state === CHANNEL_STATES.joining;
642 | }
643 | }, {
644 | key: "isLeaving",
645 | value: function isLeaving() {
646 | return this.state === CHANNEL_STATES.leaving;
647 | }
648 | }]);
649 |
650 | return Channel;
651 | }();
652 |
653 | var Serializer = {
654 | encode: function encode(msg, callback) {
655 | var payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
656 | return callback(JSON.stringify(payload));
657 | },
658 | decode: function decode(rawPayload, callback) {
659 | var _JSON$parse = JSON.parse(rawPayload),
660 | _JSON$parse2 = _slicedToArray(_JSON$parse, 5),
661 | join_ref = _JSON$parse2[0],
662 | ref = _JSON$parse2[1],
663 | topic = _JSON$parse2[2],
664 | event = _JSON$parse2[3],
665 | payload = _JSON$parse2[4];
666 |
667 | return callback({ join_ref: join_ref, ref: ref, topic: topic, event: event, payload: payload });
668 | }
669 | };
670 |
671 | /** Initializes the Socket
672 | *
673 | *
674 | * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
675 | *
676 | * @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`,
677 | * `"wss://example.com"`
678 | * `"/socket"` (inherited host & protocol)
679 | * @param {Object} opts - Optional configuration
680 | * @param {string} opts.transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
681 | *
682 | * Defaults to WebSocket with automatic LongPoll fallback.
683 | * @param {Function} opts.encode - The function to encode outgoing messages.
684 | *
685 | * Defaults to JSON:
686 | *
687 | * ```javascript
688 | * (payload, callback) => callback(JSON.stringify(payload))
689 | * ```
690 | *
691 | * @param {Function} opts.decode - The function to decode incoming messages.
692 | *
693 | * Defaults to JSON:
694 | *
695 | * ```javascript
696 | * (payload, callback) => callback(JSON.parse(payload))
697 | * ```
698 | *
699 | * @param {number} opts.timeout - The default timeout in milliseconds to trigger push timeouts.
700 | *
701 | * Defaults `DEFAULT_TIMEOUT`
702 | * @param {number} opts.heartbeatIntervalMs - The millisec interval to send a heartbeat message
703 | * @param {number} opts.reconnectAfterMs - The optional function that returns the millsec reconnect interval.
704 | *
705 | * Defaults to stepped backoff of:
706 | *
707 | * ```javascript
708 | * function(tries){
709 | * return [1000, 5000, 10000][tries - 1] || 10000
710 | * }
711 | * ```
712 | * @param {Function} opts.logger - The optional function for specialized logging, ie:
713 | * ```javascript
714 | * logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
715 | * ```
716 | *
717 | * @param {number} opts.longpollerTimeout - The maximum timeout of a long poll AJAX request.
718 | *
719 | * Defaults to 20s (double the server long poll timer).
720 | *
721 | * @param {Object} opts.params - The optional params to pass when connecting
722 | *
723 | *
724 | */
725 |
726 | var Socket = exports.Socket = function () {
727 | function Socket(endPoint) {
728 | var _this5 = this;
729 |
730 | var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
731 |
732 | _classCallCheck(this, Socket);
733 |
734 | this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
735 | this.channels = [];
736 | this.sendBuffer = [];
737 | this.ref = 0;
738 | this.timeout = opts.timeout || DEFAULT_TIMEOUT;
739 | this.transport = opts.transport || window.WebSocket || LongPoll;
740 | this.defaultEncoder = Serializer.encode;
741 | this.defaultDecoder = Serializer.decode;
742 | if (this.transport !== LongPoll) {
743 | this.encode = opts.encode || this.defaultEncoder;
744 | this.decode = opts.decode || this.defaultDecoder;
745 | } else {
746 | this.encode = this.defaultEncoder;
747 | this.decode = this.defaultDecoder;
748 | }
749 | this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
750 | this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
751 | return [1000, 2000, 5000, 10000][tries - 1] || 10000;
752 | };
753 | this.logger = opts.logger || function () {}; // noop
754 | this.longpollerTimeout = opts.longpollerTimeout || 20000;
755 | this.params = opts.params || {};
756 | this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
757 | this.heartbeatTimer = null;
758 | this.pendingHeartbeatRef = null;
759 | this.reconnectTimer = new Timer(function () {
760 | _this5.disconnect(function () {
761 | return _this5.connect();
762 | });
763 | }, this.reconnectAfterMs);
764 | }
765 |
766 | _createClass(Socket, [{
767 | key: "protocol",
768 | value: function protocol() {
769 | return location.protocol.match(/^https/) ? "wss" : "ws";
770 | }
771 | }, {
772 | key: "endPointURL",
773 | value: function endPointURL() {
774 | var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
775 | if (uri.charAt(0) !== "/") {
776 | return uri;
777 | }
778 | if (uri.charAt(1) === "/") {
779 | return this.protocol() + ":" + uri;
780 | }
781 |
782 | return this.protocol() + "://" + location.host + uri;
783 | }
784 | }, {
785 | key: "disconnect",
786 | value: function disconnect(callback, code, reason) {
787 | if (this.conn) {
788 | this.conn.onclose = function () {}; // noop
789 | if (code) {
790 | this.conn.close(code, reason || "");
791 | } else {
792 | this.conn.close();
793 | }
794 | this.conn = null;
795 | }
796 | callback && callback();
797 | }
798 |
799 | /**
800 | *
801 | * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
802 | */
803 |
804 | }, {
805 | key: "connect",
806 | value: function connect(params) {
807 | var _this6 = this;
808 |
809 | if (params) {
810 | console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
811 | this.params = params;
812 | }
813 | if (this.conn) {
814 | return;
815 | }
816 |
817 | this.conn = new this.transport(this.endPointURL());
818 | this.conn.timeout = this.longpollerTimeout;
819 | this.conn.onopen = function () {
820 | return _this6.onConnOpen();
821 | };
822 | this.conn.onerror = function (error) {
823 | return _this6.onConnError(error);
824 | };
825 | this.conn.onmessage = function (event) {
826 | return _this6.onConnMessage(event);
827 | };
828 | this.conn.onclose = function (event) {
829 | return _this6.onConnClose(event);
830 | };
831 | }
832 |
833 | /**
834 | * Logs the message. Override `this.logger` for specialized logging. noops by default
835 | * @param {string} kind
836 | * @param {string} msg
837 | * @param {Object} data
838 | */
839 |
840 | }, {
841 | key: "log",
842 | value: function log(kind, msg, data) {
843 | this.logger(kind, msg, data);
844 | }
845 |
846 | // Registers callbacks for connection state change events
847 | //
848 | // Examples
849 | //
850 | // socket.onError(function(error){ alert("An error occurred") })
851 | //
852 |
853 | }, {
854 | key: "onOpen",
855 | value: function onOpen(callback) {
856 | this.stateChangeCallbacks.open.push(callback);
857 | }
858 | }, {
859 | key: "onClose",
860 | value: function onClose(callback) {
861 | this.stateChangeCallbacks.close.push(callback);
862 | }
863 | }, {
864 | key: "onError",
865 | value: function onError(callback) {
866 | this.stateChangeCallbacks.error.push(callback);
867 | }
868 | }, {
869 | key: "onMessage",
870 | value: function onMessage(callback) {
871 | this.stateChangeCallbacks.message.push(callback);
872 | }
873 | }, {
874 | key: "onConnOpen",
875 | value: function onConnOpen() {
876 | var _this7 = this;
877 |
878 | this.log("transport", "connected to " + this.endPointURL());
879 | this.flushSendBuffer();
880 | this.reconnectTimer.reset();
881 | if (!this.conn.skipHeartbeat) {
882 | clearInterval(this.heartbeatTimer);
883 | this.heartbeatTimer = setInterval(function () {
884 | return _this7.sendHeartbeat();
885 | }, this.heartbeatIntervalMs);
886 | }
887 | this.stateChangeCallbacks.open.forEach(function (callback) {
888 | return callback();
889 | });
890 | }
891 | }, {
892 | key: "onConnClose",
893 | value: function onConnClose(event) {
894 | this.log("transport", "close", event);
895 | this.triggerChanError();
896 | clearInterval(this.heartbeatTimer);
897 | this.reconnectTimer.scheduleTimeout();
898 | this.stateChangeCallbacks.close.forEach(function (callback) {
899 | return callback(event);
900 | });
901 | }
902 | }, {
903 | key: "onConnError",
904 | value: function onConnError(error) {
905 | this.log("transport", error);
906 | this.triggerChanError();
907 | this.stateChangeCallbacks.error.forEach(function (callback) {
908 | return callback(error);
909 | });
910 | }
911 | }, {
912 | key: "triggerChanError",
913 | value: function triggerChanError() {
914 | this.channels.forEach(function (channel) {
915 | return channel.trigger(CHANNEL_EVENTS.error);
916 | });
917 | }
918 | }, {
919 | key: "connectionState",
920 | value: function connectionState() {
921 | switch (this.conn && this.conn.readyState) {
922 | case SOCKET_STATES.connecting:
923 | return "connecting";
924 | case SOCKET_STATES.open:
925 | return "open";
926 | case SOCKET_STATES.closing:
927 | return "closing";
928 | default:
929 | return "closed";
930 | }
931 | }
932 | }, {
933 | key: "isConnected",
934 | value: function isConnected() {
935 | return this.connectionState() === "open";
936 | }
937 | }, {
938 | key: "remove",
939 | value: function remove(channel) {
940 | this.channels = this.channels.filter(function (c) {
941 | return c.joinRef() !== channel.joinRef();
942 | });
943 | }
944 | }, {
945 | key: "channel",
946 | value: function channel(topic) {
947 | var chanParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
948 |
949 | var chan = new Channel(topic, chanParams, this);
950 | this.channels.push(chan);
951 | return chan;
952 | }
953 | }, {
954 | key: "push",
955 | value: function push(data) {
956 | var _this8 = this;
957 |
958 | var topic = data.topic,
959 | event = data.event,
960 | payload = data.payload,
961 | ref = data.ref,
962 | join_ref = data.join_ref;
963 |
964 | var callback = function callback() {
965 | _this8.encode(data, function (result) {
966 | _this8.conn.send(result);
967 | });
968 | };
969 | this.log("push", topic + " " + event + " (" + join_ref + ", " + ref + ")", payload);
970 | if (this.isConnected()) {
971 | callback();
972 | } else {
973 | this.sendBuffer.push(callback);
974 | }
975 | }
976 |
977 | /**
978 | * Return the next message ref, accounting for overflows
979 | */
980 |
981 | }, {
982 | key: "makeRef",
983 | value: function makeRef() {
984 | var newRef = this.ref + 1;
985 | if (newRef === this.ref) {
986 | this.ref = 0;
987 | } else {
988 | this.ref = newRef;
989 | }
990 |
991 | return this.ref.toString();
992 | }
993 | }, {
994 | key: "sendHeartbeat",
995 | value: function sendHeartbeat() {
996 | if (!this.isConnected()) {
997 | return;
998 | }
999 | if (this.pendingHeartbeatRef) {
1000 | this.pendingHeartbeatRef = null;
1001 | this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
1002 | this.conn.close(WS_CLOSE_NORMAL, "hearbeat timeout");
1003 | return;
1004 | }
1005 | this.pendingHeartbeatRef = this.makeRef();
1006 | this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
1007 | }
1008 | }, {
1009 | key: "flushSendBuffer",
1010 | value: function flushSendBuffer() {
1011 | if (this.isConnected() && this.sendBuffer.length > 0) {
1012 | this.sendBuffer.forEach(function (callback) {
1013 | return callback();
1014 | });
1015 | this.sendBuffer = [];
1016 | }
1017 | }
1018 | }, {
1019 | key: "onConnMessage",
1020 | value: function onConnMessage(rawMessage) {
1021 | var _this9 = this;
1022 |
1023 | this.decode(rawMessage.data, function (msg) {
1024 | var topic = msg.topic,
1025 | event = msg.event,
1026 | payload = msg.payload,
1027 | ref = msg.ref,
1028 | join_ref = msg.join_ref;
1029 |
1030 | if (ref && ref === _this9.pendingHeartbeatRef) {
1031 | _this9.pendingHeartbeatRef = null;
1032 | }
1033 |
1034 | _this9.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
1035 | _this9.channels.filter(function (channel) {
1036 | return channel.isMember(topic, event, payload, join_ref);
1037 | }).forEach(function (channel) {
1038 | return channel.trigger(event, payload, ref, join_ref);
1039 | });
1040 | _this9.stateChangeCallbacks.message.forEach(function (callback) {
1041 | return callback(msg);
1042 | });
1043 | });
1044 | }
1045 | }]);
1046 |
1047 | return Socket;
1048 | }();
1049 |
1050 | var LongPoll = exports.LongPoll = function () {
1051 | function LongPoll(endPoint) {
1052 | _classCallCheck(this, LongPoll);
1053 |
1054 | this.endPoint = null;
1055 | this.token = null;
1056 | this.skipHeartbeat = true;
1057 | this.onopen = function () {}; // noop
1058 | this.onerror = function () {}; // noop
1059 | this.onmessage = function () {}; // noop
1060 | this.onclose = function () {}; // noop
1061 | this.pollEndpoint = this.normalizeEndpoint(endPoint);
1062 | this.readyState = SOCKET_STATES.connecting;
1063 |
1064 | this.poll();
1065 | }
1066 |
1067 | _createClass(LongPoll, [{
1068 | key: "normalizeEndpoint",
1069 | value: function normalizeEndpoint(endPoint) {
1070 | return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
1071 | }
1072 | }, {
1073 | key: "endpointURL",
1074 | value: function endpointURL() {
1075 | return Ajax.appendParams(this.pollEndpoint, { token: this.token });
1076 | }
1077 | }, {
1078 | key: "closeAndRetry",
1079 | value: function closeAndRetry() {
1080 | this.close();
1081 | this.readyState = SOCKET_STATES.connecting;
1082 | }
1083 | }, {
1084 | key: "ontimeout",
1085 | value: function ontimeout() {
1086 | this.onerror("timeout");
1087 | this.closeAndRetry();
1088 | }
1089 | }, {
1090 | key: "poll",
1091 | value: function poll() {
1092 | var _this10 = this;
1093 |
1094 | if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
1095 | return;
1096 | }
1097 |
1098 | Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
1099 | if (resp) {
1100 | var status = resp.status,
1101 | token = resp.token,
1102 | messages = resp.messages;
1103 |
1104 | _this10.token = token;
1105 | } else {
1106 | var status = 0;
1107 | }
1108 |
1109 | switch (status) {
1110 | case 200:
1111 | messages.forEach(function (msg) {
1112 | return _this10.onmessage({ data: msg });
1113 | });
1114 | _this10.poll();
1115 | break;
1116 | case 204:
1117 | _this10.poll();
1118 | break;
1119 | case 410:
1120 | _this10.readyState = SOCKET_STATES.open;
1121 | _this10.onopen();
1122 | _this10.poll();
1123 | break;
1124 | case 0:
1125 | case 500:
1126 | _this10.onerror();
1127 | _this10.closeAndRetry();
1128 | break;
1129 | default:
1130 | throw "unhandled poll status " + status;
1131 | }
1132 | });
1133 | }
1134 | }, {
1135 | key: "send",
1136 | value: function send(body) {
1137 | var _this11 = this;
1138 |
1139 | Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
1140 | if (!resp || resp.status !== 200) {
1141 | _this11.onerror(resp && resp.status);
1142 | _this11.closeAndRetry();
1143 | }
1144 | });
1145 | }
1146 | }, {
1147 | key: "close",
1148 | value: function close(code, reason) {
1149 | this.readyState = SOCKET_STATES.closed;
1150 | this.onclose();
1151 | }
1152 | }]);
1153 |
1154 | return LongPoll;
1155 | }();
1156 |
1157 | var Ajax = exports.Ajax = function () {
1158 | function Ajax() {
1159 | _classCallCheck(this, Ajax);
1160 | }
1161 |
1162 | _createClass(Ajax, null, [{
1163 | key: "request",
1164 | value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
1165 | if (window.XDomainRequest) {
1166 | var req = new XDomainRequest(); // IE8, IE9
1167 | this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
1168 | } else {
1169 | var _req = window.XMLHttpRequest ? new window.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
1170 | new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
1171 | this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback);
1172 | }
1173 | }
1174 | }, {
1175 | key: "xdomainRequest",
1176 | value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
1177 | var _this12 = this;
1178 |
1179 | req.timeout = timeout;
1180 | req.open(method, endPoint);
1181 | req.onload = function () {
1182 | var response = _this12.parseJSON(req.responseText);
1183 | callback && callback(response);
1184 | };
1185 | if (ontimeout) {
1186 | req.ontimeout = ontimeout;
1187 | }
1188 |
1189 | // Work around bug in IE9 that requires an attached onprogress handler
1190 | req.onprogress = function () {};
1191 |
1192 | req.send(body);
1193 | }
1194 | }, {
1195 | key: "xhrRequest",
1196 | value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
1197 | var _this13 = this;
1198 |
1199 | req.open(method, endPoint, true);
1200 | req.timeout = timeout;
1201 | req.setRequestHeader("Content-Type", accept);
1202 | req.onerror = function () {
1203 | callback && callback(null);
1204 | };
1205 | req.onreadystatechange = function () {
1206 | if (req.readyState === _this13.states.complete && callback) {
1207 | var response = _this13.parseJSON(req.responseText);
1208 | callback(response);
1209 | }
1210 | };
1211 | if (ontimeout) {
1212 | req.ontimeout = ontimeout;
1213 | }
1214 |
1215 | req.send(body);
1216 | }
1217 | }, {
1218 | key: "parseJSON",
1219 | value: function parseJSON(resp) {
1220 | if (!resp || resp === "") {
1221 | return null;
1222 | }
1223 |
1224 | try {
1225 | return JSON.parse(resp);
1226 | } catch (e) {
1227 | console && console.log("failed to parse JSON response", resp);
1228 | return null;
1229 | }
1230 | }
1231 | }, {
1232 | key: "serialize",
1233 | value: function serialize(obj, parentKey) {
1234 | var queryStr = [];
1235 | for (var key in obj) {
1236 | if (!obj.hasOwnProperty(key)) {
1237 | continue;
1238 | }
1239 | var paramKey = parentKey ? parentKey + "[" + key + "]" : key;
1240 | var paramVal = obj[key];
1241 | if ((typeof paramVal === "undefined" ? "undefined" : _typeof(paramVal)) === "object") {
1242 | queryStr.push(this.serialize(paramVal, paramKey));
1243 | } else {
1244 | queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
1245 | }
1246 | }
1247 | return queryStr.join("&");
1248 | }
1249 | }, {
1250 | key: "appendParams",
1251 | value: function appendParams(url, params) {
1252 | if (Object.keys(params).length === 0) {
1253 | return url;
1254 | }
1255 |
1256 | var prefix = url.match(/\?/) ? "&" : "?";
1257 | return "" + url + prefix + this.serialize(params);
1258 | }
1259 | }]);
1260 |
1261 | return Ajax;
1262 | }();
1263 |
1264 | Ajax.states = { complete: 4 };
1265 |
1266 | var Presence = exports.Presence = {
1267 | syncState: function syncState(currentState, newState, onJoin, onLeave) {
1268 | var _this14 = this;
1269 |
1270 | var state = this.clone(currentState);
1271 | var joins = {};
1272 | var leaves = {};
1273 |
1274 | this.map(state, function (key, presence) {
1275 | if (!newState[key]) {
1276 | leaves[key] = presence;
1277 | }
1278 | });
1279 | this.map(newState, function (key, newPresence) {
1280 | var currentPresence = state[key];
1281 | if (currentPresence) {
1282 | var newRefs = newPresence.metas.map(function (m) {
1283 | return m.phx_ref;
1284 | });
1285 | var curRefs = currentPresence.metas.map(function (m) {
1286 | return m.phx_ref;
1287 | });
1288 | var joinedMetas = newPresence.metas.filter(function (m) {
1289 | return curRefs.indexOf(m.phx_ref) < 0;
1290 | });
1291 | var leftMetas = currentPresence.metas.filter(function (m) {
1292 | return newRefs.indexOf(m.phx_ref) < 0;
1293 | });
1294 | if (joinedMetas.length > 0) {
1295 | joins[key] = newPresence;
1296 | joins[key].metas = joinedMetas;
1297 | }
1298 | if (leftMetas.length > 0) {
1299 | leaves[key] = _this14.clone(currentPresence);
1300 | leaves[key].metas = leftMetas;
1301 | }
1302 | } else {
1303 | joins[key] = newPresence;
1304 | }
1305 | });
1306 | return this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
1307 | },
1308 | syncDiff: function syncDiff(currentState, _ref2, onJoin, onLeave) {
1309 | var joins = _ref2.joins,
1310 | leaves = _ref2.leaves;
1311 |
1312 | var state = this.clone(currentState);
1313 | if (!onJoin) {
1314 | onJoin = function onJoin() {};
1315 | }
1316 | if (!onLeave) {
1317 | onLeave = function onLeave() {};
1318 | }
1319 |
1320 | this.map(joins, function (key, newPresence) {
1321 | var currentPresence = state[key];
1322 | state[key] = newPresence;
1323 | if (currentPresence) {
1324 | var _state$key$metas;
1325 |
1326 | (_state$key$metas = state[key].metas).unshift.apply(_state$key$metas, _toConsumableArray(currentPresence.metas));
1327 | }
1328 | onJoin(key, currentPresence, newPresence);
1329 | });
1330 | this.map(leaves, function (key, leftPresence) {
1331 | var currentPresence = state[key];
1332 | if (!currentPresence) {
1333 | return;
1334 | }
1335 | var refsToRemove = leftPresence.metas.map(function (m) {
1336 | return m.phx_ref;
1337 | });
1338 | currentPresence.metas = currentPresence.metas.filter(function (p) {
1339 | return refsToRemove.indexOf(p.phx_ref) < 0;
1340 | });
1341 | onLeave(key, currentPresence, leftPresence);
1342 | if (currentPresence.metas.length === 0) {
1343 | delete state[key];
1344 | }
1345 | });
1346 | return state;
1347 | },
1348 | list: function list(presences, chooser) {
1349 | if (!chooser) {
1350 | chooser = function chooser(key, pres) {
1351 | return pres;
1352 | };
1353 | }
1354 |
1355 | return this.map(presences, function (key, presence) {
1356 | return chooser(key, presence);
1357 | });
1358 | },
1359 |
1360 |
1361 | // private
1362 |
1363 | map: function map(obj, func) {
1364 | return Object.getOwnPropertyNames(obj).map(function (key) {
1365 | return func(key, obj[key]);
1366 | });
1367 | },
1368 | clone: function clone(obj) {
1369 | return JSON.parse(JSON.stringify(obj));
1370 | }
1371 | };
1372 |
1373 | /**
1374 | *
1375 | * Creates a timer that accepts a `timerCalc` function to perform
1376 | * calculated timeout retries, such as exponential backoff.
1377 | *
1378 | * ## Examples
1379 | *
1380 | * ```javascript
1381 | * let reconnectTimer = new Timer(() => this.connect(), function(tries){
1382 | * return [1000, 5000, 10000][tries - 1] || 10000
1383 | * })
1384 | * reconnectTimer.scheduleTimeout() // fires after 1000
1385 | * reconnectTimer.scheduleTimeout() // fires after 5000
1386 | * reconnectTimer.reset()
1387 | * reconnectTimer.scheduleTimeout() // fires after 1000
1388 | * ```
1389 | * @param {Function} callback
1390 | * @param {Function} timerCalc
1391 | */
1392 |
1393 | var Timer = function () {
1394 | function Timer(callback, timerCalc) {
1395 | _classCallCheck(this, Timer);
1396 |
1397 | this.callback = callback;
1398 | this.timerCalc = timerCalc;
1399 | this.timer = null;
1400 | this.tries = 0;
1401 | }
1402 |
1403 | _createClass(Timer, [{
1404 | key: "reset",
1405 | value: function reset() {
1406 | this.tries = 0;
1407 | clearTimeout(this.timer);
1408 | }
1409 |
1410 | /**
1411 | * Cancels any previous scheduleTimeout and schedules callback
1412 | */
1413 |
1414 | }, {
1415 | key: "scheduleTimeout",
1416 | value: function scheduleTimeout() {
1417 | var _this15 = this;
1418 |
1419 | clearTimeout(this.timer);
1420 |
1421 | this.timer = setTimeout(function () {
1422 | _this15.tries = _this15.tries + 1;
1423 | _this15.callback();
1424 | }, this.timerCalc(this.tries + 1));
1425 | }
1426 | }]);
1427 |
1428 | return Timer;
1429 | }();
1430 |
1431 | })));
1432 |
--------------------------------------------------------------------------------