Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rest control #78

Merged
merged 39 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
481b2aa
simple rest api
alex-Arc Aug 8, 2023
9e0d7aa
describe REST API in readme
alex-Arc Aug 8, 2023
9853a69
set REST port from cli and add to startup config
alex-Arc Aug 8, 2023
7c8be8a
update old config files
alex-Arc Aug 8, 2023
dbf32ab
update fix empty else
alex-Arc Aug 8, 2023
c7bd57e
save new ip and port to config file
alex-Arc Aug 8, 2023
c0bd2ef
display local ip on when not connected
alex-Arc Aug 8, 2023
1342c19
Update src/cards.ts
alex-Arc Aug 12, 2023
99c62a3
add rest server open and close
alex-Arc Aug 13, 2023
1e76672
future todo
alex-Arc Aug 13, 2023
f316728
move file write to updateEnvironmentFile
alex-Arc Aug 13, 2023
5ebcc2c
if the server is open close it and open a new port
alex-Arc Aug 13, 2023
3fcddb6
switch to koa
alex-Arc Aug 14, 2023
b9b7e4e
remove ip validate
alex-Arc Aug 14, 2023
1af5209
remove unused next
alex-Arc Aug 14, 2023
9517d42
trying to appease the typescript gods
alex-Arc Aug 14, 2023
794971a
server definite assignment assertion
alex-Arc Aug 14, 2023
0d8645a
rest port default to 0
alex-Arc Aug 14, 2023
009d666
only test server.listening if it exists
alex-Arc Aug 14, 2023
65fdd95
fix data extraction
alex-Arc Aug 14, 2023
0a98367
return plain text if sigle parameter is requested
alex-Arc Aug 14, 2023
cb6f119
fix missing body
alex-Arc Aug 14, 2023
b15e448
fogot body in POST config
alex-Arc Aug 14, 2023
8380eee
check content type
alex-Arc Aug 15, 2023
dd509d6
update readmee
alex-Arc Aug 15, 2023
ba677d9
fix: crash when exiting
Julusian Aug 15, 2023
e51ce24
fix: ensure request data is valid. ensure errors are caught
Julusian Aug 15, 2023
87101d8
chore: fix bad commit
Julusian Aug 15, 2023
ce3c5bd
fix: sanitise data for /api/config
Julusian Aug 15, 2023
9f0721a
chore: format
Julusian Aug 15, 2023
a64cb84
chore: update readme
Julusian Aug 15, 2023
691e590
chore: update wording for config file
Julusian Aug 15, 2023
38dcabc
fix: propogate port number in `ipChange` event
Julusian Aug 15, 2023
8e35a76
fix: define config file path through environment variable, so that it…
Julusian Aug 15, 2023
6a3cd8c
chore: async-ify updateEnvironmentFile
Julusian Aug 15, 2023
3038a65
void type
alex-Arc Aug 16, 2023
f91e8cc
add async
alex-Arc Aug 16, 2023
c3288b4
Merge branch 'REST-control' of https://github.com/alex-Arc/companion-…
alex-Arc Aug 16, 2023
65585ac
Merge branch 'master' into REST-control
Julusian Aug 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ After this, you can use `sudo satellite-update` to change the version it has ins

Note: This script will create a new user called `satellite`, which Satellite will be run as and will own the configuration.

### REST API

The default rest port is 9999
a GET request to `http://Satellite-IP:9999/api/ip` will return the current target ip
a GET request to `http://Satellite-IP:9999/api/port` will return the current target ip

a POST request to `http://Satellite-IP:9999/api/ip` with a valid ip will connect the sattelite to that ip
a POST request to `http://Satellite-IP:9999/api/port` with a valid port number will connect the sattelite to that port

hostenames are not yet suporrted

## Development

NodeJS 16 is required
Expand Down
5 changes: 4 additions & 1 deletion pi-image/satellite-config
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
COMPANION_IP=127.0.0.1

# If you are connecting through a router or firewall which has remapped the port, you will need to change that here to match
COMPANION_PORT=16622
COMPANION_PORT=16622

#PORT FOR THE REST server
REST_PORT=9999
2 changes: 1 addition & 1 deletion pi-image/satellite.service
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Type=simple
User=satellite
WorkingDirectory=/usr/local/src/companion-satellite
EnvironmentFile=/boot/satellite-config
ExecStart=/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/dist/main.js $COMPANION_IP $COMPANION_PORT
ExecStart=/opt/fnm/aliases/default/bin/node /usr/local/src/companion-satellite/dist/main.js $COMPANION_IP $COMPANION_PORT $REST_PORT
Restart=on-failure
KillSignal=SIGINT
TimeoutStopSec=60
Expand Down
10 changes: 10 additions & 0 deletions pi-image/update.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ cp pi-image/50-satellite.rules /etc/udev/rules.d/

# update startup script
cp pi-image/satellite.service /etc/systemd/system

# ADD REST_PORT to old config files
if grep -q REST_PORT /boot/satellite-config; then
echo "config ok"
else
echo "
#PORT FOR THE REST server
REST_PORT=9999" >> /boot/satellite-config
fi

systemctl daemon-reload

