feat: backend inline capability registration

This commit is contained in:
DecDuck
2025-05-15 16:05:46 +10:00
parent 6dad3aeab7
commit 8e3ae01a30
4 changed files with 76 additions and 21 deletions

View File

@ -16,17 +16,6 @@ model Client {
platform Platform
lastConnected DateTime
peerAPI ClientPeerAPIConfiguration?
lastAccessedSaves SaveSlot[]
tokens APIToken[]
}
model ClientPeerAPIConfiguration {
id String @id @default(uuid())
clientId String @unique
client Client @relation(fields: [clientId], references: [id])
endpoints String[]
}
}

View File

@ -1,3 +1,10 @@
import type {
CapabilityConfiguration,
InternalClientCapability,
} from "~/server/internal/clients/capabilities";
import capabilityManager, {
validCapabilities,
} from "~/server/internal/clients/capabilities";
import clientHandler from "~/server/internal/clients/handler";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
@ -6,6 +13,8 @@ export default defineEventHandler(async (h3) => {
const name = body.name;
const platformRaw = body.platform;
const capabilities: Partial<CapabilityConfiguration> =
body.capabilities ?? {};
if (!name || !platformRaw)
throw createError({
@ -20,7 +29,46 @@ export default defineEventHandler(async (h3) => {
statusMessage: "Invalid or unsupported platform",
});
const clientId = await clientHandler.initiate({ name, platform });
if (!capabilities || typeof capabilities !== "object")
throw createError({
statusCode: 400,
statusMessage: "Capabilities must be an array",
});
const capabilityIterable = Object.entries(capabilities) as Array<
[InternalClientCapability, object]
>;
if (
capabilityIterable.length > 0 &&
capabilityIterable
.map(([capability]) => validCapabilities.find((v) => capability == v))
.filter((e) => e).length == 0
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capabilities.",
});
if (
capabilityIterable.length > 0 &&
capabilityIterable.filter(
([capability, configuration]) =>
!capabilityManager.validateCapabilityConfiguration(
capability,
configuration,
),
).length > 0
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capability configuration.",
});
const clientId = await clientHandler.initiate({
name,
platform,
capabilities,
});
return `/client/${clientId}/callback`;
});

View File

@ -1,6 +1,4 @@
import type { EnumDictionary } from "../utils/types";
import https from "https";
import { useCertificateAuthority } from "~/server/plugins/ca";
import prisma from "../db/database";
import { ClientCapabilities } from "~/prisma/client";
@ -17,7 +15,7 @@ export enum InternalClientCapability {
export const validCapabilities = Object.values(InternalClientCapability);
export type CapabilityConfiguration = {
[InternalClientCapability.PeerAPI]: { endpoints: string[] };
[InternalClientCapability.PeerAPI]: object;
[InternalClientCapability.UserStatus]: object;
[InternalClientCapability.CloudSaves]: object;
};
@ -27,6 +25,7 @@ class CapabilityManager {
InternalClientCapability,
(configuration: object) => Promise<boolean>
> = {
/*
[InternalClientCapability.PeerAPI]: async (rawConfiguration) => {
const configuration =
rawConfiguration as CapabilityConfiguration[InternalClientCapability.PeerAPI];
@ -71,12 +70,13 @@ class CapabilityManager {
valid = true;
break;
} catch {
/* empty */
}
}
return valid;
},
*/
[InternalClientCapability.PeerAPI]: async () => true,
[InternalClientCapability.UserStatus]: async () => true, // No requirements for user status
[InternalClientCapability.CloudSaves]: async () => true, // No requirements for cloud saves
};
@ -92,7 +92,7 @@ class CapabilityManager {
async upsertClientCapability(
capability: InternalClientCapability,
rawCapability: object,
rawCapabilityConfiguration: object,
clientId: string,
) {
const upsertFunctions: EnumDictionary<
@ -100,8 +100,7 @@ class CapabilityManager {
() => Promise<void> | void
> = {
[InternalClientCapability.PeerAPI]: async function () {
const configuration =
rawCapability as CapabilityConfiguration[InternalClientCapability.PeerAPI];
// const configuration =rawCapability as CapabilityConfiguration[InternalClientCapability.PeerAPI];
const currentClient = await prisma.client.findUnique({
where: { id: clientId },
@ -110,6 +109,7 @@ class CapabilityManager {
},
});
if (!currentClient) throw new Error("Invalid client ID");
/*
if (currentClient.capabilities.includes(ClientCapabilities.PeerAPI)) {
await prisma.clientPeerAPIConfiguration.update({
where: { clientId },
@ -126,6 +126,7 @@ class CapabilityManager {
endpoints: configuration.endpoints,
},
});
*/
await prisma.client.update({
where: { id: clientId },

View File

@ -2,10 +2,13 @@ import { randomUUID } from "node:crypto";
import prisma from "../db/database";
import type { Platform } from "~/prisma/client";
import { useCertificateAuthority } from "~/server/plugins/ca";
import type { CapabilityConfiguration, InternalClientCapability } from "./capabilities";
import capabilityManager from "./capabilities";
export interface ClientMetadata {
name: string;
platform: Platform;
capabilities: Partial<CapabilityConfiguration>;
}
export class ClientHandler {
@ -75,7 +78,7 @@ export class ClientHandler {
if (!metadata) throw new Error("Invalid client ID");
if (!metadata.userId) throw new Error("Un-authorized client ID");
return await prisma.client.create({
const client = await prisma.client.create({
data: {
id: id,
userId: metadata.userId,
@ -87,6 +90,20 @@ export class ClientHandler {
lastConnected: new Date(),
},
});
for (const [capability, configuration] of Object.entries(
metadata.data.capabilities,
)) {
await capabilityManager.upsertClientCapability(
capability as InternalClientCapability,
configuration,
client.id,
);
}
this.temporaryClientTable.delete(id);
return client;
}
async removeClient(id: string) {