Monorepo for Aesthetic.Computer
aesthetic.computer
1// Profile Stream Publisher, 2026.02.27
2// Sends server-side profile events to session-server /profile-event.
3
4import { connect } from "./database.mjs";
5
6const PROFILE_SECRET_CACHE_MS = 60 * 1000;
7let profileSecretCacheValue = null;
8let profileSecretCacheAt = 0;
9let profileSecretLoadPromise = null;
10
11function normalizeHandle(handle) {
12 if (!handle) return null;
13 const text = `${handle}`.trim();
14 if (!text) return null;
15 if (!text.startsWith("@")) return null;
16 return `@${text.replace(/^@+/, "")}`;
17}
18
19function pickProfileStreamSecret(record) {
20 if (!record || typeof record !== "object") return null;
21 const candidates = [
22 record.secret,
23 record.token,
24 record.profileSecret,
25 record.value,
26 ];
27 for (const raw of candidates) {
28 if (!raw) continue;
29 const value = `${raw}`.trim();
30 if (value) return value;
31 }
32 return null;
33}
34
35async function loadProfileStreamSecretFromMongo() {
36 const database = await connect();
37 try {
38 const record = await database.db
39 .collection("secrets")
40 .findOne({ _id: "profile-stream" });
41 return pickProfileStreamSecret(record);
42 } finally {
43 await database.disconnect?.();
44 }
45}
46
47async function resolveProfileStreamSecret() {
48 const now = Date.now();
49 if (profileSecretCacheAt && now - profileSecretCacheAt < PROFILE_SECRET_CACHE_MS) {
50 return profileSecretCacheValue;
51 }
52
53 if (profileSecretLoadPromise) return profileSecretLoadPromise;
54
55 profileSecretLoadPromise = (async () => {
56 let secret = null;
57
58 try {
59 secret = await loadProfileStreamSecretFromMongo();
60 } catch (err) {
61 console.warn(
62 "Profile stream secret read from MongoDB failed:",
63 err?.message || err,
64 );
65 }
66
67 if (!secret) {
68 const envSecret = `${process.env.PROFILE_STREAM_SECRET || ""}`.trim();
69 secret = envSecret || null;
70 }
71
72 profileSecretCacheValue = secret;
73 profileSecretCacheAt = Date.now();
74 return profileSecretCacheValue;
75 })();
76
77 try {
78 return await profileSecretLoadPromise;
79 } finally {
80 profileSecretLoadPromise = null;
81 }
82}
83
84function profileEventBaseUrl() {
85 const configured =
86 process.env.SESSION_SERVER_PROFILE_EVENT_URL || process.env.SESSION_SERVER_URL;
87 if (configured) return `${configured}`.replace(/\/$/, "");
88 return "https://session-server.aesthetic.computer";
89}
90
91export async function publishProfileEvent(payload = {}, options = {}) {
92 const handle = normalizeHandle(payload.handle);
93 if (!handle) return false;
94
95 const timeoutMs = Number(options.timeoutMs) || 1500;
96 const body = {
97 ...payload,
98 handle,
99 };
100
101 const headers = {
102 "Content-Type": "application/json",
103 };
104
105 const profileSecret = await resolveProfileStreamSecret();
106 if (profileSecret) {
107 headers["x-profile-secret"] = profileSecret;
108 }
109
110 const controller = new AbortController();
111 const timer = setTimeout(() => controller.abort(), timeoutMs);
112
113 try {
114 const response = await fetch(`${profileEventBaseUrl()}/profile-event`, {
115 method: "POST",
116 headers,
117 body: JSON.stringify(body),
118 signal: controller.signal,
119 });
120
121 if (!response.ok) {
122 const text = await response.text().catch(() => "");
123 console.warn(
124 `Profile event publish failed (${response.status}):`,
125 text || response.statusText,
126 );
127 return false;
128 }
129
130 return true;
131 } catch (err) {
132 const reason = err?.name === "AbortError" ? "timeout" : err?.message;
133 console.warn("Profile event publish error:", reason);
134 return false;
135 } finally {
136 clearTimeout(timer);
137 }
138}