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

Add ServiceManager to manage services with dependencies #707

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 23 additions & 48 deletions packages/server/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import {
UpdateService,
CloudflareService,
WebhookService,
ScheduledMessagesService,
OauthService,
ZrokService
} from "@server/services";
Expand Down Expand Up @@ -71,6 +70,7 @@ import { MessagePoller } from "./databases/imessage/pollers/MessagePoller";
import { obfuscatedHandle } from "./utils/StringUtils";
import { AutoStartMethods } from "./databases/server/constants";
import { MacOsInterface } from "./api/interfaces/macosInterface";
import { ServiceManager } from "./lib/ServiceManager";

const findProcess = require("find-process");

Expand Down Expand Up @@ -171,6 +171,8 @@ class BlueBubblesServer extends EventEmitter {

typingCache: string[];

serviceManager: ServiceManager;

get hasDiskAccess(): boolean {
// As long as we've tried to initialize the DB, we know if we do/do not have access.
const dbInit: boolean | null = this.iMessageRepo?.db?.isInitialized;
Expand Down Expand Up @@ -245,6 +247,8 @@ class BlueBubblesServer extends EventEmitter {
this.region = null;
this.typingCache = [];
this.findMyCache = null;

this.serviceManager = new ServiceManager();
}

emitToUI(event: string, data: any) {
Expand Down Expand Up @@ -497,6 +501,18 @@ class BlueBubblesServer extends EventEmitter {
} catch (ex: any) {
this.logger.error(`Failed to start Scheduled Message service! ${ex?.message ?? String(ex)}}`);
}

this.serviceManager.registerService("HttpService", this.httpService);
this.serviceManager.registerService("PrivateApiService", this.privateApi);
this.serviceManager.registerService("FCMService", this.fcm);
this.serviceManager.registerService("NetworkService", this.networkChecker);
this.serviceManager.registerService("CaffeinateService", this.caffeinate);
this.serviceManager.registerService("QueueService", this.queue);
this.serviceManager.registerService("UpdateService", this.updater);
this.serviceManager.registerService("OutgoingMessageManager", this.messageManager);
this.serviceManager.registerService("WebhookService", this.webhookService);
this.serviceManager.registerService("ScheduledMessagesService", this.scheduledMessages);
this.serviceManager.registerService("OauthService", this.oauthService);
}

/**
Expand All @@ -505,11 +521,10 @@ class BlueBubblesServer extends EventEmitter {
*/
async startServices(): Promise<void> {
try {
this.logger.info("Starting HTTP service...");
this.httpService.initialize();
this.httpService.start();
this.logger.info("Starting services using ServiceManager...");
await this.serviceManager.startServices();
} catch (ex: any) {
this.logger.error(`Failed to start HTTP service! ${ex?.message ?? String(ex)}}`);
this.logger.error(`Failed to start services using ServiceManager! ${ex?.message ?? String(ex)}}`);
}

// Only start the oauth service if the tutorial isn't done
Expand All @@ -532,13 +547,6 @@ class BlueBubblesServer extends EventEmitter {
this.logger.error(`Failed to connect to proxy service! ${ex?.message ?? String(ex)}`);
}

try {
this.logger.info("Starting Scheduled Messages service...");
await this.scheduledMessages.start();
} catch (ex: any) {
this.logger.error(`Failed to start Scheduled Messages service! ${ex?.message ?? String(ex)}}`);
}

const privateApiEnabled = this.repo.getConfig("enable_private_api") as boolean;
const ftPrivateApiEnabled = this.repo.getConfig("enable_ft_private_api") as boolean;
if (privateApiEnabled || ftPrivateApiEnabled) {
Expand All @@ -550,23 +558,16 @@ class BlueBubblesServer extends EventEmitter {
this.logger.info("Starting iMessage Database listeners...");
await this.startChatListeners();
}

try {
this.logger.info("Starting FCM service...");
await this.fcm.start();
} catch (ex: any) {
this.logger.error(`Failed to start FCM service! ${ex?.message ?? String(ex)}}`);
}
}

async stopServices(): Promise<void> {
this.isStopping = true;
this.logger.info("Stopping services...");
this.logger.info("Stopping services using ServiceManager...");

try {
FCMService.stop();
await this.serviceManager.stopServices();
} catch (ex: any) {
this.logger.info(`Failed to stop FCM service! ${ex?.message ?? ex}`);
this.logger.error(`Failed to stop services using ServiceManager! ${ex?.message ?? ex}`);
}

try {
Expand All @@ -576,37 +577,11 @@ class BlueBubblesServer extends EventEmitter {
this.logger.info(`Failed to stop iMessage database listeners! ${ex?.message ?? ex}`);
}

try {
await this.privateApi?.stop();
} catch (ex: any) {
this.logger.info(`Failed to stop Private API Helper service! ${ex?.message ?? ex}`);
}

try {
await this.stopProxyServices();
} catch (ex: any) {
this.logger.info(`Failed to stop Proxy services! ${ex?.message ?? ex}`);
}

try {
await this.httpService?.stop();
} catch (ex: any) {
this.logger.error(`Failed to stop HTTP service! ${ex?.message ?? ex}`);
}

try {
await this.oauthService?.stop();
} catch (ex: any) {
this.logger.error(`Failed to stop OAuth service! ${ex?.message ?? ex}`);
}

try {
this.scheduledMessages?.stop();
} catch (ex: any) {
this.logger.error(`Failed to stop Scheduled Messages service! ${ex?.message ?? ex}`);
}

this.logger.info("Finished stopping services...");
}

async stopServerComponents() {
Expand Down
58 changes: 58 additions & 0 deletions packages/server/src/server/lib/ServiceManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Loggable } from "./logging/Loggable";

interface Service {
start(): Promise<void>;
stop(): Promise<void>;
}

export class ServiceManager extends Loggable {
tag = "ServiceManager";

private services: Map<string, Service> = new Map();
private dependencies: Map<string, string[]> = new Map();

registerService(name: string, service: Service, dependencies: string[] = []) {
this.services.set(name, service);
this.dependencies.set(name, dependencies);
}

async startServices() {
const startedServices = new Set<string>();

for (const [name, service] of this.services) {
await this.startService(name, service, startedServices);
}
}

private async startService(name: string, service: Service, startedServices: Set<string>) {
if (startedServices.has(name)) return;

const dependencies = this.dependencies.get(name) || [];
for (const dependency of dependencies) {
const dependencyService = this.services.get(dependency);
if (dependencyService) {
await this.startService(dependency, dependencyService, startedServices);
}
}

this.log.info(`Starting service: ${name}`);
await service.start();
startedServices.add(name);
}

async stopServices() {
const stoppedServices = new Set<string>();

for (const [name, service] of Array.from(this.services).reverse()) {
await this.stopService(name, service, stoppedServices);
}
}

private async stopService(name: string, service: Service, stoppedServices: Set<string>) {
if (stoppedServices.has(name)) return;

this.log.info(`Stopping service: ${name}`);
await service.stop();
stoppedServices.add(name);
}
}