Skip to content
This repository was archived by the owner on Apr 13, 2025. It is now read-only.

Replace message based dashboard communication with a http endpoint #408

Closed
wants to merge 1 commit into from
Closed
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
12 changes: 10 additions & 2 deletions nodecg-io-core/dashboard/authentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { updateMonacoLayout } from "./serviceInstance";
import { setPassword, isPasswordSet } from "./crypto";
import { callCoreApi } from "./core";

// HTML elements
const spanLoaded = document.getElementById("spanLoaded") as HTMLSpanElement;
Expand Down Expand Up @@ -41,7 +42,13 @@ document.addEventListener("DOMContentLoaded", () => {

export async function isLoaded(): Promise<boolean> {
return new Promise((resolve, _reject) => {
nodecg.sendMessage("isLoaded", (_err, res) => resolve(res));
callCoreApi<boolean>({ type: "isLoaded" }).then((result) => {
if (result.failed) {
resolve(false);
} else {
resolve(result.result);
}
});
setTimeout(() => resolve(false), 5000); // Fallback in case connection gets lost.
});
}
Expand Down Expand Up @@ -79,7 +86,8 @@ export async function loadFramework(): Promise<void> {
}

async function updateFirstStartupLabel(): Promise<void> {
const isFirstStartup: boolean = await nodecg.sendMessage("isFirstStartup");
const isFirstStartupRes = await callCoreApi<boolean>({ type: "isFirstStartup" });
const isFirstStartup = isFirstStartupRes.failed === false && isFirstStartupRes.result;
if (isFirstStartup) {
pFirstStartup?.classList.remove("hidden");
} else {
Expand Down
9 changes: 5 additions & 4 deletions nodecg-io-core/dashboard/bundles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { updateOptionsArr, updateOptionsMap } from "./utils/selectUtils";
import { SetServiceDependencyMessage } from "nodecg-io-core/extension/messageManager";
import { config, sendAuthenticatedMessage } from "./crypto";
import { SetServiceDependencyRequest } from "nodecg-io-core/extension/dashboardApi";
import { config, callCoreApiAuthenticated } from "./crypto";

document.addEventListener("DOMContentLoaded", () => {
config.onChange(() => {
Expand Down Expand Up @@ -114,14 +114,15 @@ export function unsetAllBundleDependencies(): void {
}

async function setServiceDependency(bundle: string, instance: string | undefined, serviceType: string): Promise<void> {
const msg: Partial<SetServiceDependencyMessage> = {
const msg: Partial<SetServiceDependencyRequest> = {
type: "setServiceDependency",
bundleName: bundle,
instanceName: instance,
serviceType,
};

try {
await sendAuthenticatedMessage("setServiceDependency", msg);
await callCoreApiAuthenticated(msg);
} catch (err) {
nodecg.log.error(err);
}
Expand Down
25 changes: 25 additions & 0 deletions nodecg-io-core/dashboard/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Result } from "nodecg-io-core";
import { DashboardApiRequest } from "../extension/dashboardApi";

/**
* Calls a function on the dashboard api of nodecg-io-core using http.
* Throws if the request is calling a non existing function or a invalid password is provided.
* If the api responds with an error, it will be returned as a error result.
*
* @param msg the message to send to the dashboard api
*/
export async function callCoreApi<R>(msg: DashboardApiRequest): Promise<Result<R>> {
const response = await fetch("/nodecg-io-core/", {
method: "POST",
body: JSON.stringify(msg),
headers: {
"Content-Type": "application/json",
},
});

if (response.status === 200) {
return response.json();
} else {
throw new Error(`Unexpected response from API: ${response.status}`);
}
}
17 changes: 11 additions & 6 deletions nodecg-io-core/dashboard/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import { PersistentData, EncryptedData, decryptData } from "nodecg-io-core/exten
import { EventEmitter } from "events";
import { ObjectMap, ServiceInstance, ServiceDependency, Service } from "nodecg-io-core/extension/service";
import { isLoaded } from "./authentication";
import { PasswordMessage } from "nodecg-io-core/extension/messageManager";
import { callCoreApi } from "./core";
import { Result } from "nodecg-io-core";
import { AuthenticatedRequest } from "../extension/dashboardApi";

const encryptedData = nodecg.Replicant<EncryptedData>("encryptedConfig");
let services: Service<unknown, never>[] | undefined;
Expand Down Expand Up @@ -77,11 +79,11 @@ export async function setPassword(pw: string): Promise<boolean> {
return true;
}

export async function sendAuthenticatedMessage<V>(messageName: string, message: Partial<PasswordMessage>): Promise<V> {
export async function callCoreApiAuthenticated<V>(message: Partial<AuthenticatedRequest>): Promise<Result<V>> {
if (password === undefined) throw "No password available";
const msgWithAuth = Object.assign({}, message);
const msgWithAuth = Object.assign({}, message) as AuthenticatedRequest;
msgWithAuth.password = password;
return await nodecg.sendMessage(messageName, msgWithAuth);
return await callCoreApi(msgWithAuth);
}

/**
Expand Down Expand Up @@ -128,14 +130,17 @@ function persistentData2ConfigData(data: PersistentData | undefined): ConfigData
}

async function fetchServices() {
services = await nodecg.sendMessage("getServices");
const result = await callCoreApi<Array<Service<unknown, never>>>({ type: "getServices" });
if (!result.failed) {
services = result.result;
}
}

async function loadFramework(): Promise<boolean> {
if (await isLoaded()) return true;

try {
await nodecg.sendMessage("load", { password });
await callCoreApi({ type: "load", password });
return true;
} catch {
return false;
Expand Down
25 changes: 14 additions & 11 deletions nodecg-io-core/dashboard/serviceInstance.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as monaco from "monaco-editor";
import {
CreateServiceInstanceMessage,
DeleteServiceInstanceMessage,
UpdateInstanceConfigMessage,
} from "nodecg-io-core/extension/messageManager";
CreateServiceInstanceRequest,
DeleteServiceInstanceRequest,
UpdateInstanceConfigRequest,
} from "nodecg-io-core/extension/dashboardApi";
import { updateOptionsArr, updateOptionsMap } from "./utils/selectUtils";
import { objectDeepCopy } from "./utils/deepCopy";
import { config, sendAuthenticatedMessage } from "./crypto";
import { config, callCoreApiAuthenticated } from "./crypto";
import { ObjectMap } from "nodecg-io-core/extension/service";

const editorDefaultText = "<---- Select a service instance to start editing it in here";
Expand Down Expand Up @@ -139,12 +139,13 @@ export async function saveInstanceConfig(): Promise<void> {
try {
const instName = selectInstance.options[selectInstance.selectedIndex]?.value;
const config = JSON.parse(editor.getValue());
const msg: Partial<UpdateInstanceConfigMessage> = {
const msg: Partial<UpdateInstanceConfigRequest> = {
type: "updateInstanceConfig",
config: config,
instanceName: instName,
};
showNotice("Saving...");
await sendAuthenticatedMessage("updateInstanceConfig", msg);
await callCoreApiAuthenticated(msg);
showNotice("Successfully saved.");
} catch (err) {
nodecg.log.error(`Couldn't save instance config: ${err}`);
Expand All @@ -154,11 +155,12 @@ export async function saveInstanceConfig(): Promise<void> {

// Delete button
export async function deleteInstance(): Promise<void> {
const msg: Partial<DeleteServiceInstanceMessage> = {
const msg: Partial<DeleteServiceInstanceRequest> = {
type: "deleteServiceInstance",
instanceName: selectInstance.options[selectInstance.selectedIndex]?.value,
};

const deleted = await sendAuthenticatedMessage("deleteServiceInstance", msg);
const deleted = await callCoreApiAuthenticated(msg);
if (deleted) {
selectServiceInstance("select");
} else {
Expand All @@ -174,13 +176,14 @@ export async function createInstance(): Promise<void> {
const service = selectService.options[selectService.options.selectedIndex]?.value;
const name = inputInstanceName.value;

const msg: Partial<CreateServiceInstanceMessage> = {
const msg: Partial<CreateServiceInstanceRequest> = {
type: "createServiceInstance",
serviceType: service,
instanceName: name,
};

try {
await sendAuthenticatedMessage("createServiceInstance", msg);
await callCoreApiAuthenticated(msg);
} catch (e) {
showNotice(String(e));
return;
Expand Down
Loading