@privateclaw/privateclaw-relay 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,366 @@
1
+ const STORAGE_KEY = "privateclawRelayAdminToken";
2
+
3
+ const elements = {
4
+ refreshButton: document.getElementById("refreshButton"),
5
+ tokenButton: document.getElementById("tokenButton"),
6
+ lastUpdatedLabel: document.getElementById("lastUpdatedLabel"),
7
+ summaryGrid: document.getElementById("summaryGrid"),
8
+ requestStats: document.getElementById("requestStats"),
9
+ activeSessionsTableBody: document.getElementById("activeSessionsTableBody"),
10
+ historySessionsTableBody: document.getElementById("historySessionsTableBody"),
11
+ instancesTableBody: document.getElementById("instancesTableBody"),
12
+ sessionDetailContent: document.getElementById("sessionDetailContent"),
13
+ detailHint: document.getElementById("detailHint"),
14
+ sessionSearchInput: document.getElementById("sessionSearchInput"),
15
+ tokenModal: document.getElementById("tokenModal"),
16
+ tokenInput: document.getElementById("tokenInput"),
17
+ tokenError: document.getElementById("tokenError"),
18
+ saveTokenButton: document.getElementById("saveTokenButton"),
19
+ };
20
+
21
+ const state = {
22
+ token: localStorage.getItem(STORAGE_KEY) || "",
23
+ selectedSessionId: null,
24
+ searchQuery: "",
25
+ refreshTimer: null,
26
+ };
27
+
28
+ function formatNumber(value) {
29
+ return new Intl.NumberFormat().format(value ?? 0);
30
+ }
31
+
32
+ function formatDateTime(value) {
33
+ if (!value) {
34
+ return "—";
35
+ }
36
+ return new Intl.DateTimeFormat(undefined, {
37
+ dateStyle: "medium",
38
+ timeStyle: "short",
39
+ }).format(new Date(value));
40
+ }
41
+
42
+ function formatDuration(ms) {
43
+ if (!ms || ms <= 0) {
44
+ return "0s";
45
+ }
46
+ const totalSeconds = Math.floor(ms / 1000);
47
+ const days = Math.floor(totalSeconds / 86400);
48
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
49
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
50
+ const seconds = totalSeconds % 60;
51
+ const parts = [];
52
+ if (days) parts.push(`${days}d`);
53
+ if (hours) parts.push(`${hours}h`);
54
+ if (minutes) parts.push(`${minutes}m`);
55
+ if (seconds || parts.length === 0) parts.push(`${seconds}s`);
56
+ return parts.slice(0, 3).join(" ");
57
+ }
58
+
59
+ function escapeHtml(value) {
60
+ return String(value)
61
+ .replaceAll("&", "&amp;")
62
+ .replaceAll("<", "&lt;")
63
+ .replaceAll(">", "&gt;")
64
+ .replaceAll('"', "&quot;")
65
+ .replaceAll("'", "&#39;");
66
+ }
67
+
68
+ function setModalVisible(visible) {
69
+ elements.tokenModal.classList.toggle("hidden", !visible);
70
+ if (visible) {
71
+ elements.tokenInput.value = state.token;
72
+ elements.tokenInput.focus();
73
+ }
74
+ }
75
+
76
+ async function fetchJson(path) {
77
+ const response = await fetch(path, {
78
+ headers: {
79
+ Authorization: `Bearer ${state.token}`,
80
+ },
81
+ });
82
+ if (response.status === 401) {
83
+ setModalVisible(true);
84
+ throw new Error("Unauthorized");
85
+ }
86
+ if (!response.ok) {
87
+ const payload = await response.json().catch(() => ({ error: response.statusText }));
88
+ throw new Error(payload.error || response.statusText);
89
+ }
90
+ return response.json();
91
+ }
92
+
93
+ function renderSummary(overview) {
94
+ const cards = [
95
+ ["Sessions", overview.totals.sessions],
96
+ ["Active sessions", overview.totals.activeSessions],
97
+ ["Known participants", overview.totals.knownParticipants],
98
+ ["Active participants", overview.totals.activeParticipants],
99
+ ["Instances", overview.totals.instances],
100
+ ["App requests", overview.requestStats.appRequests],
101
+ ["Provider requests", overview.requestStats.providerRequests],
102
+ ["Relay errors", overview.requestStats.appErrors + overview.requestStats.providerErrors],
103
+ ];
104
+ elements.summaryGrid.innerHTML = cards
105
+ .map(
106
+ ([label, value]) => `
107
+ <article class="summary-card">
108
+ <span class="muted">${escapeHtml(label)}</span>
109
+ <strong>${escapeHtml(formatNumber(value))}</strong>
110
+ </article>
111
+ `,
112
+ )
113
+ .join("");
114
+ elements.lastUpdatedLabel.textContent = `Updated ${formatDateTime(overview.generatedAt)}`;
115
+ }
116
+
117
+ function renderRequestStats(overview) {
118
+ const requestTypes = overview.requestStats.requestTypes
119
+ .slice(0, 10)
120
+ .map(
121
+ (entry) => `
122
+ <div class="request-type">
123
+ <div class="muted">${escapeHtml(entry.actor)} · ${escapeHtml(entry.type)}</div>
124
+ <strong>${escapeHtml(formatNumber(entry.ok + entry.error))}</strong>
125
+ <div class="muted">ok ${escapeHtml(formatNumber(entry.ok))} · err ${escapeHtml(formatNumber(entry.error))}</div>
126
+ </div>
127
+ `,
128
+ )
129
+ .join("");
130
+
131
+ const errorCodes = overview.requestStats.errorCodes.length
132
+ ? overview.requestStats.errorCodes
133
+ .map(
134
+ (entry) => `
135
+ <div class="error-chip">
136
+ <div>${escapeHtml(entry.code)}</div>
137
+ <strong>${escapeHtml(formatNumber(entry.count))}</strong>
138
+ </div>
139
+ `,
140
+ )
141
+ .join("")
142
+ : '<div class="muted">No relay errors recorded.</div>';
143
+
144
+ elements.requestStats.innerHTML = `
145
+ <div class="stat-chip-row">
146
+ <div class="stat-chip">App success<strong>${escapeHtml(formatNumber(overview.requestStats.appSuccesses))}</strong></div>
147
+ <div class="stat-chip">App errors<strong>${escapeHtml(formatNumber(overview.requestStats.appErrors))}</strong></div>
148
+ <div class="stat-chip">Provider success<strong>${escapeHtml(formatNumber(overview.requestStats.providerSuccesses))}</strong></div>
149
+ <div class="stat-chip">Provider errors<strong>${escapeHtml(formatNumber(overview.requestStats.providerErrors))}</strong></div>
150
+ <div class="stat-chip">App frames<strong>${escapeHtml(formatNumber(overview.requestStats.appFrames))}</strong></div>
151
+ <div class="stat-chip">Provider frames<strong>${escapeHtml(formatNumber(overview.requestStats.providerFrames))}</strong></div>
152
+ </div>
153
+ <div>
154
+ <h3>Top request types</h3>
155
+ <div class="chip-list">${requestTypes || '<div class="muted">No request traffic yet.</div>'}</div>
156
+ </div>
157
+ <div>
158
+ <h3>Top error codes</h3>
159
+ <div class="chip-list">${errorCodes}</div>
160
+ </div>
161
+ `;
162
+ }
163
+
164
+ function statusPill(status) {
165
+ return `<span class="status-pill ${escapeHtml(status)}">${escapeHtml(status)}</span>`;
166
+ }
167
+
168
+ function renderSessions(tableBody, sessions) {
169
+ if (!sessions.length) {
170
+ tableBody.innerHTML = '<tr><td colspan="7" class="muted">No sessions found.</td></tr>';
171
+ return;
172
+ }
173
+ tableBody.innerHTML = sessions
174
+ .map(
175
+ (session) => `
176
+ <tr class="session-row" data-session-id="${escapeHtml(session.sessionId)}">
177
+ <td>${statusPill(session.status)}</td>
178
+ <td><code>${escapeHtml(session.sessionId)}</code></td>
179
+ <td>
180
+ <div><code>${escapeHtml(session.providerId)}</code></div>
181
+ <div class="provider-state ${session.providerOnline ? "online" : "offline"}">
182
+ ${session.providerOnline ? "provider online" : "provider offline"}
183
+ </div>
184
+ </td>
185
+ <td>${escapeHtml(formatNumber(session.activeParticipantCount))} / ${escapeHtml(formatNumber(session.distinctParticipantCount))}</td>
186
+ <td>${escapeHtml(formatNumber(session.appMessageCount + session.providerMessageCount))}</td>
187
+ <td>${escapeHtml(formatDateTime(session.expiresAt))}</td>
188
+ <td>${escapeHtml(formatDateTime(session.updatedAt))}</td>
189
+ </tr>
190
+ `,
191
+ )
192
+ .join("");
193
+ tableBody.querySelectorAll("tr[data-session-id]").forEach((row) => {
194
+ row.addEventListener("click", () => {
195
+ state.selectedSessionId = row.getAttribute("data-session-id");
196
+ void refreshSessionDetail();
197
+ });
198
+ });
199
+ }
200
+
201
+ function renderInstances(instances) {
202
+ if (!instances.length) {
203
+ elements.instancesTableBody.innerHTML = '<tr><td colspan="5" class="muted">No live relay instances.</td></tr>';
204
+ return;
205
+ }
206
+ elements.instancesTableBody.innerHTML = instances
207
+ .map(
208
+ (instance) => `
209
+ <tr>
210
+ <td>
211
+ <div><code>${escapeHtml(instance.instanceId)}</code></div>
212
+ <div class="muted">RSS ${escapeHtml(formatNumber(instance.memoryUsage.rss))} B</div>
213
+ </td>
214
+ <td>${escapeHtml(formatNumber(instance.activeProviders))}</td>
215
+ <td>${escapeHtml(formatNumber(instance.activeApps))}</td>
216
+ <td>${escapeHtml(formatNumber(instance.localSessions))}</td>
217
+ <td>${escapeHtml(formatDateTime(instance.lastSeenAt))}</td>
218
+ </tr>
219
+ `,
220
+ )
221
+ .join("");
222
+ }
223
+
224
+ function renderSessionDetail(detail) {
225
+ if (!detail) {
226
+ elements.sessionDetailContent.className = "detail-content empty-state";
227
+ elements.sessionDetailContent.textContent = "No session selected.";
228
+ elements.detailHint.textContent = "Select a session to inspect participants and activity.";
229
+ return;
230
+ }
231
+
232
+ const participantRows = detail.participants.length
233
+ ? detail.participants
234
+ .map(
235
+ (participant) => `
236
+ <tr>
237
+ <td><code>${escapeHtml(participant.appId)}</code></td>
238
+ <td>
239
+ <div class="participant-state ${participant.isOnline ? "online" : "offline"}">
240
+ ${participant.isOnline ? "online" : "offline"}
241
+ </div>
242
+ <div class="muted">${escapeHtml(formatDuration(participant.currentConnectedMs))}</div>
243
+ </td>
244
+ <td>${escapeHtml(formatNumber(participant.connectionCount))}</td>
245
+ <td>${escapeHtml(formatNumber(participant.messageCount))}</td>
246
+ <td>${escapeHtml(formatDateTime(participant.lastSeenAt))}</td>
247
+ <td>${escapeHtml(participant.lastDisconnectReason || "—")}</td>
248
+ </tr>
249
+ `,
250
+ )
251
+ .join("")
252
+ : '<tr><td colspan="6" class="muted">No participants recorded for this session.</td></tr>';
253
+
254
+ elements.detailHint.textContent = `Session ${detail.session.sessionId}`;
255
+ elements.sessionDetailContent.className = "detail-content";
256
+ elements.sessionDetailContent.innerHTML = `
257
+ <div class="detail-grid">
258
+ <article class="detail-metric"><span class="muted">Provider</span><strong>${escapeHtml(detail.session.providerId)}</strong></article>
259
+ <article class="detail-metric"><span class="muted">Status</span><strong>${escapeHtml(detail.session.status)}</strong></article>
260
+ <article class="detail-metric"><span class="muted">Group mode</span><strong>${detail.session.groupMode ? "yes" : "no"}</strong></article>
261
+ <article class="detail-metric"><span class="muted">Participants</span><strong>${escapeHtml(formatNumber(detail.session.activeParticipantCount))} / ${escapeHtml(formatNumber(detail.session.distinctParticipantCount))}</strong></article>
262
+ <article class="detail-metric"><span class="muted">App messages</span><strong>${escapeHtml(formatNumber(detail.session.appMessageCount))}</strong></article>
263
+ <article class="detail-metric"><span class="muted">Provider messages</span><strong>${escapeHtml(formatNumber(detail.session.providerMessageCount))}</strong></article>
264
+ </div>
265
+ <div class="table-wrap">
266
+ <table>
267
+ <thead>
268
+ <tr>
269
+ <th>Participant</th>
270
+ <th>Online time</th>
271
+ <th>Joins</th>
272
+ <th>Messages</th>
273
+ <th>Last seen</th>
274
+ <th>Last disconnect</th>
275
+ </tr>
276
+ </thead>
277
+ <tbody>${participantRows}</tbody>
278
+ </table>
279
+ </div>
280
+ `;
281
+ }
282
+
283
+ async function refreshSessionDetail() {
284
+ if (!state.selectedSessionId) {
285
+ renderSessionDetail(null);
286
+ return;
287
+ }
288
+ try {
289
+ renderSessionDetail(
290
+ await fetchJson(`/api/admin/sessions/${encodeURIComponent(state.selectedSessionId)}`),
291
+ );
292
+ } catch (error) {
293
+ if (String(error) !== "Error: Unauthorized") {
294
+ elements.sessionDetailContent.className = "detail-content empty-state";
295
+ elements.sessionDetailContent.textContent = `Failed to load session detail: ${error.message}`;
296
+ }
297
+ }
298
+ }
299
+
300
+ async function refreshDashboard() {
301
+ if (!state.token) {
302
+ setModalVisible(true);
303
+ return;
304
+ }
305
+
306
+ try {
307
+ const [overview, activeSessions, historySessions, instances] = await Promise.all([
308
+ fetchJson("/api/admin/overview"),
309
+ fetchJson("/api/admin/sessions?status=active&pageSize=100"),
310
+ fetchJson(
311
+ `/api/admin/sessions?status=all&pageSize=100&query=${encodeURIComponent(state.searchQuery)}`,
312
+ ),
313
+ fetchJson("/api/admin/instances"),
314
+ ]);
315
+ renderSummary(overview);
316
+ renderRequestStats(overview);
317
+ renderSessions(elements.activeSessionsTableBody, activeSessions.sessions);
318
+ renderSessions(elements.historySessionsTableBody, historySessions.sessions);
319
+ renderInstances(instances.instances);
320
+ await refreshSessionDetail();
321
+ } catch (error) {
322
+ if (String(error) === "Error: Unauthorized") {
323
+ return;
324
+ }
325
+ elements.requestStats.innerHTML = `<div class="error-chip">${escapeHtml(error.message)}</div>`;
326
+ }
327
+ }
328
+
329
+ function scheduleRefresh() {
330
+ if (state.refreshTimer) {
331
+ clearInterval(state.refreshTimer);
332
+ }
333
+ state.refreshTimer = setInterval(() => {
334
+ void refreshDashboard();
335
+ }, 15000);
336
+ }
337
+
338
+ elements.refreshButton.addEventListener("click", () => {
339
+ void refreshDashboard();
340
+ });
341
+
342
+ elements.tokenButton.addEventListener("click", () => {
343
+ setModalVisible(true);
344
+ });
345
+
346
+ elements.saveTokenButton.addEventListener("click", async () => {
347
+ const nextToken = elements.tokenInput.value.trim();
348
+ if (!nextToken) {
349
+ elements.tokenError.textContent = "Token is required.";
350
+ return;
351
+ }
352
+ state.token = nextToken;
353
+ localStorage.setItem(STORAGE_KEY, nextToken);
354
+ elements.tokenError.textContent = "";
355
+ setModalVisible(false);
356
+ await refreshDashboard();
357
+ });
358
+
359
+ elements.sessionSearchInput.addEventListener("input", () => {
360
+ state.searchQuery = elements.sessionSearchInput.value.trim();
361
+ void refreshDashboard();
362
+ });
363
+
364
+ setModalVisible(!state.token);
365
+ scheduleRefresh();
366
+ void refreshDashboard();
@@ -0,0 +1,145 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>PrivateClaw Relay Admin</title>
7
+ <link rel="stylesheet" href="./admin.css" />
8
+ <script type="module" src="./admin.js"></script>
9
+ </head>
10
+ <body>
11
+ <div class="shell">
12
+ <header class="hero">
13
+ <div>
14
+ <p class="eyebrow">PrivateClaw Relay</p>
15
+ <h1>Relay Admin</h1>
16
+ <p class="subtitle">
17
+ Session history, active participants, relay request stats, and instance health in one place.
18
+ </p>
19
+ </div>
20
+ <div class="hero-actions">
21
+ <button id="refreshButton" class="ghost-button">Refresh</button>
22
+ <button id="tokenButton" class="ghost-button">Token</button>
23
+ </div>
24
+ </header>
25
+
26
+ <main class="layout">
27
+ <section class="card">
28
+ <div class="card-header">
29
+ <h2>Overview</h2>
30
+ <p id="lastUpdatedLabel" class="muted">Waiting for first refresh…</p>
31
+ </div>
32
+ <div id="summaryGrid" class="summary-grid"></div>
33
+ </section>
34
+
35
+ <section class="card">
36
+ <div class="card-header">
37
+ <h2>Request &amp; response status</h2>
38
+ <p class="muted">Aggregated across all relay instances.</p>
39
+ </div>
40
+ <div id="requestStats" class="request-stats"></div>
41
+ </section>
42
+
43
+ <div class="two-column">
44
+ <section class="card">
45
+ <div class="card-header">
46
+ <h2>Current active sessions</h2>
47
+ <p class="muted">Sessions that are still open or not expired.</p>
48
+ </div>
49
+ <div class="table-wrap">
50
+ <table>
51
+ <thead>
52
+ <tr>
53
+ <th>Session</th>
54
+ <th>Provider</th>
55
+ <th>Participants</th>
56
+ <th>Messages</th>
57
+ <th>Expires</th>
58
+ </tr>
59
+ </thead>
60
+ <tbody id="activeSessionsTableBody"></tbody>
61
+ </table>
62
+ </div>
63
+ </section>
64
+
65
+ <section class="card">
66
+ <div class="card-header">
67
+ <h2>Relay instances</h2>
68
+ <p class="muted">Live instance heartbeat snapshots.</p>
69
+ </div>
70
+ <div class="table-wrap">
71
+ <table>
72
+ <thead>
73
+ <tr>
74
+ <th>Instance</th>
75
+ <th>Providers</th>
76
+ <th>Apps</th>
77
+ <th>Sessions</th>
78
+ <th>Last seen</th>
79
+ </tr>
80
+ </thead>
81
+ <tbody id="instancesTableBody"></tbody>
82
+ </table>
83
+ </div>
84
+ </section>
85
+ </div>
86
+
87
+ <section class="card">
88
+ <div class="card-header split-header">
89
+ <div>
90
+ <h2>Session history</h2>
91
+ <p class="muted">Search by session id or provider id.</p>
92
+ </div>
93
+ <label class="search-field">
94
+ <span>Search</span>
95
+ <input id="sessionSearchInput" type="search" placeholder="session / provider" />
96
+ </label>
97
+ </div>
98
+ <div class="table-wrap">
99
+ <table>
100
+ <thead>
101
+ <tr>
102
+ <th>Status</th>
103
+ <th>Session</th>
104
+ <th>Provider</th>
105
+ <th>Participants</th>
106
+ <th>App msgs</th>
107
+ <th>Provider msgs</th>
108
+ <th>Updated</th>
109
+ </tr>
110
+ </thead>
111
+ <tbody id="historySessionsTableBody"></tbody>
112
+ </table>
113
+ </div>
114
+ </section>
115
+
116
+ <section class="card detail-card">
117
+ <div class="card-header">
118
+ <h2>Session detail</h2>
119
+ <p id="detailHint" class="muted">Select a session to inspect participants and activity.</p>
120
+ </div>
121
+ <div id="sessionDetailContent" class="detail-content empty-state">
122
+ No session selected.
123
+ </div>
124
+ </section>
125
+ </main>
126
+ </div>
127
+
128
+ <div id="tokenModal" class="modal hidden" role="dialog" aria-modal="true">
129
+ <div class="modal-panel">
130
+ <h2>Admin token</h2>
131
+ <p class="muted">
132
+ Enter the fixed relay admin token. It is stored only in this browser’s local storage.
133
+ </p>
134
+ <label class="token-field">
135
+ <span>Bearer token</span>
136
+ <input id="tokenInput" type="password" autocomplete="off" />
137
+ </label>
138
+ <p id="tokenError" class="error-text"></p>
139
+ <div class="modal-actions">
140
+ <button id="saveTokenButton" class="primary-button">Save token</button>
141
+ </div>
142
+ </div>
143
+ </div>
144
+ </body>
145
+ </html>
package/dist/config.d.ts CHANGED
@@ -3,9 +3,14 @@ export interface RelayServerConfig {
3
3
  port: number;
4
4
  sessionTtlMs: number;
5
5
  frameCacheSize: number;
6
+ maxMessageBytes?: number;
7
+ appMessagesPerMinute?: number;
8
+ providerMessagesPerMinute?: number;
6
9
  webRootDir?: string;
10
+ adminWebRootDir?: string;
7
11
  instanceId?: string;
8
12
  redisUrl?: string;
13
+ adminToken?: string;
9
14
  fcmServiceAccountJson?: string;
10
15
  fcmProjectId?: string;
11
16
  fcmClientEmail?: string;
package/dist/config.js CHANGED
@@ -13,9 +13,13 @@ export function loadRelayConfig(env = process.env) {
13
13
  const port = parsePositiveInteger(env.PRIVATECLAW_RELAY_PORT?.trim() || env.PORT?.trim(), 8787, "PRIVATECLAW_RELAY_PORT");
14
14
  const sessionTtlMs = parsePositiveInteger(env.PRIVATECLAW_SESSION_TTL_MS, 15 * 60 * 1000, "PRIVATECLAW_SESSION_TTL_MS");
15
15
  const frameCacheSize = parsePositiveInteger(env.PRIVATECLAW_FRAME_CACHE_SIZE, 25, "PRIVATECLAW_FRAME_CACHE_SIZE");
16
+ const maxMessageBytes = parsePositiveInteger(env.PRIVATECLAW_MAX_MESSAGE_BYTES, 24 * 1024 * 1024, "PRIVATECLAW_MAX_MESSAGE_BYTES");
17
+ const appMessagesPerMinute = parsePositiveInteger(env.PRIVATECLAW_APP_MESSAGES_PER_MINUTE, 120, "PRIVATECLAW_APP_MESSAGES_PER_MINUTE");
18
+ const providerMessagesPerMinute = parsePositiveInteger(env.PRIVATECLAW_PROVIDER_MESSAGES_PER_MINUTE, 600, "PRIVATECLAW_PROVIDER_MESSAGES_PER_MINUTE");
16
19
  const instanceId = env.PRIVATECLAW_RELAY_INSTANCE_ID?.trim() ||
17
20
  env.RAILWAY_REPLICA_ID?.trim();
18
21
  const redisUrl = env.PRIVATECLAW_REDIS_URL?.trim() || env.REDIS_URL?.trim();
22
+ const adminToken = env.PRIVATECLAW_ADMIN_TOKEN?.trim();
19
23
  const fcmServiceAccountJson = env.PRIVATECLAW_FCM_SERVICE_ACCOUNT_JSON?.trim();
20
24
  const fcmProjectId = env.PRIVATECLAW_FCM_PROJECT_ID?.trim();
21
25
  const fcmClientEmail = env.PRIVATECLAW_FCM_CLIENT_EMAIL?.trim();
@@ -25,8 +29,12 @@ export function loadRelayConfig(env = process.env) {
25
29
  port,
26
30
  sessionTtlMs,
27
31
  frameCacheSize,
32
+ maxMessageBytes,
33
+ appMessagesPerMinute,
34
+ providerMessagesPerMinute,
28
35
  ...(instanceId ? { instanceId } : {}),
29
36
  ...(redisUrl ? { redisUrl } : {}),
37
+ ...(adminToken ? { adminToken } : {}),
30
38
  ...(fcmServiceAccountJson ? { fcmServiceAccountJson } : {}),
31
39
  ...(fcmProjectId ? { fcmProjectId } : {}),
32
40
  ...(fcmClientEmail ? { fcmClientEmail } : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAcA,SAAS,oBAAoB,CAC3B,KAAyB,EACzB,QAAgB,EAChB,KAAa;IAEb,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8BAA8B,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,MAAM,IAAI,GAAG,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,WAAW,CAAC;IAC/D,MAAM,IAAI,GAAG,oBAAoB,CAC/B,GAAG,CAAC,sBAAsB,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,EACtD,IAAI,EACJ,wBAAwB,CACzB,CAAC;IACF,MAAM,YAAY,GAAG,oBAAoB,CACvC,GAAG,CAAC,0BAA0B,EAC9B,EAAE,GAAG,EAAE,GAAG,IAAI,EACd,4BAA4B,CAC7B,CAAC;IACF,MAAM,cAAc,GAAG,oBAAoB,CACzC,GAAG,CAAC,4BAA4B,EAChC,EAAE,EACF,8BAA8B,CAC/B,CAAC;IACF,MAAM,UAAU,GACd,GAAG,CAAC,6BAA6B,EAAE,IAAI,EAAE;QACzC,GAAG,CAAC,kBAAkB,EAAE,IAAI,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,IAAI,GAAG,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IAC5E,MAAM,qBAAqB,GACzB,GAAG,CAAC,oCAAoC,EAAE,IAAI,EAAE,CAAC;IACnD,MAAM,YAAY,GAAG,GAAG,CAAC,0BAA0B,EAAE,IAAI,EAAE,CAAC;IAC5D,MAAM,cAAc,GAAG,GAAG,CAAC,4BAA4B,EAAE,IAAI,EAAE,CAAC;IAChE,MAAM,aAAa,GAAG,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,CAAC;IAE9D,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,YAAY;QACZ,cAAc;QACd,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACjC,GAAG,CAAC,qBAAqB,CAAC,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7C,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5C,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./admin-metrics-store.js";
1
2
  export * from "./config.js";
2
3
  export * from "./provider-setup.js";
3
4
  export * from "./relay-server.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./admin-metrics-store.js";
1
2
  export * from "./config.js";
2
3
  export * from "./provider-setup.js";
3
4
  export * from "./relay-server.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,0BAA0B,CAAC;AACzC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,uBAAuB,CAAC"}