- Create MobileChannel for geographic bounds-based packet filtering - Add MobileUserSocket for mobile client connections - Implement subscribe_bounds, update_bounds, and unsubscribe events - Leverage existing StreamingPacketsPubSub infrastructure - Add comprehensive mobile API documentation with Swift examples - WebSocket endpoint: wss://aprs.me/mobile/websocket - Channel: mobile:packets This enables iOS/Android apps to receive real-time APRS packets filtered by geographic viewport, with efficient bandwidth usage.
83 lines
No EOL
3.8 KiB
Markdown
83 lines
No EOL
3.8 KiB
Markdown
This is a web application written using the Phoenix web framework.
|
|
|
|
## Mobile API
|
|
|
|
The application provides a real-time WebSocket API for mobile clients (iOS/Android):
|
|
|
|
- **Endpoint**: `wss://aprs.me/mobile/websocket`
|
|
- **Channel**: `mobile:packets`
|
|
- **Documentation**: See `docs/mobile-api.md` for complete API reference
|
|
- **Implementation**: `lib/aprsme_web/channels/mobile_channel.ex`
|
|
|
|
**Key Features:**
|
|
- Real-time streaming of APRS packets
|
|
- Geographic bounds filtering (only receive packets in viewport)
|
|
- Dynamic bounds updates (follow user as they pan/zoom)
|
|
- Efficient - leverages existing StreamingPacketsPubSub infrastructure
|
|
|
|
## Elixir guidelines
|
|
|
|
- Elixir lists **do not support index based access via the access syntax**
|
|
|
|
**Never do this (invalid)**:
|
|
|
|
i = 0
|
|
mylist = ["blue", "green"]
|
|
mylist[i]
|
|
|
|
Instead, **always** use `Enum.at`, pattern matching, or `List` for index based list access, ie:
|
|
|
|
i = 0
|
|
mylist = ["blue", "green"]
|
|
Enum.at(mylist, i)
|
|
|
|
- Elixir supports `if/else` but **does NOT support `if/else if` or `if/elsif`. **Never use `else if` or `elseif` in Elixir**, **always** use `cond` or `case` for multiple conditionals.
|
|
|
|
**Never do this (invalid)**:
|
|
|
|
<%= if condition do %>
|
|
...
|
|
<% else if other_condition %>
|
|
...
|
|
<% end %>
|
|
|
|
Instead **always** do this:
|
|
|
|
<%= cond do %>
|
|
<% condition -> %>
|
|
...
|
|
<% condition2 -> %>
|
|
...
|
|
<% true -> %>
|
|
...
|
|
<% end %>
|
|
|
|
- Elixir variables are immutable, but can be rebound, so for block expressions like `if`, `case`, `cond`, etc
|
|
you *must* bind the result of the expression to a variable if you want to use it and you CANNOT rebind the result inside the expression, ie:
|
|
|
|
# INVALID: we are rebinding inside the `if` and the result never gets assigned
|
|
if connected?(socket) do
|
|
socket = assign(socket, :val, val)
|
|
end
|
|
|
|
# VALID: we rebind the result of the `if` to a new variable
|
|
socket =
|
|
if connected?(socket) do
|
|
assign(socket, :val, val)
|
|
end
|
|
|
|
- Use `with` for chaining operations that return `{:ok, _}` or `{:error, _}`
|
|
- **Never** nest multiple modules in the same file as it can cause cyclic dependencies and compilation errors
|
|
- **Never** use map access syntax (`changeset[:field]`) on structs as they do not implement the Access behaviour by default. For regular structs, you **must** access the fields directly, such as `my_struct.field` or use higher level APIs that are available on the struct if they exist, `Ecto.Changeset.get_field/2` for changesets
|
|
- Elixir's standard library has everything necessary for date and time manipulation. Familiarize yourself with the common `Time`, `Date`, `DateTime`, and `Calendar` interfaces by accessing their documentation as necessary. **Never** install additional dependencies unless asked or for date/time parsing (which you can use the `date_time_parser` package)
|
|
- Don't use `String.to_atom/1` on user input (memory leak risk)
|
|
- Predicate function names should not start with `is_` and should end in a question mark. Names like `is_thing` should be reserved for guards
|
|
- Elixir's builtin OTP primitives like `DynamicSupervisor` and `Registry`, require names in the child spec, such as `{DynamicSupervisor, name: MyApp.MyDynamicSup}`, then you can use `DynamicSupervisor.start_child(MyApp.MyDynamicSup, child_spec)`
|
|
- Use `Task.async_stream(collection, callback, options)` for concurrent enumeration with back-pressure. The majority of times you will want to pass `timeout: :infinity` as option
|
|
- **Always run mix format after changing any Elixir files**
|
|
|
|
## Git Commit Guidelines
|
|
|
|
- **Never** add "Co-Authored-By: Claude" or similar AI co-authorship tags to commits
|
|
- Commits should appear as authored solely by the user (Graham)
|
|
- Keep commit messages clear and concise, describing what changed and why |