Changes (/shrug)

* Added middleware timer for performance debugging
* Relationships and avatar database keys
* CDN
* Profiles are SelfAccounts in most cases, rather than Accounts
* Simplified profile content management
* Progression fixes
* Relationships (favorites not yet implemented)
* Relationship backend
* Relationship and avatar routes
This commit is contained in:
2025-03-31 01:48:46 -04:00
parent 639e809a20
commit 638c0fbf1f
19 changed files with 999 additions and 65 deletions

View File

@@ -338,4 +338,12 @@ export function LoginLock(rq: express.Request<NoBody, NoBody, { LoginLock: strin
export type NoBody = Record<string | number | symbol, never>;
export function startTimer(rq: express.Request, rs: express.Response, nxt: express.NextFunction) {
rs.locals.timer = performance.now();
nxt();
}
export function stopTimer(_rq: express.Request, rs: express.Response) {
log.n(`(${rs.locals.reqId.substring(0, 11)}) Middleware took ${(performance.now() - rs.locals.timer).toString().substring(0, 6)} ms`);
}
export * as APIUtils from "./apiutils.ts";

View File

@@ -0,0 +1,46 @@
import { Redis } from "../../db.ts";
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
interface AvatarSettings {
OutfitSelections: string,
HairColor: string,
SkinColor: string,
FaceFeatures: string
}
export class ProfileAvatarManager extends ProfileContentManager {
#rootKey = Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
this.profileId.toString(),
Redis.KeyGroups.Profiles.Avatar.Root
);
async setAvatar(settings: AvatarSettings) {
const keys = Redis.KeyGroups.Profiles.Avatar;
await Promise.all([
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Outfit), settings.OutfitSelections),
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Hair), settings.HairColor),
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Skin), settings.SkinColor),
Redis.Database.set(Redis.buildKey(this.#rootKey, keys.Face), settings.FaceFeatures),
]);
}
async getAvatar() {
const keys = Redis.KeyGroups.Profiles.Avatar;
const [outfit, hair, skin, face] = await Promise.all([
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Outfit)),
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Hair)),
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Skin)),
Redis.Database.get(Redis.buildKey(this.#rootKey, keys.Face))
]);
return {
OutfitSelections: outfit ?? "",
HairColor: hair ?? "",
SkinColor: skin ?? "",
FaceFeatures: face ?? ""
} as AvatarSettings;
}
}

View File

@@ -1,15 +1,9 @@
export class ProfileContentManager {
profileNotSetError = new Error("The profile on this manager is not set.");
profileId: number | null = null;
setProfile(id: number) {
this.profileId = id;
constructor(profileId: number) {
this.profileId = profileId;
}
profileIsSet() {
return this.profileId !== null;
}
profileId: number;
}

View File

@@ -1,5 +1,4 @@
import Logging from "@proxnet/undead-logging";
import { Config } from "../../config.ts";
import { GameConfigs } from "../config.ts";
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
import { Redis } from "../../db.ts";
@@ -10,26 +9,12 @@ const config = GameConfigs.getConfig();
export class ProfileProgressionManager extends ProfileContentManager {
async #getNextLevelRequiredXp() {
const xp = await this.getXp();
if (typeof config?.LevelProgressionMaps == 'undefined') return null;
for (const item of config?.LevelProgressionMaps) {
if (xp >= item.RequiredXp) {
const next = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item) + 1];
if (typeof next == 'undefined') return null;
else return next.RequiredXp;
}
}
}
/**
* Set the profile's exact # of XP
* @returns The new # of XP
*/
async setXp(xp: number) {
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
await Redis.Database.set(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp), xp.toString());
return xp;
}
@@ -56,7 +41,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
for (const item of config?.LevelProgressionMaps) {
if (xp >= item.RequiredXp) {
const current = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item)];
const current = config?.LevelProgressionMaps[config?.LevelProgressionMaps.indexOf(item) + 1];
if (typeof current == 'undefined') return 1;
else return current.Level;
@@ -65,8 +50,7 @@ export class ProfileProgressionManager extends ProfileContentManager {
}
async getXp() {
if (!this.profileIsSet()) throw this.profileNotSetError;
let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Xp));
let data = await Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Xp));
if (data == null) data = (await this.setXp(0)).toString();
const parsedData = parseInt(data);

