@dudousxd/nestjs-telescope-ui 1.16.1 → 1.18.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # @dudousxd/nestjs-telescope-ui
2
2
 
3
+ ## 1.18.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#63](https://github.com/DavideCarvalho/nestjs-telescope/pull/63) [`2d0e76e`](https://github.com/DavideCarvalho/nestjs-telescope/commit/2d0e76ee52e3a556c24b567fdb4b9e6cef25c067) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **Headless console launcher: `openTelescopeConsole` / `mintTelescopeConsoleSession` / `telescopeConsoleSessionUrl`, exported from `@dudousxd/nestjs-telescope-ui/client`.**
8
+
9
+ The console is entered from the HOST's app: a browser navigation to it carries no identity, so
10
+ something inside the host has to mint the Mode A session cookie first (an XHR that _does_ carry the
11
+ host's auth), then navigate. Every host was writing that by hand, which meant hardcoding two things
12
+ this package owns:
13
+
14
+ - **the session endpoint's path** — `<path>/api/auth/session`. Nothing tells a host when that moves; the break
15
+ only shows up as a runtime 404 after a version bump.
16
+ - **`redirect: 'manual'`** — and this one is a real trap. `fetch` follows redirects by default, so a
17
+ host whose auth layer rewrites a 401 into a sign-in redirect gets a resolved 200 against the
18
+ sign-in HTML. `response.ok` reads true, the caller navigates, and the user lands in a console with
19
+ no session — indistinguishable from a permissions bug. The helper detects the redirect (browser
20
+ opaque response _and_ Node/undici 3xx) and throws a message naming the likely cause.
21
+
22
+ ```ts
23
+ import { openTelescopeConsole } from "@dudousxd/nestjs-telescope-ui/client";
24
+
25
+ await openTelescopeConsole({
26
+ headers: () => ({ Authorization: `Bearer ${token()}` }),
27
+ });
28
+ ```
29
+
30
+ No UI: the host owns the button, the page and the copy. `headers` accepts a sync or async function
31
+ so a refreshing token is read at call time rather than captured at wiring time. `fetch` and
32
+ `navigate` are injectable (tests, routers, non-browser callers). A refused mint throws
33
+ `ConsoleSessionError` (carrying `status` and `url`) and **does not navigate** — a denied user gets a
34
+ real error instead of the console's "no session" page.
35
+
36
+ Additive only: nothing existing changes.
37
+
38
+ ## 1.17.0
39
+
40
+ ### Minor Changes
41
+
42
+ - [#61](https://github.com/DavideCarvalho/nestjs-telescope/pull/61) [`c3ee743`](https://github.com/DavideCarvalho/nestjs-telescope/commit/c3ee743fd7c51b34dcc2b349d07659ce9d5d7cbe) Thanks [@DavideCarvalho](https://github.com/DavideCarvalho)! - **`dashboardAuth.unauthenticatedPage` — hosts can now render the dashboard's unauthenticated page themselves.**
43
+
44
+ Under Mode A, a visitor navigating straight to `/telescope` with no cookie got the SPA shell, which
45
+ then rendered the built-in auth screen: _"open this console from your application."_ Deliberately
46
+ generic, because the library cannot know who hosts it — it can't name the host's launcher, link to
47
+ it, or look like the rest of the host's product.
48
+
49
+ ```ts
50
+ const dashboardAuth = {
51
+ secret: process.env.TELESCOPE_AUTH_SECRET,
52
+ session: (request) => resolveAdmin(request),
53
+ unauthenticatedPage: ({ request, response, basePath }) => {
54
+ (response as Response)
55
+ .status(401)
56
+ .render("console-locked", { returnTo: basePath });
57
+ },
58
+ };
59
+
60
+ TelescopeModule.forRoot({ enabled: true, dashboardAuth });
61
+ TelescopeUiModule.forRoot({ dashboardAuth }); // <- also here; see below
62
+ ```
63
+
64
+ Telescope's auth screen is a React component inside the published bundle — there is no
65
+ server-rendered page to replace. So the hook gates the **SPA shell route**: the session is checked
66
+ before the shell is served, which also means the bundle no longer loads at all for a visitor with no
67
+ session. Hashed assets stay ungated; they carry no data.
68
+
69
+ **`TelescopeUiModule` gains a `dashboardAuth` option, and it must be given the same config.**
70
+ `TelescopeModule` (core) gates the JSON API; `TelescopeUiModule` serves the HTML, so it is the only
71
+ one that can replace that page — the same independence the two modules' `guards` options already
72
+ have. Setting `unauthenticatedPage` on core alone changes nothing a visitor sees. Only `secret`, the
73
+ configured modes and `unauthenticatedPage` are read there; minting and renewal stay core's job.
74
+
75
+ **Mode-A-only by design.** With `login` configured the hook is ignored: under Mode B the login form
76
+ the visitor needs is _inside_ the bundle this page would replace, so gating the shell would lock a
77
+ Mode B host out of its own dashboard.
78
+
79
+ Fail-closed by construction: it only runs when the request carries no valid session cookie (a cookie
80
+ signed with another secret does not count), and every data route stays behind `TelescopeGuard`
81
+ regardless. A hook that throws, or returns without writing, logs one warning and serves the SPA
82
+ rather than hanging the request or turning a navigation into a `500`.
83
+
84
+ The UI shell route became non-passthrough `@Res()` so the host can own the response; its
85
+ `Content-Type`/`Cache-Control` moved from decorators into a new `sendHtml` helper, unchanged. Core
86
+ additionally exports `sendHtml` / `responseAlreadyWritten`.
87
+
88
+ Fully backward compatible — omit the option and the shell is served exactly as before, with no
89
+ session check on that route at all.
90
+
3
91
  ## 1.16.1
4
92
 
5
93
  ### Patch Changes
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Headless client for opening the console from YOUR app.
3
+ *
4
+ * The console SPA is served at a literal path and knows nothing about your app's auth, so a plain
5
+ * browser navigation to it carries no identity. Mode A closes that gap: an XHR from inside your app
6
+ * — which DOES carry your auth — posts to the console's session endpoint, the `session` hook
7
+ * decides, and the library answers with its own signed cookie. The navigation that follows rides it.
8
+ *
9
+ * Telescope's mount is configured as `path` (no leading slash, e.g. `'telescope'`); this accepts either
10
+ * form and normalizes it.
11
+ *
12
+ * No UI here on purpose: you own the button, the page and the copy. This module owns the two things
13
+ * a host would otherwise have to rediscover — where the session endpoint actually lives, and how to
14
+ * call it without the failure mode below.
15
+ */
16
+ /**
17
+ * Where `POST` mints the console session cookie.
18
+ *
19
+ * Derived from `basePath` rather than hardcoded by the caller: this package owns the route (the auth
20
+ * controller lives under the configured mount), so a host that hardcodes it
21
+ * is guessing at something that can change under a version bump — with no compile error when it
22
+ * does, because the break only shows up as a 404 at runtime.
23
+ */
24
+ export declare function telescopeConsoleSessionUrl(basePath?: string): string;
25
+ /** Where the console SPA itself is served. */
26
+ export declare function telescopeConsoleUrl(basePath?: string): string;
27
+ export interface OpenConsoleOptions {
28
+ /** Where the console is mounted. MUST match `TelescopeUiModule.forRoot({ path }) / TelescopeModule.forRoot({ path })`. */
29
+ basePath?: string;
30
+ /**
31
+ * Headers for the mint request — in practice your app's `Authorization` header. A function (sync
32
+ * or async) is accepted so a token can be read at call time rather than captured at wiring time,
33
+ * which is what a refreshing token needs.
34
+ */
35
+ headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
36
+ /** Injected for tests and non-browser callers. Defaults to the global `fetch`. */
37
+ fetch?: typeof globalThis.fetch;
38
+ signal?: AbortSignal;
39
+ /**
40
+ * Performs the navigation after a successful mint. Defaults to `location.assign`. Override to
41
+ * route through your own router, or to open in a new tab.
42
+ */
43
+ navigate?: (url: string) => void;
44
+ }
45
+ /** Thrown when the session could not be minted. Never thrown after a successful mint. */
46
+ export declare class ConsoleSessionError extends Error {
47
+ readonly url: string;
48
+ /** The HTTP status, or `undefined` when the request never produced one (network error). */
49
+ readonly status?: number | undefined;
50
+ constructor(message: string, url: string,
51
+ /** The HTTP status, or `undefined` when the request never produced one (network error). */
52
+ status?: number | undefined);
53
+ }
54
+ /**
55
+ * Mint the console session cookie. Resolves on success; throws {@link ConsoleSessionError} on
56
+ * refusal. Use this directly when you want to mint without navigating (a pre-flight check, or a
57
+ * link the user opens later).
58
+ */
59
+ export declare function mintTelescopeConsoleSession(options?: OpenConsoleOptions): Promise<void>;
60
+ /**
61
+ * Mint the session, then navigate to the console. Throws without navigating when the mint is
62
+ * refused, so a denied user gets a real error instead of landing on the console's
63
+ * "no session" page — which reads as a bug rather than a permission decision.
64
+ */
65
+ export declare function openTelescopeConsole(options?: OpenConsoleOptions): Promise<void>;
66
+ //# sourceMappingURL=console-session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-session.d.ts","sourceRoot":"","sources":["../../src/client/console-session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAWH;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,GAAE,MAA0B,GAAG,MAAM,CAEvF;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,CAAC,QAAQ,GAAE,MAA0B,GAAG,MAAM,CAEhF;AAED,MAAM,WAAW,kBAAkB;IACjC,0HAA0H;IAC1H,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IACnE,kFAAkF;IAClF,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAChC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAClC;AAED,yFAAyF;AACzF,qBAAa,mBAAoB,SAAQ,KAAK;IAG1C,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,2FAA2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;gBAHxB,OAAO,EAAE,MAAM,EACN,GAAG,EAAE,MAAM;IACpB,2FAA2F;IAClF,MAAM,CAAC,EAAE,MAAM,YAAA;CAK3B;AAOD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDjG;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK1F"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Headless client for opening the console from YOUR app.
3
+ *
4
+ * The console SPA is served at a literal path and knows nothing about your app's auth, so a plain
5
+ * browser navigation to it carries no identity. Mode A closes that gap: an XHR from inside your app
6
+ * — which DOES carry your auth — posts to the console's session endpoint, the `session` hook
7
+ * decides, and the library answers with its own signed cookie. The navigation that follows rides it.
8
+ *
9
+ * Telescope's mount is configured as `path` (no leading slash, e.g. `'telescope'`); this accepts either
10
+ * form and normalizes it.
11
+ *
12
+ * No UI here on purpose: you own the button, the page and the copy. This module owns the two things
13
+ * a host would otherwise have to rediscover — where the session endpoint actually lives, and how to
14
+ * call it without the failure mode below.
15
+ */
16
+ /** The mount default; matches `TelescopeUiModule.forRoot()`'s own default. */
17
+ const DEFAULT_BASE_PATH = '/telescope';
18
+ function normalizeBasePath(basePath) {
19
+ const trimmed = basePath.trim().replace(/\/+$/, '');
20
+ if (trimmed === '')
21
+ return '';
22
+ return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;
23
+ }
24
+ /**
25
+ * Where `POST` mints the console session cookie.
26
+ *
27
+ * Derived from `basePath` rather than hardcoded by the caller: this package owns the route (the auth
28
+ * controller lives under the configured mount), so a host that hardcodes it
29
+ * is guessing at something that can change under a version bump — with no compile error when it
30
+ * does, because the break only shows up as a 404 at runtime.
31
+ */
32
+ export function telescopeConsoleSessionUrl(basePath = DEFAULT_BASE_PATH) {
33
+ return `${normalizeBasePath(basePath)}/api/auth/session`;
34
+ }
35
+ /** Where the console SPA itself is served. */
36
+ export function telescopeConsoleUrl(basePath = DEFAULT_BASE_PATH) {
37
+ return normalizeBasePath(basePath) || '/';
38
+ }
39
+ /** Thrown when the session could not be minted. Never thrown after a successful mint. */
40
+ export class ConsoleSessionError extends Error {
41
+ url;
42
+ status;
43
+ constructor(message, url,
44
+ /** The HTTP status, or `undefined` when the request never produced one (network error). */
45
+ status) {
46
+ super(message);
47
+ this.url = url;
48
+ this.status = status;
49
+ this.name = 'ConsoleSessionError';
50
+ }
51
+ }
52
+ async function resolveHeaders(headers) {
53
+ if (headers === undefined)
54
+ return {};
55
+ return typeof headers === 'function' ? await headers() : headers;
56
+ }
57
+ /**
58
+ * Mint the console session cookie. Resolves on success; throws {@link ConsoleSessionError} on
59
+ * refusal. Use this directly when you want to mint without navigating (a pre-flight check, or a
60
+ * link the user opens later).
61
+ */
62
+ export async function mintTelescopeConsoleSession(options = {}) {
63
+ const url = telescopeConsoleSessionUrl(options.basePath);
64
+ const doFetch = options.fetch ?? globalThis.fetch;
65
+ if (typeof doFetch !== 'function') {
66
+ throw new ConsoleSessionError('No `fetch` available; pass one via `options.fetch`.', url);
67
+ }
68
+ let response;
69
+ try {
70
+ response = await doFetch(url, {
71
+ method: 'POST',
72
+ // The whole point: the response's Set-Cookie must stick, and the cookie must ride the
73
+ // navigation that follows.
74
+ credentials: 'include',
75
+ headers: await resolveHeaders(options.headers),
76
+ // Load-bearing, and the reason this helper exists rather than three lines at the call site.
77
+ // `fetch` FOLLOWS redirects by default, so an app whose auth layer rewrites a 401 into a
78
+ // "go to /signin" redirect makes this request resolve 200 against the sign-in HTML —
79
+ // `response.ok` reads true, the caller navigates, and the user lands in a console with no
80
+ // session, which looks exactly like a permissions bug. Handling the redirect explicitly turns
81
+ // that into a clear error instead of a silent false success.
82
+ redirect: 'manual',
83
+ ...(options.signal ? { signal: options.signal } : {}),
84
+ });
85
+ }
86
+ catch (cause) {
87
+ throw new ConsoleSessionError(`Could not reach the console session endpoint at ${url}: ${String(cause)}`, url);
88
+ }
89
+ // `redirect: 'manual'` surfaces differently per runtime: browsers give an opaque response
90
+ // (`type: 'opaqueredirect'`, status 0), Node/undici gives the real 3xx. Both mean the same thing.
91
+ const redirected = response.type === 'opaqueredirect' || (response.status >= 300 && response.status < 400);
92
+ if (redirected) {
93
+ throw new ConsoleSessionError(`The console session endpoint at ${url} answered with a redirect instead of minting a session. Something in front of it — usually an auth middleware or exception filter that rewrites 401s into a sign-in redirect — is intercepting the response. Exempt this path from that rewrite so the real status reaches this caller.`, url, response.status || undefined);
94
+ }
95
+ if (!response.ok) {
96
+ throw new ConsoleSessionError(`The console refused to open (HTTP ${response.status}).`, url, response.status);
97
+ }
98
+ }
99
+ /**
100
+ * Mint the session, then navigate to the console. Throws without navigating when the mint is
101
+ * refused, so a denied user gets a real error instead of landing on the console's
102
+ * "no session" page — which reads as a bug rather than a permission decision.
103
+ */
104
+ export async function openTelescopeConsole(options = {}) {
105
+ await mintTelescopeConsoleSession(options);
106
+ const target = telescopeConsoleUrl(options.basePath);
107
+ const navigate = options.navigate ?? ((url) => globalThis.location?.assign(url));
108
+ navigate(target);
109
+ }
110
+ //# sourceMappingURL=console-session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"console-session.js","sourceRoot":"","sources":["../../src/client/console-session.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ export { ConsoleSessionError, telescopeConsoleSessionUrl, telescopeConsoleUrl, mintTelescopeConsoleSession, openTelescopeConsole, type OpenConsoleOptions, } from './console-session.js';
1
2
  export { createTelescopeClient } from './telescope-client.js';
2
3
  export type { BulkActionName, JobActionName, TelescopeClient, TelescopeClientOptions, } from './telescope-client.js';
3
4
  export type * from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,mBAAmB,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,EACpB,KAAK,kBAAkB,GACxB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,sBAAsB,GACvB,MAAM,uBAAuB,CAAC;AAC/B,mBAAmB,YAAY,CAAC"}
@@ -1,2 +1,5 @@
1
+ // Headless console-launcher primitives (path derivation + mint-then-navigate). Re-exported here
2
+ // because `./client` resolves to this file — see the package's `exports` map.
3
+ export { ConsoleSessionError, telescopeConsoleSessionUrl, telescopeConsoleUrl, mintTelescopeConsoleSession, openTelescopeConsole, } from './console-session.js';
1
4
  export { createTelescopeClient } from './telescope-client.js';
2
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,8EAA8E;AAC9E,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,2BAA2B,EAC3B,oBAAoB,GAErB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -3,8 +3,27 @@ import { type TelescopeUiModuleOptions } from './telescope-ui.options.js';
3
3
  export declare class TelescopeUiController {
4
4
  private readonly assetsDir;
5
5
  private readonly path;
6
+ /**
7
+ * Resolved from the SAME `dashboardAuth` the host passes to core (see `TelescopeUiModuleOptions`).
8
+ * Only `secret`, `modes` and `unauthenticatedPage` are read here — minting and renewal stay
9
+ * core's job. `null` whenever the host did not pass it, which is the default.
10
+ */
11
+ private readonly auth;
12
+ private readonly logger;
13
+ private warned;
6
14
  constructor(options: TelescopeUiModuleOptions);
7
- index(): string;
15
+ index(req: unknown, res: unknown): Promise<void>;
16
+ /**
17
+ * Hand the response to the host's `unauthenticatedPage` when this navigation has no valid session.
18
+ * Returns `true` when the host wrote a page (the caller must not also serve the SPA).
19
+ *
20
+ * Mode-A-only, deliberately: with `login` configured the SPA's own login form is the way in, so
21
+ * replacing the shell would lock a Mode B host out of its own dashboard.
22
+ */
23
+ private serveUnauthenticatedPage;
24
+ private hasValidSession;
25
+ /** Once per process, not once per request: a broken hook fires on every unauthenticated visit. */
26
+ private warnOnce;
8
27
  asset(file: string): StreamableFile;
9
28
  }
10
29
  //# sourceMappingURL=telescope-ui.controller.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"telescope-ui.controller.d.ts","sourceRoot":"","sources":["../../src/server/telescope-ui.controller.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,cAAc,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAwB,KAAK,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAiChG,qBACa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;gBAEY,OAAO,EAAE,wBAAwB;IAY3E,KAAK,IAAI,MAAM;IAYf,KAAK,CAAgB,IAAI,EAAE,MAAM,GAAG,cAAc;CAcnD"}
1
+ {"version":3,"file":"telescope-ui.controller.d.ts","sourceRoot":"","sources":["../../src/server/telescope-ui.controller.ts"],"names":[],"mappings":"AAcA,OAAO,EAUL,cAAc,EACf,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAwB,KAAK,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAiChG,qBACa,qBAAqB;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA+B;IACpD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA0C;IACjE,OAAO,CAAC,MAAM,CAAS;gBAEmB,OAAO,EAAE,wBAAwB;IAiBrE,KAAK,CAAQ,GAAG,EAAE,OAAO,EAAS,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpE;;;;;;OAMG;YACW,wBAAwB;IA6BtC,OAAO,CAAC,eAAe;IAMvB,kGAAkG;IAClG,OAAO,CAAC,QAAQ;IAUhB,KAAK,CAAgB,IAAI,EAAE,MAAM,GAAG,cAAc;CAcnD"}
@@ -10,11 +10,12 @@ var __metadata = (this && this.__metadata) || function (k, v) {
10
10
  var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
+ var TelescopeUiController_1;
13
14
  import { existsSync, readFileSync } from 'node:fs';
14
15
  import { basename, extname, join, resolve, sep } from 'node:path';
15
16
  import { fileURLToPath } from 'node:url';
16
- import { normalizeTelescopePath } from '@dudousxd/nestjs-telescope';
17
- import { Controller, Get, Header, Inject, NotFoundException, Param, StreamableFile, } from '@nestjs/common';
17
+ import { SESSION_COOKIE_NAME, normalizeTelescopePath, parseCookieHeader, readCookieHeader, resolveDashboardAuth, responseAlreadyWritten, sendHtml, verifySessionCookie, } from '@dudousxd/nestjs-telescope';
18
+ import { Controller, Get, Header, Inject, Logger, NotFoundException, Param, Req, Res, StreamableFile, } from '@nestjs/common';
18
19
  import { TELESCOPE_UI_OPTIONS } from './telescope-ui.options.js';
19
20
  /** dist/server/telescope-ui.controller.js -> ../spa */
20
21
  function defaultAssetsDir() {
@@ -44,23 +45,89 @@ function rewriteAssetBase(html, path) {
44
45
  }
45
46
  return `${injectedBase}${rebased}`;
46
47
  }
47
- let TelescopeUiController = class TelescopeUiController {
48
+ let TelescopeUiController = TelescopeUiController_1 = class TelescopeUiController {
48
49
  assetsDir;
49
50
  path;
51
+ /**
52
+ * Resolved from the SAME `dashboardAuth` the host passes to core (see `TelescopeUiModuleOptions`).
53
+ * Only `secret`, `modes` and `unauthenticatedPage` are read here — minting and renewal stay
54
+ * core's job. `null` whenever the host did not pass it, which is the default.
55
+ */
56
+ auth;
57
+ logger = new Logger(TelescopeUiController_1.name);
58
+ warned = false;
50
59
  constructor(options) {
51
60
  this.assetsDir = options.assetsDir ?? defaultAssetsDir();
52
61
  this.path = normalizeTelescopePath(options.path);
62
+ this.auth = resolveDashboardAuth(options.dashboardAuth);
53
63
  }
54
64
  // index.html references hash-named asset bundles. It MUST NOT be cached, or a
55
65
  // browser keeps loading a stale bundle across deploys (the classic "one widget
56
66
  // stuck loading / old labels after an upgrade"). The hashed assets below are
57
67
  // immutable and cached forever instead.
58
- index() {
68
+ //
69
+ // Non-passthrough `@Res()` so a host's `dashboardAuth.unauthenticatedPage` can OWN the response
70
+ // and serve its own page in place of the SPA. Without a hook this behaves exactly as before: the
71
+ // shell is served to everyone, with no session check on this route at all, and the SPA's own auth
72
+ // screen renders once its API calls come back 401. `sendHtml` sets the two headers that used to be
73
+ // `@Header` decorators.
74
+ async index(req, res) {
75
+ if (await this.serveUnauthenticatedPage(req, res))
76
+ return;
59
77
  const indexPath = join(this.assetsDir, 'index.html');
60
78
  if (!existsSync(indexPath)) {
61
79
  throw new NotFoundException('Telescope UI is not built. Run the package build.');
62
80
  }
63
- return rewriteAssetBase(readFileSync(indexPath, 'utf8'), this.path);
81
+ sendHtml(res, 200, rewriteAssetBase(readFileSync(indexPath, 'utf8'), this.path));
82
+ }
83
+ /**
84
+ * Hand the response to the host's `unauthenticatedPage` when this navigation has no valid session.
85
+ * Returns `true` when the host wrote a page (the caller must not also serve the SPA).
86
+ *
87
+ * Mode-A-only, deliberately: with `login` configured the SPA's own login form is the way in, so
88
+ * replacing the shell would lock a Mode B host out of its own dashboard.
89
+ */
90
+ async serveUnauthenticatedPage(req, res) {
91
+ const auth = this.auth;
92
+ const hook = auth?.unauthenticatedPage;
93
+ if (!auth || !hook)
94
+ return false;
95
+ if (auth.modes.includes('login'))
96
+ return false;
97
+ if (this.hasValidSession(auth, req))
98
+ return false;
99
+ try {
100
+ await hook({ request: req, response: res, basePath: `/${this.path}` });
101
+ if (responseAlreadyWritten(res))
102
+ return true;
103
+ // Returned without writing. Serving the SPA is the only outcome that neither hangs the
104
+ // request nor invents a page the host did not ask for — but it is almost certainly a bug.
105
+ this.warnOnce('dashboardAuth.unauthenticatedPage returned without writing a response; serving the SPA ' +
106
+ 'instead. The hook must write AND end the response it is given.');
107
+ }
108
+ catch (error) {
109
+ this.warnOnce(`dashboardAuth.unauthenticatedPage threw; serving the SPA instead. ${String(error)}`);
110
+ // It may have written headers before throwing; writing again would raise
111
+ // ERR_HTTP_HEADERS_SENT on top of the original failure.
112
+ if (responseAlreadyWritten(res))
113
+ return true;
114
+ }
115
+ // Falling back to the SPA is safe: every data route stays behind TelescopeGuard, so the visitor
116
+ // gets the built-in auth screen, not the dashboard's contents.
117
+ return false;
118
+ }
119
+ hasValidSession(auth, req) {
120
+ const cookieValue = parseCookieHeader(readCookieHeader(req))[SESSION_COOKIE_NAME];
121
+ if (cookieValue === undefined)
122
+ return false;
123
+ return verifySessionCookie(cookieValue, { secret: auth.secret }) !== null;
124
+ }
125
+ /** Once per process, not once per request: a broken hook fires on every unauthenticated visit. */
126
+ warnOnce(message) {
127
+ if (this.warned)
128
+ return;
129
+ this.warned = true;
130
+ this.logger.warn(message);
64
131
  }
65
132
  // Asset filenames are content-hashed by the build, so they're safe to cache
66
133
  // forever — a new bundle gets a new filename referenced by the (uncached) index.
@@ -82,11 +149,11 @@ let TelescopeUiController = class TelescopeUiController {
82
149
  };
83
150
  __decorate([
84
151
  Get(),
85
- Header('Content-Type', 'text/html; charset=utf-8'),
86
- Header('Cache-Control', 'no-store, must-revalidate'),
152
+ __param(0, Req()),
153
+ __param(1, Res()),
87
154
  __metadata("design:type", Function),
88
- __metadata("design:paramtypes", []),
89
- __metadata("design:returntype", String)
155
+ __metadata("design:paramtypes", [Object, Object]),
156
+ __metadata("design:returntype", Promise)
90
157
  ], TelescopeUiController.prototype, "index", null);
91
158
  __decorate([
92
159
  Get('assets/:file'),
@@ -96,7 +163,7 @@ __decorate([
96
163
  __metadata("design:paramtypes", [String]),
97
164
  __metadata("design:returntype", StreamableFile)
98
165
  ], TelescopeUiController.prototype, "asset", null);
99
- TelescopeUiController = __decorate([
166
+ TelescopeUiController = TelescopeUiController_1 = __decorate([
100
167
  Controller(),
101
168
  __param(0, Inject(TELESCOPE_UI_OPTIONS)),
102
169
  __metadata("design:paramtypes", [Object])
@@ -1 +1 @@
1
- {"version":3,"file":"telescope-ui.controller.js","sourceRoot":"","sources":["../../src/server/telescope-ui.controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"telescope-ui.controller.js","sourceRoot":"","sources":["../../src/server/telescope-ui.controller.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { DashboardAuthOptions } from '@dudousxd/nestjs-telescope';
1
2
  import type { CanActivate, DynamicModule, Type } from '@nestjs/common';
2
3
  export declare const TELESCOPE_UI_OPTIONS: unique symbol;
3
4
  export interface TelescopeUiModuleOptions {
@@ -31,6 +32,20 @@ export interface TelescopeUiModuleOptions {
31
32
  * listed guard(s) become the controller's entire gate.
32
33
  */
33
34
  guards?: Array<Type<CanActivate> | CanActivate>;
35
+ /**
36
+ * The SAME `dashboardAuth` passed to `TelescopeModule.forRoot` (core). Only needed to use
37
+ * `dashboardAuth.unauthenticatedPage`: this module serves the page + SPA shell, so it is the only
38
+ * one that can replace that page — core gates the API, not the HTML.
39
+ *
40
+ * Like {@link guards}, the two options are INDEPENDENT (different package, different module).
41
+ * Setting `unauthenticatedPage` only on core gates the API but leaves the page untouched, and the
42
+ * visitor keeps getting the SPA's built-in auth screen.
43
+ *
44
+ * Everything except `secret`, the configured modes, and `unauthenticatedPage` is ignored here —
45
+ * minting and renewal stay entirely core's job. The full options type is accepted so a host can
46
+ * pass one object to both modules rather than hand-picking fields.
47
+ */
48
+ dashboardAuth?: DashboardAuthOptions;
34
49
  /**
35
50
  * Extra `imports` merged into `TelescopeUiModule`'s own dynamic module — the
36
51
  * DI resolution path for a class passed to {@link guards}.
@@ -1 +1 @@
1
- {"version":3,"file":"telescope-ui.options.d.ts","sourceRoot":"","sources":["../../src/server/telescope-ui.options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEvE,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACvC,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACpC"}
1
+ {"version":3,"file":"telescope-ui.options.d.ts","sourceRoot":"","sources":["../../src/server/telescope-ui.options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEvE,eAAO,MAAM,oBAAoB,eAAiC,CAAC;AAEnE,MAAM,WAAW,wBAAwB;IACvC,2FAA2F;IAC3F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,WAAW,CAAC,CAAC;IAChD;;;;;;;;;;;;OAYG;IACH,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC;;;OAGG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"telescope-ui.options.js","sourceRoot":"","sources":["../../src/server/telescope-ui.options.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC"}
1
+ {"version":3,"file":"telescope-ui.options.js","sourceRoot":"","sources":["../../src/server/telescope-ui.options.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,oBAAoB,GAAG,MAAM,CAAC,sBAAsB,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.left-0{left:0}.top-0{top:0}.top-full{top:100%}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.col-span-2{grid-column:span 2 / span 2}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-4{margin-left:1rem}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1\.5{height:.375rem}.h-2{height:.5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-6{height:1.5rem}.h-full{height:100%}.max-h-64{max-height:16rem}.max-h-72{max-height:18rem}.min-h-screen{min-height:100vh}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-20{width:5rem}.w-24{width:6rem}.w-40{width:10rem}.w-48{width:12rem}.w-56{width:14rem}.w-64{width:16rem}.w-full{width:100%}.min-w-0{min-width:0px}.min-w-\[640px\]{min-width:640px}.min-w-\[760px\]{min-width:760px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0,.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-px{gap:1px}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-y-1{row-gap:.25rem}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.125rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.125rem * var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-amber-500\/40{border-color:#f59e0b66}.border-amber-500\/50{border-color:#f59e0b80}.border-emerald-500\/40{border-color:#10b98166}.border-red-500\/40{border-color:#ef444466}.border-red-500\/50{border-color:#ef444480}.border-red-900\/50{border-color:#7f1d1d80}.border-violet-500\/40{border-color:#8b5cf666}.border-zinc-700{--tw-border-opacity: 1;border-color:rgb(63 63 70 / var(--tw-border-opacity, 1))}.border-zinc-800{--tw-border-opacity: 1;border-color:rgb(39 39 42 / var(--tw-border-opacity, 1))}.border-zinc-800\/60{border-color:#27272a99}.border-zinc-900{--tw-border-opacity: 1;border-color:rgb(24 24 27 / var(--tw-border-opacity, 1))}.border-zinc-950\/60{border-color:#09090b99}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-amber-500{--tw-bg-opacity: 1;background-color:rgb(245 158 11 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-amber-500\/60{background-color:#f59e0b99}.bg-amber-950\/60{background-color:#451a0399}.bg-black\/50{background-color:#00000080}.bg-black\/60{background-color:#0009}.bg-cyan-400{--tw-bg-opacity: 1;background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1))}.bg-emerald-400{--tw-bg-opacity: 1;background-color:rgb(52 211 153 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/10{background-color:#10b9811a}.bg-emerald-500\/5{background-color:#10b9810d}.bg-emerald-500\/60{background-color:#10b98199}.bg-emerald-600{--tw-bg-opacity: 1;background-color:rgb(5 150 105 / var(--tw-bg-opacity, 1))}.bg-emerald-950\/60{background-color:#022c2299}.bg-fuchsia-400{--tw-bg-opacity: 1;background-color:rgb(232 121 249 / var(--tw-bg-opacity, 1))}.bg-indigo-400{--tw-bg-opacity: 1;background-color:rgb(129 140 248 / var(--tw-bg-opacity, 1))}.bg-indigo-500{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity, 1))}.bg-lime-400{--tw-bg-opacity: 1;background-color:rgb(163 230 53 / var(--tw-bg-opacity, 1))}.bg-orange-400{--tw-bg-opacity: 1;background-color:rgb(251 146 60 / var(--tw-bg-opacity, 1))}.bg-orange-500{--tw-bg-opacity: 1;background-color:rgb(249 115 22 / var(--tw-bg-opacity, 1))}.bg-pink-400{--tw-bg-opacity: 1;background-color:rgb(244 114 182 / var(--tw-bg-opacity, 1))}.bg-pink-500{--tw-bg-opacity: 1;background-color:rgb(236 72 153 / var(--tw-bg-opacity, 1))}.bg-purple-400{--tw-bg-opacity: 1;background-color:rgb(192 132 252 / var(--tw-bg-opacity, 1))}.bg-red-400{--tw-bg-opacity: 1;background-color:rgb(248 113 113 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-red-500\/10{background-color:#ef44441a}.bg-red-500\/5{background-color:#ef44440d}.bg-red-950\/30{background-color:#450a0a4d}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-rose-500{--tw-bg-opacity: 1;background-color:rgb(244 63 94 / var(--tw-bg-opacity, 1))}.bg-sky-400{--tw-bg-opacity: 1;background-color:rgb(56 189 248 / var(--tw-bg-opacity, 1))}.bg-sky-500{--tw-bg-opacity: 1;background-color:rgb(14 165 233 / var(--tw-bg-opacity, 1))}.bg-sky-500\/10{background-color:#0ea5e91a}.bg-teal-400{--tw-bg-opacity: 1;background-color:rgb(45 212 191 / var(--tw-bg-opacity, 1))}.bg-teal-500{--tw-bg-opacity: 1;background-color:rgb(20 184 166 / var(--tw-bg-opacity, 1))}.bg-transparent{background-color:transparent}.bg-violet-400{--tw-bg-opacity: 1;background-color:rgb(167 139 250 / var(--tw-bg-opacity, 1))}.bg-violet-500{--tw-bg-opacity: 1;background-color:rgb(139 92 246 / var(--tw-bg-opacity, 1))}.bg-violet-500\/15{background-color:#8b5cf626}.bg-zinc-400{--tw-bg-opacity: 1;background-color:rgb(161 161 170 / var(--tw-bg-opacity, 1))}.bg-zinc-500{--tw-bg-opacity: 1;background-color:rgb(113 113 122 / var(--tw-bg-opacity, 1))}.bg-zinc-500\/10{background-color:#71717a1a}.bg-zinc-600{--tw-bg-opacity: 1;background-color:rgb(82 82 91 / var(--tw-bg-opacity, 1))}.bg-zinc-800{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.bg-zinc-800\/60{background-color:#27272a99}.bg-zinc-800\/80{background-color:#27272acc}.bg-zinc-900{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1))}.bg-zinc-900\/20{background-color:#18181b33}.bg-zinc-900\/40{background-color:#18181b66}.bg-zinc-900\/60{background-color:#18181b99}.bg-zinc-950{--tw-bg-opacity: 1;background-color:rgb(9 9 11 / var(--tw-bg-opacity, 1))}.fill-amber-400{fill:#fbbf24}.fill-emerald-400{fill:#34d399}.fill-emerald-500\/10{fill:#10b9811a}.fill-none{fill:none}.fill-pink-400{fill:#f472b6}.fill-red-400{fill:#f87171}.fill-sky-400{fill:#38bdf8}.fill-violet-400{fill:#a78bfa}.fill-zinc-500{fill:#71717a}.stroke-emerald-400{stroke:#34d399}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pr-2{padding-right:.5rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-\[12vh\]{padding-top:12vh}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.normal-case{text-transform:none}.tabular-nums{--tw-numeric-spacing: tabular-nums;font-variant-numeric:var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction)}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.text-amber-200{--tw-text-opacity: 1;color:rgb(253 230 138 / var(--tw-text-opacity, 1))}.text-amber-300{--tw-text-opacity: 1;color:rgb(252 211 77 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-black\/80{color:#000c}.text-cyan-400{--tw-text-opacity: 1;color:rgb(34 211 238 / var(--tw-text-opacity, 1))}.text-emerald-300{--tw-text-opacity: 1;color:rgb(110 231 183 / var(--tw-text-opacity, 1))}.text-emerald-400{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.text-emerald-400\/80{color:#34d399cc}.text-emerald-500{--tw-text-opacity: 1;color:rgb(16 185 129 / var(--tw-text-opacity, 1))}.text-fuchsia-300{--tw-text-opacity: 1;color:rgb(240 171 252 / var(--tw-text-opacity, 1))}.text-fuchsia-400{--tw-text-opacity: 1;color:rgb(232 121 249 / var(--tw-text-opacity, 1))}.text-indigo-300{--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity, 1))}.text-indigo-400{--tw-text-opacity: 1;color:rgb(129 140 248 / var(--tw-text-opacity, 1))}.text-lime-400{--tw-text-opacity: 1;color:rgb(163 230 53 / var(--tw-text-opacity, 1))}.text-orange-400{--tw-text-opacity: 1;color:rgb(251 146 60 / var(--tw-text-opacity, 1))}.text-red-200{--tw-text-opacity: 1;color:rgb(254 202 202 / var(--tw-text-opacity, 1))}.text-red-300{--tw-text-opacity: 1;color:rgb(252 165 165 / var(--tw-text-opacity, 1))}.text-red-300\/80{color:#fca5a5cc}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-sky-300{--tw-text-opacity: 1;color:rgb(125 211 252 / var(--tw-text-opacity, 1))}.text-sky-400{--tw-text-opacity: 1;color:rgb(56 189 248 / var(--tw-text-opacity, 1))}.text-teal-300{--tw-text-opacity: 1;color:rgb(94 234 212 / var(--tw-text-opacity, 1))}.text-violet-300{--tw-text-opacity: 1;color:rgb(196 181 253 / var(--tw-text-opacity, 1))}.text-violet-400{--tw-text-opacity: 1;color:rgb(167 139 250 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-zinc-100{--tw-text-opacity: 1;color:rgb(244 244 245 / var(--tw-text-opacity, 1))}.text-zinc-200{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.text-zinc-300{--tw-text-opacity: 1;color:rgb(212 212 216 / var(--tw-text-opacity, 1))}.text-zinc-400{--tw-text-opacity: 1;color:rgb(161 161 170 / var(--tw-text-opacity, 1))}.text-zinc-500{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.text-zinc-600{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-red-500\/20{--tw-ring-color: rgb(239 68 68 / .2)}.ring-zinc-800{--tw-ring-opacity: 1;--tw-ring-color: rgb(39 39 42 / var(--tw-ring-opacity, 1))}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.light{color-scheme:light}.light .bg-zinc-950{background-color:#fff}.light .bg-zinc-900{background-color:#f4f4f5}.light .bg-zinc-800{background-color:#e4e4e7}.light .text-zinc-100,.light .text-zinc-200,.light .text-zinc-300{color:#18181b}.light .text-zinc-400,.light .text-zinc-500,.light .text-zinc-600{color:#52525b}.light .border-zinc-800,.light .border-zinc-700{border-color:#d4d4d8}.placeholder\:text-zinc-500::-moz-placeholder{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-500::placeholder{--tw-text-opacity: 1;color:rgb(113 113 122 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-600::-moz-placeholder{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.placeholder\:text-zinc-600::placeholder{--tw-text-opacity: 1;color:rgb(82 82 91 / var(--tw-text-opacity, 1))}.first\:border-0:first-child{border-width:0px}.first\:pt-0:first-child{padding-top:0}.last\:border-0:last-child{border-width:0px}.hover\:border-emerald-500:hover{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.hover\:border-zinc-600:hover{--tw-border-opacity: 1;border-color:rgb(82 82 91 / var(--tw-border-opacity, 1))}.hover\:border-zinc-700:hover{--tw-border-opacity: 1;border-color:rgb(63 63 70 / var(--tw-border-opacity, 1))}.hover\:bg-amber-500\/10:hover{background-color:#f59e0b1a}.hover\:bg-amber-500\/20:hover{background-color:#f59e0b33}.hover\:bg-emerald-500:hover{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.hover\:bg-emerald-500\/10:hover{background-color:#10b9811a}.hover\:bg-emerald-500\/20:hover{background-color:#10b98133}.hover\:bg-red-500\/10:hover{background-color:#ef44441a}.hover\:bg-zinc-800:hover{--tw-bg-opacity: 1;background-color:rgb(39 39 42 / var(--tw-bg-opacity, 1))}.hover\:bg-zinc-800\/60:hover{background-color:#27272a99}.hover\:bg-zinc-900:hover{--tw-bg-opacity: 1;background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1))}.hover\:bg-zinc-900\/40:hover{background-color:#18181b66}.hover\:bg-zinc-900\/70:hover{background-color:#18181bb3}.hover\:text-emerald-400:hover{--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.hover\:text-zinc-100:hover{--tw-text-opacity: 1;color:rgb(244 244 245 / var(--tw-text-opacity, 1))}.hover\:text-zinc-200:hover{--tw-text-opacity: 1;color:rgb(228 228 231 / var(--tw-text-opacity, 1))}.hover\:text-zinc-300:hover{--tw-text-opacity: 1;color:rgb(212 212 216 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:brightness-110:hover{--tw-brightness: brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-emerald-500:focus{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.focus\:border-emerald-500\/60:focus{border-color:#10b98199}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width: 640px){.sm\:col-span-4{grid-column:span 4 / span 4}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width: 768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}}@media (min-width: 1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.lg\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.lg\:grid-cols-\[280px_minmax\(0\,1fr\)\]{grid-template-columns:280px minmax(0,1fr)}}