Skip to content

Commit

Permalink
Remote Config Service
Browse files Browse the repository at this point in the history
  • Loading branch information
kerfouffle committed Nov 10, 2023
1 parent 4ec3099 commit 3f8a500
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 2 deletions.
25 changes: 24 additions & 1 deletion packages/server/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ import {
CloudflareService,
WebhookService,
ScheduledMessagesService,
OauthService
OauthService,
RemoteConfigService
} from "@server/services";
import { EventCache } from "@server/eventCache";
import { runTerminalScript, openSystemPreferences } from "@server/api/apple/scripts";
Expand Down Expand Up @@ -126,6 +127,8 @@ class BlueBubblesServer extends EventEmitter {

networkChecker: NetworkService;

remoteConfigService: RemoteConfigService;

caffeinate: CaffeinateService;

updater: UpdateService;
Expand Down Expand Up @@ -484,6 +487,13 @@ class BlueBubblesServer extends EventEmitter {
} catch (ex: any) {
this.log(`Failed to start Scheduled Message service! ${ex.message}`, "error");
}

try {
this.log("Initializing Remote Config Service...");
this.remoteConfigService = new RemoteConfigService();
} catch (ex: any) {
this.log(`Failed to initialize Remote Config Service! ${ex.message}`, "error");
}
}