View File

@@ -1,4 +1,5 @@
import { Profile } from "../profiles.ts";
import { Redis } from "../../db.ts";
import UnifiedProfile, { Profile } from "../profiles.ts";
import { ProfileContentManager } from "./base/profilemanagerbase.ts";
enum RelationshipType {
@@ -19,7 +20,8 @@ interface Relationship {
PlayerID: number, // target player, not this player
RelationshipType: RelationshipType,
Muted: ReciprocalStatus,
Ignored: ReciprocalStatus
Ignored: ReciprocalStatus,
Favorited?: ReciprocalStatus
}
export class ProfileRelationshipManager extends ProfileContentManager {
@@ -29,36 +31,181 @@ export class ProfileRelationshipManager extends ProfileContentManager {
PlayerID: 1,
RelationshipType: RelationshipType.Friend,
Muted: ReciprocalStatus.None,
Ignored: ReciprocalStatus.None
Ignored: ReciprocalStatus.None,
Favorited: ReciprocalStatus.Mutual
},
{
PlayerID: 2,
RelationshipType: RelationshipType.Friend,
Muted: ReciprocalStatus.None,
Ignored: ReciprocalStatus.None
Ignored: ReciprocalStatus.None,
Favorited: ReciprocalStatus.Mutual
}
]
#rootKey = Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
this.profileId.toString(),
Redis.KeyGroups.Profiles.Relationships.Root
);
#ignoringKey = Redis.buildKey(
this.#rootKey,
Redis.KeyGroups.Profiles.Relationships.Ignoring
);
#mutedKey = Redis.buildKey(
this.#rootKey,
Redis.KeyGroups.Profiles.Relationships.Muted
);
#friendsKey = Redis.buildKey(
this.#rootKey,
Redis.KeyGroups.Profiles.Relationships.Friends
);
#incomingFriends = Redis.buildKey(
this.#rootKey,
Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests
);
#outgoingFriends = Redis.buildKey(
this.#rootKey,
Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests
);
getRelationships() {
return this.#baseRelationships; // temporary
}
setPlayerIgnored(player: Profile) {
async getAllIgnored() {
return (await Redis.Database.smembers(this.#ignoringKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
}
setPlayerMuted(player: Profile) {
async removePlayerIgnored(player: Profile) {
await Redis.Database.srem(this.#ignoringKey, player.getId().toString());
}
getPlayerRelationship(player: Profile) {
async setPlayerIgnored(player: Profile) {
await Redis.Database.sadd(this.#ignoringKey, player.getId().toString());
}
sendPlayerFriendRequest(player: Profile) {
async getAllMuted() {
return (await Redis.Database.smembers(this.#mutedKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
}
async removePlayerMuted(player: Profile) {
await Redis.Database.srem(this.#mutedKey, player.getId().toString());
}
async setPlayerMuted(player: Profile) {
await Redis.Database.sadd(this.#mutedKey, player.getId().toString());
}
async getAllFriends() {
return (await Redis.Database.smembers(this.#friendsKey)).map(val => parseInt(val)).filter(val => !isNaN(val));
}
async #getIncomingRequests() {
return (await Redis.Database.smembers(this.#incomingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val));
}
async #getOutgoingRequests() {
return (await Redis.Database.smembers(this.#outgoingFriends)).map(val => parseInt(val)).filter(val => !isNaN(val));
}
createRemoteRootKey(remoteProfileId: number) {
return Redis.buildKey(
Redis.KeyGroups.Profiles.Root,
remoteProfileId.toString(),
Redis.KeyGroups.Profiles.Relationships.Root
);
}
async #clearAssociationWithRemote(remoteProfileId: number) {
const remoteRootKey = this.createRemoteRootKey(remoteProfileId);
await Redis.Database.srem(this.#incomingFriends, remoteProfileId);
await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.OutgoingFriendRequests), this.profileId);
await Redis.Database.srem(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
await Redis.Database.srem(this.#outgoingFriends, remoteProfileId);
}
async acceptRequest(remoteProfileId: number) {
const requests = await this.#getIncomingRequests();
if (!requests.includes(remoteProfileId)) return;
await this.#clearAssociationWithRemote(remoteProfileId);
const remoteRootKey = this.createRemoteRootKey(remoteProfileId);
await Redis.Database.sadd(this.#friendsKey, remoteProfileId);
await Redis.Database.sadd(Redis.buildKey(remoteRootKey, Redis.KeyGroups.Profiles.Relationships.Friends), this.profileId);
}
async denyRequest(remoteProfileId: number) {
const requests = await this.#getIncomingRequests();
if (!requests.includes(remoteProfileId)) return;
await this.#clearAssociationWithRemote(remoteProfileId);
}
async getRelationshipType(remoteProfileId: number) {
const isFriendsWithRemote = (await Redis.Database.sismember(this.#friendsKey, remoteProfileId)) >= 1;
const remoteSentRequest = (await this.#getIncomingRequests()).includes(remoteProfileId);
const localSentRequest = (await this.#getOutgoingRequests()).includes(remoteProfileId);
if (isFriendsWithRemote || (remoteSentRequest && localSentRequest)) return RelationshipType.Friend;
else if (remoteSentRequest) return RelationshipType.FriendRequestReceived;
else if (localSentRequest) return RelationshipType.FriendRequestSent;
else return RelationshipType.None;
}
async getMutedReciprocal(remoteProfileId: number) {
const remoteKey = this.createRemoteRootKey(remoteProfileId);
const localMuted = (await this.getAllMuted()).includes(remoteProfileId);
const remoteMuted = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Muted), this.profileId);
if (localMuted && remoteMuted) return ReciprocalStatus.Mutual;
else if (localMuted) return ReciprocalStatus.Local;
else if (remoteMuted) return ReciprocalStatus.Remote;
else return ReciprocalStatus.None;
}
async getIgnoredReciprocal(remoteProfileId: number) {
const remoteKey = this.createRemoteRootKey(remoteProfileId);
const localIgnored = (await this.getAllMuted()).includes(remoteProfileId);
const remoteIgnored = await Redis.Database.sismember(Redis.buildKey(remoteKey, Redis.KeyGroups.Profiles.Relationships.Ignoring), this.profileId);
if (localIgnored && remoteIgnored) return ReciprocalStatus.Mutual;
else if (localIgnored) return ReciprocalStatus.Local;
else if (remoteIgnored) return ReciprocalStatus.Remote;
else return ReciprocalStatus.None;
}
async getPlayerRelationship(player: Profile) {
const relationship: Relationship = {
PlayerID: player.getId(),
RelationshipType: await this.getRelationshipType(player.getId()),
Muted: await this.getMutedReciprocal(player.getId()),
Ignored: await this.getIgnoredReciprocal(player.getId()),
}
return relationship;
}
async sendPlayerFriendRequest(player: Profile) {
await Redis.Database.sadd(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
await Redis.Database.sadd(this.#outgoingFriends, player.getId());
}
async removePlayerFriendRequest(player: Profile) {
await Redis.Database.srem(Redis.buildKey(this.createRemoteRootKey(player.getId()), Redis.KeyGroups.Profiles.Relationships.IncomingFriendRequests), this.profileId);
await Redis.Database.srem(this.#outgoingFriends, player.getId());
}
async ignoreAllAssociatedPlatformUsers(platformid: string) {
const ids = (await Redis.Database.smembers(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, platformid))).map(val => parseInt(val)).filter(val => !isNaN(val));
for (const id of ids) this.setPlayerIgnored(UnifiedProfile.get(id));
}
}

View File

@@ -10,31 +10,26 @@ export interface Setting {
export class ProfileSettingsManager extends ProfileContentManager {
async getSettings() {
if (!this.profileIsSet()) throw this.profileNotSetError;
const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings));
const settings = await Redis.Database.hgetall(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
const returnSettings: Setting[] = [];
for (const key of Object.keys(settings)) returnSettings.push({ Key: key, Value: settings[key] });
return returnSettings;
}
async getSetting(key: SettingKey) {
if (!this.profileIsSet()) throw this.profileNotSetError;
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key);
return await Redis.Database.hget(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
}
async setSetting(key: SettingKey, value: string) {
if (!this.profileIsSet()) throw this.profileNotSetError;
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
await Redis.Database.hset(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key, value);
}
async delSetting(key: SettingKey) {
if (!this.profileIsSet()) throw this.profileNotSetError;
await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings), key);
await Redis.Database.hdel(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings), key);
}
async delAllSettings() {
if (!this.profileIsSet()) throw this.profileNotSetError;
await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId!.toString(), Redis.KeyGroups.Profiles.Settings));
await Redis.Database.del(Redis.buildKey(Redis.KeyGroups.Profiles.Root, this.profileId.toString(), Redis.KeyGroups.Profiles.Settings));
}
}

View File

@@ -13,6 +13,7 @@ import { ProfileProgressionManager } from "./profile/progression.ts";
import { ProfileReputationManager } from "./profile/reputation.ts";
import Logging from "@proxnet/undead-logging";
import { ProfileRelationshipManager } from "./profile/relationships.ts";
import { ProfileAvatarManager } from "./profile/avatar.ts";
const config = Config.getConfig();
@@ -35,6 +36,12 @@ interface AccountExport {
username: string;
displayName: string;
}
interface SelfAccountExport extends AccountExport {
email?: string,
phone?: string,
juniorState?: number,
parentAccountId?: number
}
export interface ProfileTokenFormat extends TokenBaseFormat {
sub: number;
role: "developer" | "user";
@@ -120,7 +127,7 @@ class Profile {
}
// surely this can be written better
static getExportAccount(id: number): Promise<AccountExport | null> {
static getExportAccount(id: number): Promise<SelfAccountExport | null> {
return new Promise((resolve, _reject) => {
Redis.Database.get(Redis.buildKey(Redis.KeyGroups.Profiles.Root, id.toString(), Redis.KeyGroups.Profiles.Username)).then((val) => {
if (val == null) resolve(null);
@@ -140,6 +147,7 @@ class Profile {
platforms: 1,
username: values[2] == null ? "DATABASEERROR" : values[2],
displayName: values[3] == null ? (values[2] == null ? "DATABASEERROR" : values[2]) : values[3],
email: "" // removes a notification in messages (untested)
});
});
}
@@ -160,19 +168,20 @@ class Profile {
#socket: SignalRSocketHandler | null = null;
Settings = new ProfileSettingsManager();
Progression = new ProfileProgressionManager();
Reputation = new ProfileReputationManager();
Relationships = new ProfileRelationshipManager();
Settings: ProfileSettingsManager;
Progression: ProfileProgressionManager;
Reputation: ProfileReputationManager;
Relationships: ProfileRelationshipManager;
Avatar: ProfileAvatarManager;
constructor(id: number) {
this.#id = id;
// Set IDs for all content managers
this.Settings.setProfile(this.#id);
this.Progression.setProfile(this.#id);
this.Reputation.setProfile(this.#id);
this.Relationships.setProfile(this.#id);
this.Settings = new ProfileSettingsManager(this.#id);
this.Progression = new ProfileProgressionManager(this.#id);
this.Reputation = new ProfileReputationManager(this.#id);
this.Relationships = new ProfileRelationshipManager(this.#id);
this.Avatar = new ProfileAvatarManager(this.#id);
}
setInstance(instance: RoomInstance | null) {

View File

@@ -58,6 +58,7 @@ export const KeyGroups = {
Rooms: "rooms",
},
Profile_Usernames: "profile-usernames",
PlatformAssociations: "platforms",
Profiles: {
Root: "profiles",
Username: "username",
@@ -68,6 +69,21 @@ export const KeyGroups = {
Settings: "settings",
DeviceClass: "deviceClass",
Xp: "xp",
Relationships: {
Root: "relationships",
IncomingFriendRequests: "incomingFriendRequests",
OutgoingFriendRequests: "outgoingFriendRequests",
Friends: "friends",
Ignoring: "ignore",
Muted: "muted",
},
Avatar: {
Root: "avatar",
Outfit: "outfit",
Hair: "hair",
Skin: "skin",
Face: "face"
}
},
Operators: "operators",
Users: {

View File

@@ -55,7 +55,8 @@ app.disable("x-powered-by");
app.use(
(rq: express.Request, rs: express.Response, nxt: express.NextFunction) => {
rs.setHeader("Instance", instanceId);
log.n(`${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
rs.locals.reqId = generateRandomString(12);
log.n(`${rs.locals.reqId} ${APIUtils.getSrcIpDefault(rq)} ${rq.method} ${rq.originalUrl}`);
nxt();
},
);
@@ -78,6 +79,7 @@ const accountRouter = await import("./routes/account.ts");
const imgRouter = await import("./routes/img.ts");
const matchRouter = await import("./routes/match.ts");
const notifyRouter = await import("./socket/route.ts");
const cdnRouter = await import("./routes/cdn.ts");
app.use(nameserverRouter.route.path, nameserverRouter.route.router);
app.use(apiRouter.route.path, apiRouter.route.router);
@@ -87,6 +89,7 @@ app.use(accountRouter.route.path, accountRouter.route.router);
app.use(imgRouter.route.path, imgRouter.route.router);
app.use(matchRouter.route.path, matchRouter.route.router);
app.use(notifyRouter.route.path, notifyRouter.route.router);
app.use(cdnRouter.route.path, cdnRouter.route.router);
app.use((rq: express.Request, rs: express.Response) => {
log.e(`${APIUtils.getSrcIpDefault(rq)} 404 ${rq.method} ${rq.url.toString()}`);

View File

@@ -9,6 +9,7 @@ import { route as PlayersRoute } from "./api/players.ts"
import { route as SettingsRoute } from "./api/settings.ts";
import { route as PlayerSubscriptionsRoute } from "./api/playersubscriptions.ts";
import { route as PlayerReputationRoute } from "./api/playerReputation.ts";
import { route as AvatarRoute } from "./api/avatar.ts";
export const route = APIUtils.createRouter("/api");
@@ -21,4 +22,5 @@ route.router.use(RelationshipsRoute.path, RelationshipsRoute.router);
route.router.use(PlayersRoute.path, PlayersRoute.router);
route.router.use(SettingsRoute.path, SettingsRoute.router);
route.router.use(PlayerSubscriptionsRoute.path, PlayerSubscriptionsRoute.router);
route.router.use(PlayerReputationRoute.path, PlayerReputationRoute.router);
route.router.use(PlayerReputationRoute.path, PlayerReputationRoute.router);
route.router.use(AvatarRoute.path, AvatarRoute.router);

15
src/routes/api/avatar.ts Normal file
View File

@@ -0,0 +1,15 @@
import { APIUtils } from "../../apiutils.ts";
import { AuthType } from "../../data/users.ts";
export const route = APIUtils.createRouter("/avatar");
route.router.get('/v2',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
async (_rq, rs) => {
rs.json(await rs.locals.profile.Avatar.getAvatar());
},
);

View File

@@ -1,5 +1,7 @@
import { APIUtils } from "../../apiutils.ts";
import { z } from "zod";
import { APIUtils, NoBody } from "../../apiutils.ts";
import { AuthType } from "../../data/users.ts";
import express from "express";
export const route = APIUtils.createRouter("/relationships");
@@ -12,4 +14,28 @@ route.router.get('/v2/get',
rs.json(rs.locals.profile.Relationships.getRelationships());
}
);
interface BulkIngorePlatformBody {
Platform: string,
PlatformIds: string[]
}
const bulkIgnorePlatformSchema = z.object({
Platform: z.number(),
PlatformIds: z.array(z.string())
});
route.router.post('/v1/bulkignoreplatformusers',
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Game),
express.json(),
APIUtils.validateRequestBody(bulkIgnorePlatformSchema),
(rq: express.Request<NoBody, NoBody, BulkIngorePlatformBody>, rs: express.Response) => {
for (const id of rq.body.PlatformIds) rs.locals.profile.Relationships.ignoreAllAssociatedPlatformUsers(id);
rs.sendStatus(200);
},
);

View File

@@ -6,6 +6,7 @@ import { Config } from "../../config.ts";
import Logging from "@proxnet/undead-logging";
import { z } from "zod";
import { AuthType } from "../../data/users.ts";
import { Redis } from "../../db.ts";
const config = Config.getConfig();
@@ -75,6 +76,7 @@ interface TokenResponseBody {
route.router.post("/token",
APIUtils.startTimer,
APIUtils.Authentication,
APIUtils.AuthenticationType(AuthType.Web),
express.urlencoded({ extended: true }),
@@ -83,6 +85,7 @@ route.router.post("/token",
async (
rq: express.Request<NoBody, NoBody, TokenRequestBody>,
rs: express.Response<TokenResponseBody>,
nxt: express.NextFunction
) => {
function requestFailed(msg: string = "invalid_request") {
@@ -146,6 +149,7 @@ route.router.post("/token",
rs.locals.user.addAssociatedDeviceId(rq.body.device_id);
rs.locals.user.addAssociatedPlatformId(rq.body.platform_id);
Redis.Database.sadd(Redis.buildKey(Redis.KeyGroups.PlatformAssociations, rq.body.platform_id), targetAccount);
const profile = UnifiedProfile.get(targetAccount);
if (!(await Profile.exists(profile.getId()))) {
@@ -163,5 +167,9 @@ route.router.post("/token",
});
await profile.setKnownDeviceClass(Number(rq.body.device_class));
nxt();
},
APIUtils.stopTimer
);

6
src/routes/cdn.ts Normal file
View File

@@ -0,0 +1,6 @@
import { APIUtils } from "../apiutils.ts";
import { route as ConfigRoute } from "./cdn/config.ts";
export const route = APIUtils.createRouter("/cdn");
route.router.use(ConfigRoute.path, ConfigRoute.router);

13
src/routes/cdn/config.ts Normal file
View File

@@ -0,0 +1,13 @@
import { APIUtils } from "../../apiutils.ts";
export const route = APIUtils.createRouter("/config");
route.router.get('/LoadingScreenTipData',
APIUtils.Authentication,
(_rq, rs) => {
rs.json([]);
}
);

View File

@@ -84,7 +84,6 @@ route.router.post('/heartbeat',
async (_rq, rs) => {
const pres = await Presence.get(rs.locals.profile);
await pres.update();
log.d(`pres heartbeat for ${rs.locals.profile.getId()}: ${JSON.stringify(await pres.export())}`);
rs.json(await pres.export());
}

View File

@@ -119,7 +119,7 @@ export class SignalRSocketHandler {
destroy(sock: SignalRSocketHandler) {
return () => {
sock.sendRaw({ type: 7, error: "Socket closed by server" });
sock.sendRaw({ type: 7, error: "Socket closed" });
sock.#socket.close();
sock.#log.i(`Closed hub socket`);
}

View File

@@ -7,6 +7,8 @@ declare global {
profile: Profile;
user: User;
token: string | undefined;
timer: number
reqId: string
}
}
}