@sentry/junior-dashboard 0.58.0

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.
Files changed (89) hide show
  1. package/LICENSE +201 -0
  2. package/dist/app.d.ts +22 -0
  3. package/dist/app.js +492 -0
  4. package/dist/auth.d.ts +30 -0
  5. package/dist/client/App.d.ts +2 -0
  6. package/dist/client/api.d.ts +10 -0
  7. package/dist/client/code.d.ts +15 -0
  8. package/dist/client/components/CommandRail.d.ts +6 -0
  9. package/dist/client/components/ConversationList.d.ts +7 -0
  10. package/dist/client/components/ConversationRowStats.d.ts +6 -0
  11. package/dist/client/components/ConversationStack.d.ts +5 -0
  12. package/dist/client/components/ConversationSummary.d.ts +5 -0
  13. package/dist/client/components/EmptyTelemetry.d.ts +5 -0
  14. package/dist/client/components/FilterTabs.d.ts +6 -0
  15. package/dist/client/components/JuniorLogo.d.ts +2 -0
  16. package/dist/client/components/LoadingView.d.ts +4 -0
  17. package/dist/client/components/Section.d.ts +6 -0
  18. package/dist/client/components/SectionHeader.d.ts +6 -0
  19. package/dist/client/components/SectionTitle.d.ts +5 -0
  20. package/dist/client/components/StatusBadge.d.ts +6 -0
  21. package/dist/client/components/ToolFrame.d.ts +10 -0
  22. package/dist/client/components/Transcript.d.ts +5 -0
  23. package/dist/client/components/TranscriptHeader.d.ts +7 -0
  24. package/dist/client/components/TranscriptLoading.d.ts +2 -0
  25. package/dist/client/components/TranscriptText.d.ts +6 -0
  26. package/dist/client/components/TranscriptToolView.d.ts +9 -0
  27. package/dist/client/components/TranscriptTurn.d.ts +8 -0
  28. package/dist/client/components/TurnDurationChart.d.ts +6 -0
  29. package/dist/client/components/statusStyles.d.ts +3 -0
  30. package/dist/client/components/transcriptPreview.d.ts +4 -0
  31. package/dist/client/components/transcriptRenderModel.d.ts +30 -0
  32. package/dist/client/components/transcriptStyles.d.ts +4 -0
  33. package/dist/client/format.d.ts +64 -0
  34. package/dist/client/pages/CommandCenter.d.ts +6 -0
  35. package/dist/client/pages/ConversationPage.d.ts +5 -0
  36. package/dist/client/pages/ConversationsPage.d.ts +5 -0
  37. package/dist/client/styles.d.ts +2 -0
  38. package/dist/client/types.d.ts +153 -0
  39. package/dist/client.d.ts +7 -0
  40. package/dist/client.js +56012 -0
  41. package/dist/config.d.ts +4 -0
  42. package/dist/handler.d.ts +2 -0
  43. package/dist/handler.js +563 -0
  44. package/dist/nitro.d.ts +16 -0
  45. package/dist/nitro.js +80 -0
  46. package/dist/tailwind.css +2 -0
  47. package/package.json +60 -0
  48. package/src/app.ts +421 -0
  49. package/src/auth.ts +202 -0
  50. package/src/client/App.tsx +175 -0
  51. package/src/client/api.ts +99 -0
  52. package/src/client/code.tsx +139 -0
  53. package/src/client/components/CommandRail.tsx +74 -0
  54. package/src/client/components/ConversationList.tsx +94 -0
  55. package/src/client/components/ConversationRowStats.tsx +21 -0
  56. package/src/client/components/ConversationStack.tsx +67 -0
  57. package/src/client/components/ConversationSummary.tsx +40 -0
  58. package/src/client/components/EmptyTelemetry.tsx +11 -0
  59. package/src/client/components/FilterTabs.tsx +35 -0
  60. package/src/client/components/JuniorLogo.tsx +8 -0
  61. package/src/client/components/LoadingView.tsx +16 -0
  62. package/src/client/components/Section.tsx +17 -0
  63. package/src/client/components/SectionHeader.tsx +16 -0
  64. package/src/client/components/SectionTitle.tsx +10 -0
  65. package/src/client/components/StatusBadge.tsx +40 -0
  66. package/src/client/components/ToolFrame.tsx +52 -0
  67. package/src/client/components/Transcript.tsx +39 -0
  68. package/src/client/components/TranscriptHeader.tsx +52 -0
  69. package/src/client/components/TranscriptLoading.tsx +10 -0
  70. package/src/client/components/TranscriptText.tsx +45 -0
  71. package/src/client/components/TranscriptToolView.tsx +207 -0
  72. package/src/client/components/TranscriptTurn.tsx +487 -0
  73. package/src/client/components/TurnDurationChart.tsx +349 -0
  74. package/src/client/components/statusStyles.ts +9 -0
  75. package/src/client/components/transcriptPreview.ts +20 -0
  76. package/src/client/components/transcriptRenderModel.ts +193 -0
  77. package/src/client/components/transcriptStyles.ts +11 -0
  78. package/src/client/format.ts +628 -0
  79. package/src/client/pages/CommandCenter.tsx +37 -0
  80. package/src/client/pages/ConversationPage.tsx +145 -0
  81. package/src/client/pages/ConversationsPage.tsx +59 -0
  82. package/src/client/styles.ts +6 -0
  83. package/src/client/types.ts +153 -0
  84. package/src/client.tsx +79 -0
  85. package/src/config.ts +72 -0
  86. package/src/handler.ts +26 -0
  87. package/src/nitro.ts +110 -0
  88. package/src/tailwind.css +13 -0
  89. package/src/virtual-modules.d.ts +5 -0