/**
Expand Down Expand Up @@ -544,6 +554,13 @@ class BlueBubblesServer extends EventEmitter {
this.log("Starting iMessage Database listeners...");
await this.startChatListeners();
}

try {
this.log("Starting Remote Config Service...");
await this.remoteConfigService.start();
} catch (ex: any) {
this.log(`Failed to start Remote Config Service! ${ex.message}`, "error");
}
}

async stopServices(): Promise<void> {
Expand Down Expand Up @@ -593,6 +610,12 @@ class BlueBubblesServer extends EventEmitter {
this.log(`Failed to stop Scheduled Messages service! ${ex?.message ?? ex}`, "error");
}

try {
this.remoteConfigService?.stop();
} catch (ex: any) {
this.log(`Failed to stop Remote Config Service! ${ex.message}`, "error");
}

this.log("Finished stopping services...");
}

Expand Down
4 changes: 3 additions & 1 deletion packages/server/src/server/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { WebhookService } from "./webhookService";
import { FindMyService } from "./findMyService";
import { ScheduledMessagesService } from "./scheduledMessagesService";
import { OauthService } from "./oauthService";
import { RemoteConfigService } from "./remoteConfigService";

export {
FCMService,
Expand All @@ -27,5 +28,6 @@ export {
WebhookService,
FindMyService,
ScheduledMessagesService,
OauthService
OauthService,
RemoteConfigService
};
107 changes: 107 additions & 0 deletions packages/server/src/server/services/remoteConfigService/commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { isEmpty } from "@server/helpers/utils";
import { Server } from "@server";


// This whole file was bascially cribbed from main.ts. I imagine the code should
// be consolidated and extracted somwhere.


function quickStrConvert (val: string) {
if (val.toLowerCase() === "true") return true;
if (val.toLowerCase() === "false") return false;
return val;
}

async function handleSet (parts: string[]) {
const configKey = parts.length > 1 ? parts[1] : null;
const configValue = parts.length > 2 ? parts[2] : null;
if (!configKey || !configValue) {
return "Empty config key/value. Ignoring...";
}

if (!Server().repo.hasConfig(configKey)) {
return `Configuration, '${configKey}' does not exist. Ignoring...`;
}

try {
await Server().repo.setConfig(configKey, quickStrConvert(configValue));
return `Successfully set config item, '${configKey}' to, '${quickStrConvert(configValue)}'`;
} catch (ex: any) {
Server().log(`Failed set config item, '${configKey}'\n${ex}`, "error");
return `Failed set config item, '${configKey}'\n${ex}`;
}
}

async function handleShow (parts: string[]) {
const configKey = parts.length > 1 ? parts[1] : null;
if (!configKey) {
return "Empty config key. Ignoring...";
}

if (!Server().repo.hasConfig(configKey)) {
return `Configuration, '${configKey}' does not exist. Ignoring...`;
}

try {
const value = await Server().repo.getConfig(configKey);
return `${configKey} -> ${value}`;
} catch (ex: any) {
Server().log(`Failed set config item, '${configKey}'\n${ex}`, "error");
return `Failed set config item, '${configKey}'\n${ex}`;
}
}

const help = `[================================== Help Menu ==================================]\n
Available Commands:
- help: Show the help menu
- restart: Relaunch/Restart the app
- set: Set configuration item -> \`set <config item> <value>\`
Available configuration items:
-> tutorial_is_done: boolean
-> socket_port: number
-> server_address: string
-> ngrok_key: string
-> password: string
-> auto_caffeinate: boolean
-> auto_start: boolean
-> enable_ngrok: boolean
-> encrypt_coms: boolean
-> hide_dock_icon: boolean
-> last_fcm_restart: number
-> start_via_terminal: boolean
- show: Show the current configuration for an item -> \`show <config item>\`
- exit: Exit the configurator
\n[===============================================================================]`;


export async function handleLine (line: string) : Promise<[string, boolean]> {
line = line.trim();
if (line === "") return ['', true];

// Handle the standard input
const parts = line.split(" ");
if (isEmpty(parts)) return ['', true];

if (!Server()) {
return ["Server is not running???????", true];
}

switch (parts[0].toLowerCase()) {
case "help":
return [help, true];
case "set":
return [await handleSet(parts), true];
break;
case "show":
return [await handleShow(parts), true];
break;
case "restart":
case "relaunch":
Server().relaunch();
return ["Okay, restarting", true];
case "exit":
return ["Thank you for using the Bluebubbles configurator!", false];
default:
return ["Unrecognized command. Type 'help' for help.", true];
}
}
110 changes: 110 additions & 0 deletions packages/server/src/server/services/remoteConfigService/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import * as net from "net";
import { Server } from "@server";
import { LogLevel } from "electron-log";
import { LineExtractor } from "./lineExtractor";
import { app } from "electron";
import { handleLine } from "./commands";


const PROMPT = 'bluebubbles-configurator>';


export class RemoteConfigService {
server: net.Server;

clients: Set<net.Socket> = new Set();

socketPath: string;

constructor () {
// not sure if this socketPath stuff belongs in fileSystem/index.ts
const isDev = process.env.NODE_ENV !== "production";
this.socketPath = app.getPath("userData");
if (isDev) {
this.socketPath = path.join(this.socketPath, "bluebubbles-server");
}
this.socketPath = path.join(this.socketPath, "remote-config.sock");

this.log(`Remote Config Service socket path: ${this.socketPath}`, "debug");
}

private log(message: string, level: LogLevel = "info") {
Server().log(`[RemoteConfigService] ${String(message)}`, level);
}

start() {
this.server = net.createServer({allowHalfOpen: false}, (client: net.Socket) => {
this.handleClient(client);
});

if (fs.existsSync(this.socketPath)) {
fs.unlinkSync(this.socketPath);
}
this.server.listen(this.socketPath);

this.server.on("error", err => {
this.log("An error occured in the RemoteConfigService server socket", "error");
this.log(err.toString(), "error");
this.closeServer();
});
}

private handleClient(client: net.Socket) {
this.log("Remote Config Service client connected", "debug");

this.clients.add(client);
const lineExtractor = new LineExtractor();

client.write(`Welcome to the BlueBubbles configurator!\nType 'help' for help.\nType 'exit' to exit.\n${PROMPT} `);

client.on("data", async (data: Buffer) => {
for (const line of lineExtractor.feed(data)) {
await this.handleLine(client, line);
}
});
client.on("close", () => {
this.log("Remote Config Service client closed", "debug");
this.clients.delete(client);
});
client.on("error", err => {
this.log("An error occured in a RemoteConfigService client", "error");
this.log(err.toString(), "error");
});
}

private async handleLine (client: net.Socket, line: string) {
this.log(`Remote Config Service client received line: ${JSON.stringify(line)}`, "debug");
const [response, keepOpen] = await handleLine(line);
if (response) {
client.write(`${response}\n`);
}
if (keepOpen) {
client.write(`${PROMPT} `);
}
else {
client.destroy();
}
}

private closeServer () {
if (this.server === null) {
return;
}
this.server.close();
this.server = null;
}

private destroyAllClients () {
for (const client of this.clients) {
client.destroy();
}
this.clients = new Set();
}

stop() {
this.log("Stopping Remote Config Service...");

this.destroyAllClients();
this.closeServer();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export class LineExtractor {

buffer: Buffer;

constructor () {
this.buffer = Buffer.from([]);
}

*feed (data: Buffer) {
this.buffer = Buffer.concat([this.buffer, data]);
while (true) {
const i = this.buffer.indexOf(10);
if (i === -1) break;

yield this.buffer.subarray(0, i).toString('utf-8');
this.buffer = this.buffer.subarray(i + 1);
}
}
}

0 comments on commit 3f8a500

Please sign in to comment.