diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..244c521 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:20-alpine + +WORKDIR /usr/src/app +COPY package*.json ./ +RUN npm ci --omit=dev +COPY src/. . + +EXPOSE 4000 7000 +CMD [ "node", "src/main.js" ] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a9c89dc --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +STORAGE_URL = gitea.bjornmossa.net +IMAGE_NAME = $(STORAGE_URL)/radioiceberg/metadata +GIT_TAG = $(shell git describe --abbrev=0 --tags) + +login: + docker login $(STORAGE_URL) + +start: + npx live-server ./src + +build: + docker build . -t $(IMAGE_NAME):$(GIT_TAG) + +bump-latest: + docker tag $(IMAGE_NAME):$(GIT_TAG) $(IMAGE_NAME):latest + +push-current: login + docker push $(IMAGE_NAME):$(GIT_TAG) + +push-latest: login + docker push $(IMAGE_NAME):latest diff --git a/package.json b/package.json new file mode 100644 index 0000000..adc2d2c --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "iceberg-metadata", + "version": "1.0.0", + "description": "Microservice for updating metadata on radio", + "main": "main.js", + "type": "module", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Arseniusz/radioiceberg.git" + }, + "author": "radio iceberg team", + "license": "GPL-3.0-or-later", + "bugs": { + "url": "https://github.com/Arseniusz/radioiceberg/issues" + }, + "homepage": "https://github.com/Arseniusz/radioiceberg#readme", + "dependencies": { + "ws": "^8.13.0" + } +} diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..eacd94d --- /dev/null +++ b/src/main.js @@ -0,0 +1,49 @@ +import { createServer } from "node:http"; +import { randomUUID } from "node:crypto"; +import { WebSocketServer } from "ws"; +import { notifier } from "./notifier.js"; + +const wss = new WebSocketServer({ port: 7000 }); + +wss.on("connection", (ws) => { + const id = randomUUID(); + const message = { + command: "clientConnected", + data: { + socket: ws, + id, + }, + }; + + notifier.processMessage(message); + + ws.on("close", () => { + const message = { + command: "cliendDisconnected", + data: { + id, + }, + }; + + notifier.processMessage(message); + }); +}); + +createServer((req, res) => { + let body = ""; + + req.on("data", (chunk) => { + body += chunk; + }); + + req.on("end", () => { + try { + const message = JSON.parse(body); + notifier.processMessage(message); + } catch (error) { + console.error(error); + } + }); + + res.end(); +}).listen(4000); diff --git a/src/notifier.js b/src/notifier.js new file mode 100644 index 0000000..47f9462 --- /dev/null +++ b/src/notifier.js @@ -0,0 +1,60 @@ +import { state } from "./state.js"; + +function updateMeta(message) { + const { data } = message; + this.state.lastPlayed = data; + this.notifyAll(message); +} + +function startOnline(message) { + this.state.isOnline = true; + this.notifyAll(message); +} + +function stopOnline(message) { + this.state.isOnline = false; + this.notifyAll(message); +} + +function addClient({ data }) { + const { socket, id } = data; + this.clients.set(id, socket); + this.notifyOne(socket, { command: "setState", data: this.state }); +} + +function deleteClient({ data }) { + const { id } = data; + this.clients.delete(id); +} + +const actions = { + clientConnected: addClient, + cliendDisconnected: deleteClient, + metadataChange: updateMeta, + liveStarted: startOnline, + liveEnded: stopOnline, +}; + +const notifier = { + __proto__: state, + notifyAll: function (message) { + const messageStrinified = JSON.stringify(message); + this.clients.forEach((c) => c.send(messageStrinified)); + }, + notifyOne: function (socket, message) { + const messageStrinified = JSON.stringify(message); + console.log("NOTIFY ONE:", message); + socket.send(messageStrinified); + }, + processMessage: function (message) { + const action = actions[message.command]; + if (action) { + action.call(this, message); + console.log("PERFORM ACTION", message.command); + } else { + console.log("no avaible command for state mutation", message.command); + } + }, +}; + +export { notifier }; diff --git a/src/state.js b/src/state.js new file mode 100644 index 0000000..e8ba7d2 --- /dev/null +++ b/src/state.js @@ -0,0 +1,9 @@ +const state = { + state: { + lastPlayed: null, + isOnline: false, + }, + clients: new Map(), +}; + +export { state };