@@ -0,0 +1,4 @@
1
+ import type { JuniorDashboardOptions } from "./app";
2
+ export type JuniorDashboardRuntimeConfig = Omit<JuniorDashboardOptions, "auth" | "reporting">;
3
+ /** Read dashboard runtime config injected by the Nitro module. */
4
+ export declare function resolveDashboardConfig(): Promise<JuniorDashboardRuntimeConfig>;
@@ -0,0 +1,2 @@
1
+ declare const handler: unknown;
2
+ export default handler;
@@ -0,0 +1,563 @@
1
+ // src/handler.ts
2
+ import { defineHandler } from "nitro";
3
+
4
+ // src/app.ts
5
+ import { Hono } from "hono";
6
+ import { existsSync, readFileSync } from "fs";
7
+ import path from "path";
8
+ import { createJuniorReporting } from "@sentry/junior/reporting";
9
+ import { initSentry } from "@sentry/junior/instrumentation";
10
+
11
+ // src/auth.ts
12
+ import { betterAuth } from "better-auth";
13
+ var DEFAULT_SESSION_MAX_AGE_SECONDS = 60 * 60 * 8;
14
+ function sanitizeDashboardSession(session) {
15
+ const { email, emailVerified, hostedDomain, name } = session.user;
16
+ return {
17
+ user: {
18
+ email,
19
+ emailVerified,
20
+ hostedDomain,
21
+ name
22
+ }
23
+ };
24
+ }
25
+ function required(value, name) {
26
+ if (!value?.trim()) {
27
+ throw new Error(`${name} is required for Junior dashboard auth`);
28
+ }
29
+ return value.trim();
30
+ }
31
+ function firstHostedDomain(domains) {
32
+ return domains.length === 1 ? domains[0] : void 0;
33
+ }
34
+ function withHttps(host) {
35
+ return /^https?:\/\//.test(host) ? host : `https://${host}`;
36
+ }
37
+ function stripTrailingSlashes(value) {
38
+ let end = value.length;
39
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
40
+ end -= 1;
41
+ }
42
+ return end === value.length ? value : value.slice(0, end);
43
+ }
44
+ function resolveBaseURL(config) {
45
+ const explicit = config.baseURL ?? process.env.BETTER_AUTH_URL ?? process.env.JUNIOR_BASE_URL;
46
+ if (explicit?.trim()) {
47
+ return stripTrailingSlashes(withHttps(explicit.trim()));
48
+ }
49
+ const vercelProd = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim();
50
+ if (vercelProd) {
51
+ return stripTrailingSlashes(withHttps(vercelProd));
52
+ }
53
+ const vercelUrl = process.env.VERCEL_URL?.trim();
54
+ if (vercelUrl) {
55
+ return stripTrailingSlashes(withHttps(vercelUrl));
56
+ }
57
+ return "http://localhost:3000";
58
+ }
59
+ function createDashboardAuth(config) {
60
+ const secret = required(
61
+ config.secret ?? process.env.BETTER_AUTH_SECRET ?? process.env.JUNIOR_SECRET,
62
+ "JUNIOR_SECRET or BETTER_AUTH_SECRET"
63
+ );
64
+ const baseURL = resolveBaseURL(config);
65
+ const googleClientId = required(
66
+ config.googleClientId ?? process.env.GOOGLE_CLIENT_ID,
67
+ "GOOGLE_CLIENT_ID"
68
+ );
69
+ const googleClientSecret = required(
70
+ config.googleClientSecret ?? process.env.GOOGLE_CLIENT_SECRET,
71
+ "GOOGLE_CLIENT_SECRET"
72
+ );
73
+ const auth = betterAuth({
74
+ appName: "Junior Dashboard",
75
+ baseURL,
76
+ basePath: config.authPath,
77
+ secret,
78
+ trustedOrigins: config.trustedOrigins,
79
+ socialProviders: {
80
+ google: {
81
+ clientId: googleClientId,
82
+ clientSecret: googleClientSecret,
83
+ hd: config.googleHostedDomain,
84
+ prompt: "select_account",
85
+ mapProfileToUser(profile) {
86
+ return {
87
+ email: profile.email,
88
+ emailVerified: profile.email_verified,
89
+ hostedDomain: profile.hd,
90
+ image: profile.picture,
91
+ name: profile.name
92
+ };
93
+ }
94
+ }
95
+ },
96
+ user: {
97
+ additionalFields: {
98
+ hostedDomain: {
99
+ type: "string",
100
+ required: false,
101
+ input: false,
102
+ returned: true
103
+ }
104
+ }
105
+ },
106
+ account: {
107
+ storeStateStrategy: "cookie",
108
+ storeAccountCookie: false,
109
+ updateAccountOnSignIn: false
110
+ },
111
+ session: {
112
+ expiresIn: config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
113
+ disableSessionRefresh: true,
114
+ cookieCache: {
115
+ enabled: true,
116
+ strategy: "jwe",
117
+ maxAge: config.sessionMaxAgeSeconds ?? DEFAULT_SESSION_MAX_AGE_SECONDS,
118
+ refreshCache: false
119
+ }
120
+ }
121
+ });
122
+ return {
123
+ handler(request) {
124
+ return auth.handler(request);
125
+ },
126
+ async getSession(request) {
127
+ const session = await auth.api.getSession({ headers: request.headers });
128
+ if (!session) {
129
+ return null;
130
+ }
131
+ return sanitizeDashboardSession(session);
132
+ },
133
+ async signInWithGoogle(request, callbackURL) {
134
+ const result = await auth.api.signInSocial({
135
+ body: {
136
+ provider: "google",
137
+ callbackURL
138
+ },
139
+ headers: request.headers,
140
+ returnHeaders: true
141
+ });
142
+ if (!("url" in result.response) || !result.response.url) {
143
+ throw new Error("Google sign-in did not return a redirect URL");
144
+ }
145
+ result.headers.set("location", result.response.url);
146
+ return new Response(null, {
147
+ status: 302,
148
+ headers: result.headers
149
+ });
150
+ }
151
+ };
152
+ }
153
+ function resolveGoogleHostedDomainHint(domains) {
154
+ return firstHostedDomain(domains.map((domain) => domain.toLowerCase()));
155
+ }
156
+
157
+ // src/app.ts
158
+ var DEFAULT_BASE_PATH = "/";
159
+ var DEFAULT_AUTH_PATH = "/api/auth";
160
+ var DASHBOARD_CLIENT_VERSION = Date.now().toString(36);
161
+ function hasSentryConversationLinks() {
162
+ return Boolean(
163
+ process.env.SENTRY_DSN?.trim() && process.env.SENTRY_ORG_SLUG?.trim()
164
+ );
165
+ }
166
+ function normalizePath(path2, fallback) {
167
+ const value = path2.trim() || fallback;
168
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
169
+ return stripTrailingSlashes2(withSlash);
170
+ }
171
+ function stripTrailingSlashes2(value) {
172
+ let end = value.length;
173
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
174
+ end -= 1;
175
+ }
176
+ return end === value.length ? value : value.slice(0, end);
177
+ }
178
+ function normalizeValues(values) {
179
+ return [
180
+ ...new Set(
181
+ (values ?? []).map((value) => value.trim().toLowerCase()).filter(Boolean)
182
+ )
183
+ ];
184
+ }
185
+ function isJsonRoute(pathname) {
186
+ return pathname.startsWith("/api/");
187
+ }
188
+ function dashboardLoginUrl(request) {
189
+ const url = new URL(request.url);
190
+ url.pathname = "/api/dashboard/login";
191
+ url.search = "";
192
+ return url.toString();
193
+ }
194
+ function callbackUrl(request, basePath) {
195
+ const url = new URL(request.url);
196
+ url.pathname = basePath;
197
+ url.search = "";
198
+ return url.toString();
199
+ }
200
+ function isAuthorized(session, allowedDomains, allowedEmails) {
201
+ const email = session.user.email?.toLowerCase();
202
+ const domain = session.user.hostedDomain?.toLowerCase();
203
+ if (session.user.emailVerified && email && allowedEmails.includes(email)) {
204
+ return true;
205
+ }
206
+ return Boolean(
207
+ session.user.emailVerified && domain && allowedDomains.includes(domain)
208
+ );
209
+ }
210
+ function unauthorized(request) {
211
+ if (isJsonRoute(new URL(request.url).pathname)) {
212
+ return Response.json({ error: "unauthenticated" }, { status: 401 });
213
+ }
214
+ return Response.redirect(dashboardLoginUrl(request), 302);
215
+ }
216
+ function forbidden(request) {
217
+ if (!isJsonRoute(new URL(request.url).pathname)) {
218
+ return new Response(
219
+ `<!DOCTYPE html>
220
+ <html lang="en">
221
+ <head>
222
+ <meta charset="utf-8" />
223
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
224
+ <title>Junior access denied</title>
225
+ <style>
226
+ ${readDashboardTailwind()}
227
+ </style>
228
+ </head>
229
+ <body class="m-0 bg-black font-sans text-white [color-scheme:dark]">
230
+ <main class="grid min-h-screen place-items-center p-8">
231
+ <section class="max-w-lg border-l-4 border-rose-400 pl-4">
232
+ <h1 class="m-0 mb-3 text-[1.75rem] font-bold leading-tight">Access denied</h1>
233
+ <p class="m-0 leading-relaxed text-[#b8b8b8]">Your Google account is authenticated, but it is not allowed to use this Junior dashboard.</p>
234
+ </section>
235
+ </main>
236
+ </body>
237
+ </html>`,
238
+ {
239
+ headers: {
240
+ "cache-control": "no-store",
241
+ "content-type": "text/html; charset=utf-8"
242
+ },
243
+ status: 403
244
+ }
245
+ );
246
+ }
247
+ return Response.json({ error: "forbidden" }, { status: 403 });
248
+ }
249
+ function dashboardSessionBypass() {
250
+ return {
251
+ user: {
252
+ email: "local-dashboard@localhost",
253
+ emailVerified: true,
254
+ hostedDomain: "localhost"
255
+ }
256
+ };
257
+ }
258
+ function readDashboardAsset(fileName) {
259
+ const localDistUrl = new URL(`./${fileName}`, import.meta.url);
260
+ if (existsSync(localDistUrl)) {
261
+ return readFileSync(localDistUrl, "utf8");
262
+ }
263
+ const sourceDistUrl = new URL(`../dist/${fileName}`, import.meta.url);
264
+ if (existsSync(sourceDistUrl)) {
265
+ return readFileSync(sourceDistUrl, "utf8");
266
+ }
267
+ const workspacePackagePath = path.join(
268
+ process.cwd(),
269
+ "node_modules",
270
+ "@sentry",
271
+ "junior-dashboard",
272
+ "dist",
273
+ fileName
274
+ );
275
+ if (existsSync(workspacePackagePath)) {
276
+ return readFileSync(workspacePackagePath, "utf8");
277
+ }
278
+ return "";
279
+ }
280
+ function readDashboardClient() {
281
+ const client = readDashboardAsset("client.js");
282
+ if (!client) {
283
+ throw new Error("Junior dashboard client bundle was not found");
284
+ }
285
+ return client;
286
+ }
287
+ function dashboardTimeZone() {
288
+ return process.env.JUNIOR_TIMEZONE || "America/Los_Angeles";
289
+ }
290
+ function readDashboardTailwind() {
291
+ return readDashboardAsset("tailwind.css");
292
+ }
293
+ function dashboardPagePaths(basePath) {
294
+ return [
295
+ basePath,
296
+ basePath === "/" ? "/conversations" : `${basePath}/conversations`,
297
+ basePath === "/" ? "/sessions" : `${basePath}/sessions`
298
+ ];
299
+ }
300
+ function renderDashboard(basePath) {
301
+ return new Response(
302
+ `<!DOCTYPE html>
303
+ <html lang="en">
304
+ <head>
305
+ <meta charset="utf-8" />
306
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
307
+ <title>Junior</title>
308
+ <style>
309
+ ${readDashboardTailwind()}
310
+ </style>
311
+ </head>
312
+ <body class="m-0 bg-black text-white [color-scheme:dark]">
313
+ <div id="dashboard-root"></div>
314
+ <script>
315
+ window.__JUNIOR_DASHBOARD_BASE_PATH__ = ${JSON.stringify(basePath)};
316
+ (function () {
317
+ function escapeHtml(value) {
318
+ return String(value)
319
+ .replace(/&/g, "&amp;")
320
+ .replace(/</g, "&lt;")
321
+ .replace(/>/g, "&gt;")
322
+ .replace(/"/g, "&quot;")
323
+ .replace(/'/g, "&#039;");
324
+ }
325
+ function errorText(error) {
326
+ if (!error) return "Unknown dashboard error";
327
+ if (typeof error === "string") return error;
328
+ if (error.stack) return error.stack;
329
+ if (error.message) return error.message;
330
+ try {
331
+ return JSON.stringify(error, null, 2);
332
+ } catch (_error) {
333
+ return String(error);
334
+ }
335
+ }
336
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__ = function (error) {
337
+ var root = document.getElementById("dashboard-root");
338
+ if (!root) return;
339
+ root.innerHTML =
340
+ '<main class="grid min-h-screen place-items-center bg-black p-8 text-white">' +
341
+ '<section class="w-full max-w-5xl border border-rose-400/50 bg-[#0b0b0b] p-5 font-sans">' +
342
+ '<div class="font-mono text-xs uppercase leading-none text-[#888]">Dashboard Error</div>' +
343
+ '<h1 class="mt-2 text-3xl font-bold leading-tight tracking-normal">Junior failed to render</h1>' +
344
+ '<p class="my-4 max-w-3xl text-[#b8b8b8]">The dashboard hit a client-side exception. The stack trace is shown here so the page does not fail blank.</p>' +
345
+ '<pre class="max-h-[60vh] overflow-auto whitespace-pre-wrap break-words border border-white/10 bg-black p-4 font-mono text-sm leading-relaxed text-white">' +
346
+ escapeHtml(errorText(error)) +
347
+ "</pre></section></main>";
348
+ };
349
+ window.addEventListener("error", function (event) {
350
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__(event.error || event.message);
351
+ });
352
+ window.addEventListener("unhandledrejection", function (event) {
353
+ window.__JUNIOR_DASHBOARD_SHOW_ERROR__(event.reason);
354
+ });
355
+ })();
356
+ </script>
357
+ <script type="module" src="/api/dashboard/client.js?v=${DASHBOARD_CLIENT_VERSION}"></script>
358
+ </body>
359
+ </html>`,
360
+ {
361
+ headers: {
362
+ "cache-control": "no-store",
363
+ "content-type": "text/html; charset=utf-8"
364
+ }
365
+ }
366
+ );
367
+ }
368
+ function renderFavicon() {
369
+ return new Response(
370
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="#000000"/><text x="16" y="20.5" fill="#ffffff" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" font-size="11" font-weight="900" text-anchor="middle">Jr</text></svg>`,
371
+ { headers: { "content-type": "image/svg+xml" } }
372
+ );
373
+ }
374
+ function createDashboardApp(options) {
375
+ if (process.env.SENTRY_DSN?.trim()) {
376
+ initSentry();
377
+ }
378
+ const basePath = normalizePath(
379
+ options.basePath ?? DEFAULT_BASE_PATH,
380
+ DEFAULT_BASE_PATH
381
+ );
382
+ const authPath = normalizePath(
383
+ options.authPath ?? DEFAULT_AUTH_PATH,
384
+ DEFAULT_AUTH_PATH
385
+ );
386
+ const allowedDomains = normalizeValues(options.allowedGoogleDomains);
387
+ const allowedEmails = normalizeValues(options.allowedEmails);
388
+ const authRequired = options.authRequired !== false;
389
+ if (authRequired && allowedDomains.length === 0 && allowedEmails.length === 0) {
390
+ throw new Error(
391
+ "Junior dashboard auth requires allowedGoogleDomains or allowedEmails"
392
+ );
393
+ }
394
+ const auth = authRequired ? options.auth ?? createDashboardAuth({
395
+ authPath,
396
+ trustedOrigins: options.trustedOrigins ?? [],
397
+ googleHostedDomain: resolveGoogleHostedDomainHint(allowedDomains),
398
+ sessionMaxAgeSeconds: options.sessionMaxAgeSeconds
399
+ }) : void 0;
400
+ const reporting = options.reporting ?? createJuniorReporting();
401
+ const app2 = new Hono();
402
+ if (auth) {
403
+ app2.on(["GET", "POST"], `${authPath}/*`, (c) => auth.handler(c.req.raw));
404
+ }
405
+ app2.get("/favicon.ico", () => renderFavicon());
406
+ app2.get("/api/dashboard/login", async (c) => {
407
+ if (!auth) {
408
+ return Response.redirect(callbackUrl(c.req.raw, basePath), 302);
409
+ }
410
+ return auth.signInWithGoogle(c.req.raw, callbackUrl(c.req.raw, basePath));
411
+ });
412
+ const requireDashboardSession = async (c, next) => {
413
+ if (!authRequired) {
414
+ c.set("dashboardSession", dashboardSessionBypass());
415
+ await next();
416
+ return;
417
+ }
418
+ if (!auth) {
419
+ return unauthorized(c.req.raw);
420
+ }
421
+ const session = await auth.getSession(c.req.raw);
422
+ if (!session) {
423
+ return unauthorized(c.req.raw);
424
+ }
425
+ if (!isAuthorized(session, allowedDomains, allowedEmails)) {
426
+ return forbidden(c.req.raw);
427
+ }
428
+ c.set("dashboardSession", sanitizeDashboardSession(session));
429
+ await next();
430
+ };
431
+ if (basePath === "/") {
432
+ app2.use("/*", requireDashboardSession);
433
+ } else {
434
+ app2.use(basePath, requireDashboardSession);
435
+ app2.use(`${basePath}/*`, requireDashboardSession);
436
+ }
437
+ app2.use("/api/dashboard/*", requireDashboardSession);
438
+ for (const path2 of dashboardPagePaths(basePath)) {
439
+ app2.get(path2, () => renderDashboard(basePath));
440
+ if (path2 !== "/") {
441
+ app2.get(`${path2}/*`, () => renderDashboard(basePath));
442
+ }
443
+ }
444
+ app2.get("/api/dashboard/health", async () => {
445
+ return Response.json(await reporting.getHealth());
446
+ });
447
+ app2.get("/api/dashboard/runtime", async () => {
448
+ return Response.json(await reporting.getRuntimeInfo());
449
+ });
450
+ app2.get("/api/dashboard/plugins", async () => {
451
+ return Response.json(await reporting.getPlugins());
452
+ });
453
+ app2.get("/api/dashboard/skills", async () => {
454
+ return Response.json(await reporting.getSkills());
455
+ });
456
+ app2.get("/api/dashboard/sessions", async () => {
457
+ return Response.json(await reporting.getSessions());
458
+ });
459
+ app2.get("/api/dashboard/conversations/:conversationId", async (c) => {
460
+ return Response.json(
461
+ await reporting.getConversation(
462
+ decodeURIComponent(c.req.param("conversationId"))
463
+ )
464
+ );
465
+ });
466
+ app2.get("/api/dashboard/config", () => {
467
+ return Response.json({
468
+ allowedEmailCount: allowedEmails.length,
469
+ allowedGoogleDomainCount: allowedDomains.length,
470
+ authRequired,
471
+ authPath,
472
+ basePath,
473
+ sentryConversationLinks: hasSentryConversationLinks(),
474
+ timeZone: dashboardTimeZone()
475
+ });
476
+ });
477
+ app2.get("/api/dashboard/me", (c) => {
478
+ return Response.json(c.get("dashboardSession"));
479
+ });
480
+ app2.get("/api/dashboard/info", async () => {
481
+ return Response.json(await reporting.getRuntimeInfo());
482
+ });
483
+ app2.get("/api/dashboard/client.js", () => {
484
+ return new Response(readDashboardClient(), {
485
+ headers: {
486
+ "cache-control": "no-store",
487
+ "content-type": "application/javascript; charset=utf-8"
488
+ }
489
+ });
490
+ });
491
+ return app2;
492
+ }
493
+
494
+ // src/config.ts
495
+ async function resolveDashboardConfig() {
496
+ try {
497
+ const mod = await import("#junior-dashboard/config");
498
+ return mod.dashboard ?? readEnvConfig();
499
+ } catch (error) {
500
+ if (!isMissingVirtualConfig(error)) {
501
+ throw error;
502
+ }
503
+ return readEnvConfig();
504
+ }
505
+ }
506
+ function readEnvConfig() {
507
+ return {
508
+ authRequired: process.env.JUNIOR_DASHBOARD_AUTH_REQUIRED !== "false",
509
+ allowedGoogleDomains: readListEnv("JUNIOR_DASHBOARD_GOOGLE_DOMAINS"),
510
+ allowedEmails: readListEnv("JUNIOR_DASHBOARD_ALLOWED_EMAILS"),
511
+ trustedOrigins: readListEnv("JUNIOR_DASHBOARD_TRUSTED_ORIGINS")
512
+ };
513
+ }
514
+ function readListEnv(name) {
515
+ const value = process.env[name];
516
+ if (!value?.trim()) {
517
+ return [];
518
+ }
519
+ if (value.trim().startsWith("[")) {
520
+ let parsed;
521
+ try {
522
+ parsed = JSON.parse(value);
523
+ } catch (error) {
524
+ throw new Error(`${name} must be a JSON string array`, {
525
+ cause: error
526
+ });
527
+ }
528
+ if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) {
529
+ throw new Error(`${name} must be a JSON string array`);
530
+ }
531
+ return parsed;
532
+ }
533
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
534
+ }
535
+ function isMissingVirtualConfig(error) {
536
+ if (!(error instanceof Error)) {
537
+ return false;
538
+ }
539
+ const code = error.code;
540
+ return (code === "ERR_PACKAGE_IMPORT_NOT_DEFINED" || code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") && error.message.includes("#junior-dashboard/config");
541
+ }
542
+
543
+ // src/handler.ts
544
+ var app;
545
+ var appPromise;
546
+ async function resolveApp() {
547
+ appPromise ??= resolveDashboardConfig().then((config) => {
548
+ app = createDashboardApp(config);
549
+ return app;
550
+ }).catch((error) => {
551
+ appPromise = void 0;
552
+ throw error;
553
+ });
554
+ return app ?? appPromise;
555
+ }
556
+ var handler = defineHandler(async (event) => {
557
+ const dashboardApp = await resolveApp();
558
+ return dashboardApp.fetch(event.req);
559
+ });
560
+ var handler_default = handler;
561
+ export {
562
+ handler_default as default
563
+ };
@@ -0,0 +1,16 @@
1
+ export interface JuniorDashboardNitroOptions {
2
+ basePath?: string;
3
+ authPath?: string;
4
+ authRequired?: boolean;
5
+ allowedGoogleDomains?: string[];
6
+ allowedEmails?: string[];
7
+ trustedOrigins?: string[];
8
+ sessionMaxAgeSeconds?: number;
9
+ disabled?: boolean;
10
+ }
11
+ /** Mount the authenticated Junior dashboard into a Nitro deployment. */
12
+ export declare function juniorDashboardNitro(options: JuniorDashboardNitroOptions): {
13
+ nitro: {
14
+ setup(nitro: unknown): void;
15
+ };
16
+ };
package/dist/nitro.js ADDED
@@ -0,0 +1,80 @@
1
+ // src/nitro.ts
2
+ function normalizePath(path, fallback) {
3
+ const value = path?.trim() || fallback;
4
+ const withSlash = value.startsWith("/") ? value : `/${value}`;
5
+ return stripTrailingSlashes(withSlash);
6
+ }
7
+ function stripTrailingSlashes(value) {
8
+ let end = value.length;
9
+ while (end > 1 && value.charCodeAt(end - 1) === 47) {
10
+ end -= 1;
11
+ }
12
+ return end === value.length ? value : value.slice(0, end);
13
+ }
14
+ function routeEntry(handler) {
15
+ return { handler };
16
+ }
17
+ function virtualHandler(config) {
18
+ return `import { defineHandler } from "nitro";
19
+ import { createDashboardApp } from "@sentry/junior-dashboard";
20
+
21
+ let app;
22
+
23
+ export default defineHandler(async (event) => {
24
+ app ??= createDashboardApp(${JSON.stringify(config)});
25
+ return app.fetch(event.req);
26
+ });
27
+ `;
28
+ }
29
+ function dashboardPageRoutes(basePath, handler) {
30
+ const sessionsPath = basePath === "/" ? "/sessions" : `${basePath}/sessions`;
31
+ const conversationsPath = basePath === "/" ? "/conversations" : `${basePath}/conversations`;
32
+ if (basePath === "/") {
33
+ return {
34
+ "/": routeEntry(handler),
35
+ [conversationsPath]: routeEntry(handler),
36
+ [`${conversationsPath}/**`]: routeEntry(handler),
37
+ [sessionsPath]: routeEntry(handler),
38
+ [`${sessionsPath}/**`]: routeEntry(handler)
39
+ };
40
+ }
41
+ return {
42
+ [basePath]: routeEntry(handler),
43
+ [`${basePath}/**`]: routeEntry(handler)
44
+ };
45
+ }
46
+ function juniorDashboardNitro(options) {
47
+ return {
48
+ nitro: {
49
+ setup(nitro) {
50
+ if (options.disabled) {
51
+ return;
52
+ }
53
+ const basePath = normalizePath(options.basePath, "/");
54
+ const authPath = normalizePath(options.authPath, "/api/auth");
55
+ const handler = "#junior-dashboard/handler";
56
+ const dashboardConfig = {
57
+ ...options,
58
+ basePath,
59
+ authPath,
60
+ disabled: void 0
61
+ };
62
+ nitro.options.virtual[handler] = virtualHandler(dashboardConfig);
63
+ nitro.options.virtual["#junior-dashboard/config"] = `export const dashboard = ${JSON.stringify(dashboardConfig)};`;
64
+ const dashboardRoutes = {
65
+ ...dashboardPageRoutes(basePath, handler),
66
+ "/api/dashboard/**": routeEntry(handler),
67
+ [authPath]: routeEntry(handler),
68
+ [`${authPath}/**`]: routeEntry(handler)
69
+ };
70
+ nitro.options.routes = {
71
+ ...dashboardRoutes,
72
+ ...nitro.options.routes ?? {}
73
+ };
74
+ }
75
+ }
76
+ };
77
+ }
78
+ export {
79
+ juniorDashboardNitro
80
+ };