# install some scripts
Expand Down
18 changes: 17 additions & 1 deletion src/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,25 @@ export class CardGenerator {
context2d.fillStyle = '#ffffff'

context2d.fillText(`Remote: ${remoteIp}`, 10, height - 10)
context2d.fillText(`Status: ${status}`, 10, height - 30)
context2d.fillText(`Local: ${getIPAddress()}`, 10, height - 30)
context2d.fillText(`Status: ${status}`, 10, height - 50)

// return result
return Buffer.from(context2d.getImageData(0, 0, width, height).data)
}
}
//#TODO: make llocal ip reactive
import { networkInterfaces } from 'os'
function getIPAddress() {
alex-Arc marked this conversation as resolved.
Show resolved Hide resolved
for (let devName in networkInterfaces()) {
let iface = networkInterfaces()[devName];
if (iface) {
for (let i = 0; i < iface.length; i++) {
let alias = iface[i];
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal)
return alias.address;
}
}
}
return '0.0.0.0';
}
54 changes: 53 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ import { CompanionSatelliteClient } from './client'
import { DeviceManager } from './devices'
import { DEFAULT_PORT } from './lib'

import { RestServer } from './rest'
import * as fs from 'fs'

const cli = meow(
`
Usage
$ companion-satellite hostname [port]
$ companion-satellite hostname [port] [REST port]

Examples
$ companion-satellite 192.168.1.100
$ companion-satellite 192.168.1.100 16622
$ companion-satellite 192.168.1.100 16622 9999
`,
{}
)
Expand All @@ -21,25 +25,73 @@ if (cli.input.length === 0) {
}

let port = DEFAULT_PORT
let rest_port = 9999
if (cli.input.length > 1) {
port = Number(cli.input[1])
if (isNaN(port)) {
cli.showHelp(1)
}
if (cli.input.length > 2) {
rest_port = Number(cli.input[2])
if (isNaN(rest_port)) {
cli.showHelp(1)
}
}
}

console.log('Starting')

const client = new CompanionSatelliteClient({ debug: true })
const devices = new DeviceManager(client)
const server = new RestServer(client, rest_port)
Julusian marked this conversation as resolved.
Show resolved Hide resolved

client.on('log', (l) => console.log(l))
client.on('error', (e) => console.error(e))

client.on('ipChange', (newIP) => {
updateEnvironmentFile('/boot/satellite-config',
{ "COMPANION_IP": newIP, "COMPANION_PORT": String(client.port) })
})

exitHook(() => {
console.log('Exiting')
client.disconnect()
devices.close()
server.close()
})

client.connect(cli.input[0], port)

function updateEnvironmentFile(filePath: string, changes: Record<string, string>): void {
fs.access(filePath, (err: any) => {
if (err) {
console.log(err)
} else {
fs.readFile(filePath, 'utf-8', function (err: any, data: string) {
if (err) {
console.error(err)
}
else {
let lines = data.split(/\r?\n/)

for (let i = 0; i < lines.length; i++) {
for (const key in changes) {
if (lines[i].startsWith(key)) {
lines[i] = key + '=' + changes[key]
}
}
}

let newData = lines.join('\n')

fs.writeFile(filePath, newData, 'utf-8', function (err: any) {
if (err) {
console.error(err)
}
});
}

})
}
})
}
100 changes: 100 additions & 0 deletions src/rest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import http = require('http')
import net = require('net')
import { CompanionSatelliteClient } from './client'

export class RestServer {
private _cs_client: CompanionSatelliteClient
private _server: http.Server

constructor(client: CompanionSatelliteClient, port: Number) {

this._cs_client = client
// super()

this._server = http.createServer(async (req, res) => {
//set the request route
if (req.method === 'GET') {
console.log("api get: ", req.url)
switch (req.url) {
//#TODO:add host case
case ('/api/ip'): {
alex-Arc marked this conversation as resolved.
Show resolved Hide resolved
res.writeHead(200, { "Content-Type": "application/json" })
res.write(this._cs_client.host)
res.end()
break
}
case ('/api/port'): {
res.writeHead(200, { "Content-Type": "application/json" })
res.write(String(this._cs_client.port))
res.end()
break
}
default: {
res.writeHead(204, { "Content-Type": "application/json" })
res.end(JSON.stringify({ message: "Route not found" }))
break
}
}
}
if (req.method === 'POST') {
let body = ''
req.on('data', (data) => {
body += data
})
req.on('end', () => {
console.log("api post:", req.url, body)
switch (req.url) {
case ('/api/ip'): {
if (net.isIP(body) != 0) {
res.writeHead(200, { "Content-Type": "application/json" })
res.end()
this._cs_client.connect(body, this._cs_client.port)
} else {
res.writeHead(400, { "Content-Type": "application/json" })
res.end(JSON.stringify({ message: "Malformed ip" }))
}
break
}
case ('/api/port'): {
let port = Number(body)
if (port && port > 0 && port < 65535) {
res.writeHead(200, { "Content-Type": "application/json" })
res.end()
this._cs_client.connect(this._cs_client.host, port)
} else {
res.writeHead(400, { "Content-Type": "application/json" })
res.end(JSON.stringify({ message: "bad port" }))
}
break
}
default: {
res.writeHead(204, { "Content-Type": "application/json" })
res.end(JSON.stringify({ message: "Route not found" }))
break
}
}
})
}
})

this.open(port)
}

public open(port: Number) {
if (this._server.listening) { this.close() }
if (port != 0) {
this._server.listen(port, () => {
console.log(`REST server starting: port: ${port}`)
})
} else {
console.log("REST server not starting: port 0")
}

}

Julusian marked this conversation as resolved.
Show resolved Hide resolved
public close(): void {
this._server.closeAllConnections()
alex-Arc marked this conversation as resolved.
Show resolved Hide resolved
console.log("The rest server is closed")
}

}