Skip to content

Commit

Permalink
interactor: an extremely simple webapp for /
Browse files Browse the repository at this point in the history
  • Loading branch information
delfick committed Apr 28, 2024
1 parent c59727a commit 8598cd3
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 2 deletions.
6 changes: 6 additions & 0 deletions apps/interactor/docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Changelog
=========

.. _release-interactor-0-16-5:

0.16.5 - TBD
* Starting to add a webapp to the interactor
It currently does nothing.

.. _release-interactor-0-16-4:

0.16.4 - 24 April 2024
Expand Down
54 changes: 54 additions & 0 deletions apps/interactor/interactor/commander/commands/sio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import attrs
from interactor.commander.store import Command, reg, store
from photons_web_server import commander


@attrs.define
class SIOBody:
path: str
method: str = "GET"
body: dict[str, object] = attrs.field(factory=dict)
params: dict[str, object] = attrs.field(factory=dict)


@attrs.define
class InvalidRoute(Exception):
pass


@store.command
class SIOCommands(Command):
@classmethod
def add_routes(kls, routes: commander.RouteTransformer) -> None:
routes.sio("command", kls.respond)

async def respond(
self,
respond: commander.Responder,
message: commander.Message,
) -> None:
route_transformer = self.meta.retrieve_one(
commander.RouteTransformer, type_cache=reg.type_cache
)
body = self.create(SIOBody, message.body)
route = route_transformer.app.router.resolve(path=body.path, method=body.method)
if not route:
await respond(InvalidRoute())

handler = route[0].handler
if not route or not (cmd := getattr(handler, "__commander_class__", None)):
await respond(InvalidRoute())
return

with route_transformer.instantiate_route(message.request, cmd, handler) as route:
route_args = self.store.determine_http_args_and_kwargs(
self.meta,
route,
respond.progress,
message.request,
(),
{"_body_raw": body.body, "_params_raw": body.params},
)
response = await route(*route_args)
await respond(response.raw_body)
return
16 changes: 15 additions & 1 deletion apps/interactor/interactor/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import typing as tp

import sanic.exceptions
import socketio
import strcs
from interactor.commander.animations import Animations
from interactor.database import DB
Expand Down Expand Up @@ -58,6 +59,18 @@ async def setup(
self.server_options = options
self.animation_options = animation_options

self.sio = socketio.AsyncServer(async_mode="sanic")
store.add_sio_server(self.sio)

@self.sio.on("connect")
async def on_connect(sid: str, *args: object) -> None:
async with self.sio.session(sid) as session:
if "lock" not in session:
session["lock"] = asyncio.Lock()

async with (await self.sio.get_session(sid))["lock"]:
await self.sio.emit("server_time", time.time(), to=sid)

self.database = DB(self.server_options.database.uri)
self.database._merged_options_formattable = True
self.cleaners.append(self.database.finish)
Expand Down Expand Up @@ -103,7 +116,8 @@ async def setup_routes(self):
message_from_exc=InteractorMessageFromExc,
)

self.app.add_route(self.serve_static, "/", ctx_serve_index=True, name="static-spa")
self.sio.attach(self.app, socketio_path="webapp")
self.app.router.static_routes[("webapp", "")].routes[0].ctx.only_debug_logs = True

self.app.add_route(
self.serve_static,
Expand Down
106 changes: 106 additions & 0 deletions apps/interactor/interactor_webapp/interactor/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion apps/interactor/interactor_webapp/interactor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"dev": "vite dev --host 127.0.0.1",
"build": "vite build",
"watch": "vite build --watch",
"watch": "vite build --watch -m development --sourcemap inline",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
Expand All @@ -25,10 +25,12 @@
"eslint-plugin-svelte": "^2.35.1",
"prettier": "^3.1.1",
"prettier-plugin-svelte": "^3.1.2",
"socket.io-client": "^4.7.5",
"svelte": "^4.2.7",
"svelte-check": "^3.6.0",
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"ulidx": "^2.3.0",
"vite": "^5.0.3"
},
"type": "module"
Expand Down
86 changes: 86 additions & 0 deletions apps/interactor/interactor_webapp/interactor/src/lib/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Socket } from 'socket.io-client';
import { ulid } from "ulidx";
import { writable, type Writable } from 'svelte/store';

export class Device {
constructor(public serial: string) {
}
}

type MM = {
[key: string]: unknown
}

type WaitingMap = {
[key: string]: ((body: MM) => void) | null
};

export class Commands {
socket: Socket;
waiting: WaitingMap;
devices: Writable<Array<Device>>
connecting: Writable<boolean>

constructor(socket: Socket) {
this.socket = socket
this.devices = writable([])
this.connecting = writable(true)
this.waiting = {};

socket.on('connect', this.connect)
socket.on('disconnect', this.disconnect)
socket.on('progress', this.reply);
socket.on('reply', this.reply);
socket.on('error', this.reply);
}

connect = async (): Promise<void> => {
const serials = await this.refresh_serials();
this.connecting.set(false)
this.devices.set(serials.map(serial => new Device(serial)))
}

disconnect = async (): Promise<void> => {
this.connecting.set(true)
}

reply = (body: MM): void => {
const message_id = body.message_id
if (typeof message_id == "string") {
const handle = this.waiting[message_id]
if (handle) {
handle(body)
}
}
}

send<T_Ret>(path: string, options: MM, handler: (data: MM) => T_Ret): Promise<T_Ret> {
const socket = this.socket;
if (!socket) {
return Promise.reject("No active socket")
}
return new Promise(resolve => {
const handle = (data: MM): void => {
resolve(handler(data))
};
const command = { path: path, message_id: ulid(), ...options };
this.waiting[command.message_id] = handle
socket.emit('command', command)
})
}

refresh_serials(): Promise<Array<string>> {
return new Promise(resolve => {
this.send("/v2/discover/serials", {}, (data: MM) => {
if (data.reply instanceof Object && "error" in data.reply) {
throw new Error(String(data.reply.error))
}
if (!Array.isArray(data.reply)) {
throw new Error("Reply wasn't an array")
}
resolve(data.reply);
})
});
}
}

Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
<script>
import { io } from 'socket.io-client';
import { Commands } from '$lib/commands';
const socketio = io({ path: '/webapp' });
const commands = new Commands(socketio);
$: connecting = commands.connecting;
$: devices = commands.devices;
</script>

<h1>Interactor</h1>

{#if $connecting}
<p>Connecting...</p>
{:else}
<ul>
{#each $devices as device}
<li>{device.serial}</li>
{/each}
</ul>
{/if}

0 comments on commit 8598cd3

Please sign in to comment.