@vizamodo/modo-dispatcher 1.1.77

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 (109) hide show
  1. package/README.md +43 -0
  2. package/dist/artifacts/builders.d.ts +32 -0
  3. package/dist/artifacts/builders.js +72 -0
  4. package/dist/artifacts/downloader.d.ts +28 -0
  5. package/dist/artifacts/downloader.js +102 -0
  6. package/dist/artifacts/normalizer.d.ts +22 -0
  7. package/dist/artifacts/normalizer.js +65 -0
  8. package/dist/artifacts/types.d.ts +94 -0
  9. package/dist/artifacts/types.js +1 -0
  10. package/dist/auth/identity/extract-identity.d.ts +8 -0
  11. package/dist/auth/identity/extract-identity.js +15 -0
  12. package/dist/auth/identity/normalize-teams.d.ts +1 -0
  13. package/dist/auth/identity/normalize-teams.js +5 -0
  14. package/dist/auth/identity/validate-claims.d.ts +2 -0
  15. package/dist/auth/identity/validate-claims.js +5 -0
  16. package/dist/auth/jwt/parse.d.ts +2 -0
  17. package/dist/auth/jwt/parse.js +16 -0
  18. package/dist/auth/jwt/verify.d.ts +1 -0
  19. package/dist/auth/jwt/verify.js +9 -0
  20. package/dist/auth/oauth/auth-error.d.ts +12 -0
  21. package/dist/auth/oauth/auth-error.js +47 -0
  22. package/dist/auth/oauth/auth-session-store.d.ts +30 -0
  23. package/dist/auth/oauth/auth-session-store.js +46 -0
  24. package/dist/auth/oauth/callback-server.d.ts +21 -0
  25. package/dist/auth/oauth/callback-server.js +319 -0
  26. package/dist/auth/oauth/github.d.ts +14 -0
  27. package/dist/auth/oauth/github.js +100 -0
  28. package/dist/auth/oauth/sse.d.ts +11 -0
  29. package/dist/auth/oauth/sse.js +67 -0
  30. package/dist/auth/oauth/templates/failed.html +629 -0
  31. package/dist/auth/oauth/templates/pending.html +620 -0
  32. package/dist/auth/oauth/templates/success.html +577 -0
  33. package/dist/auth/session/access-token.d.ts +15 -0
  34. package/dist/auth/session/access-token.js +64 -0
  35. package/dist/auth/session/login.d.ts +18 -0
  36. package/dist/auth/session/login.js +144 -0
  37. package/dist/auth/session/logout.d.ts +3 -0
  38. package/dist/auth/session/logout.js +21 -0
  39. package/dist/auth/session/refresh-token.d.ts +8 -0
  40. package/dist/auth/session/refresh-token.js +16 -0
  41. package/dist/auth/session/refresh.d.ts +2 -0
  42. package/dist/auth/session/refresh.js +61 -0
  43. package/dist/auth/session/rotate.d.ts +4 -0
  44. package/dist/auth/session/rotate.js +4 -0
  45. package/dist/auth/session/session-manager.d.ts +16 -0
  46. package/dist/auth/session/session-manager.js +54 -0
  47. package/dist/auth/session/token-state.d.ts +20 -0
  48. package/dist/auth/session/token-state.js +55 -0
  49. package/dist/auth/session/types.d.ts +35 -0
  50. package/dist/auth/session/types.js +1 -0
  51. package/dist/auth/storage/keychain.d.ts +16 -0
  52. package/dist/auth/storage/keychain.js +107 -0
  53. package/dist/auth/storage/memory.d.ts +10 -0
  54. package/dist/auth/storage/memory.js +15 -0
  55. package/dist/auth/storage/types.d.ts +5 -0
  56. package/dist/auth/storage/types.js +1 -0
  57. package/dist/config/defaults.d.ts +94 -0
  58. package/dist/config/defaults.js +116 -0
  59. package/dist/core/auth-bootstrap.d.ts +15 -0
  60. package/dist/core/auth-bootstrap.js +33 -0
  61. package/dist/core/bootstrap.d.ts +73 -0
  62. package/dist/core/bootstrap.js +248 -0
  63. package/dist/core/dispatcher.d.ts +2 -0
  64. package/dist/core/dispatcher.js +28 -0
  65. package/dist/core/ensure-bootstrap.d.ts +3 -0
  66. package/dist/core/ensure-bootstrap.js +25 -0
  67. package/dist/core/errors.d.ts +69 -0
  68. package/dist/core/errors.js +121 -0
  69. package/dist/core/runtime-orchestrator.d.ts +17 -0
  70. package/dist/core/runtime-orchestrator.js +47 -0
  71. package/dist/core/runtime.d.ts +16 -0
  72. package/dist/core/runtime.js +52 -0
  73. package/dist/core/validation.d.ts +2 -0
  74. package/dist/core/validation.js +21 -0
  75. package/dist/crypto/encrypt.d.ts +12 -0
  76. package/dist/crypto/encrypt.js +80 -0
  77. package/dist/flows/email-otp-flow.d.ts +7 -0
  78. package/dist/flows/email-otp-flow.js +249 -0
  79. package/dist/gateway/auth-header.d.ts +2 -0
  80. package/dist/gateway/auth-header.js +21 -0
  81. package/dist/gateway/client.d.ts +7 -0
  82. package/dist/gateway/client.js +24 -0
  83. package/dist/gateway/dispatch-with-bootstrap-retry.d.ts +25 -0
  84. package/dist/gateway/dispatch-with-bootstrap-retry.js +157 -0
  85. package/dist/gateway/dispatch.d.ts +23 -0
  86. package/dist/gateway/dispatch.js +110 -0
  87. package/dist/gateway/session-waiter.d.ts +11 -0
  88. package/dist/gateway/session-waiter.js +126 -0
  89. package/dist/gateway/session.d.ts +21 -0
  90. package/dist/gateway/session.js +74 -0
  91. package/dist/gateway/types.d.ts +113 -0
  92. package/dist/gateway/types.js +19 -0
  93. package/dist/index.d.ts +22 -0
  94. package/dist/index.js +21 -0
  95. package/dist/runtime/runtime-context.d.ts +6 -0
  96. package/dist/runtime/runtime-context.js +1 -0
  97. package/dist/types/dispatcher.d.ts +275 -0
  98. package/dist/types/dispatcher.js +1 -0
  99. package/dist/types/payload.d.ts +5 -0
  100. package/dist/types/payload.js +2 -0
  101. package/dist/types/runtime-env.d.ts +2 -0
  102. package/dist/types/runtime-env.js +5 -0
  103. package/dist/ui/banner.d.ts +4 -0
  104. package/dist/ui/banner.js +20 -0
  105. package/dist/utils/request-dedup.d.ts +75 -0
  106. package/dist/utils/request-dedup.js +102 -0
  107. package/dist/utils/type-guards.d.ts +173 -0
  108. package/dist/utils/type-guards.js +232 -0
  109. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @vizamodo/modo-dispatcher
2
+
3
+ Client-side dispatch engine for the Viza CLI platform. Handles authentication, payload encryption, gateway communication, session lifecycle, and artifact normalization for both runtime (hub-worker) and infra (GitHub Actions) executions.
4
+
5
+ ---
6
+
7
+ ## 🚨 AI & Agentic Workflow Directives
8
+
9
+ **Do not rely on this root README for system truths.**
10
+
11
+ Historical context, deep architecture details, coding rules, contracts, and playbooks have been moved to the `.ai/` Layered Context System. All future AI agents and automated tooling **MUST bootstrap via `.clinerules`** and read `.ai/index/README.md` immediately before performing any work on this repository.
12
+
13
+ > **Quick route for agents:** `.ai/index/README.md` → follow layers 00 → 10 → 20 → 30 in order.
14
+
15
+ ---
16
+
17
+ ## Quick Start (For Humans)
18
+
19
+ ```bash
20
+ npm install
21
+ npm run build
22
+ npm test
23
+ ```
24
+
25
+ | Task | Command |
26
+ |------|---------|
27
+ | Build | `npm run build` |
28
+ | Test | `npm test` |
29
+ | Publish (dev) | `npm run release:dev` |
30
+ | Publish (prod) | `npm run release:prod` |
31
+
32
+ For advanced build/publish workflows, see [`.ai/40-playbooks/build-and-publish.md`](.ai/40-playbooks/build-and-publish.md).
33
+
34
+ ---
35
+
36
+ ## Package Info
37
+
38
+ - **npm**: `@vizamodo/modo-dispatcher` (public)
39
+ - **Version**: 1.1.72
40
+ - **Type**: ESM TypeScript library
41
+ - **Runtime**: Node.js >= 20
42
+ - **Dependencies**: `chalk`, `ws`
43
+ - **License**: MIT
@@ -0,0 +1,32 @@
1
+ import type { DispatchResult } from "../types/dispatcher.js";
2
+ import type { Artifacts, RuntimeArtifacts } from "./types.js";
3
+ /**
4
+ * Build a runtime (hub-worker) DispatchResult from the final session done data.
5
+ */
6
+ export declare function buildRuntimeResult(done: {
7
+ sessionId: string;
8
+ status: string;
9
+ conclusion: string;
10
+ artifacts?: object;
11
+ }): DispatchResult;
12
+ /**
13
+ * Build a GitHub (infra) DispatchResult.
14
+ * Downloads log and result artifacts from the artifact store.
15
+ */
16
+ export declare function buildGithubResult(done: {
17
+ sessionId: string;
18
+ status: string;
19
+ conclusion: string;
20
+ artifacts?: Artifacts;
21
+ }): Promise<DispatchResult>;
22
+ /**
23
+ * Build the final DispatchResult from a done session.
24
+ * Dispatches to the runtime or GitHub builder based on runType.
25
+ */
26
+ export declare function finalizeResult(done: {
27
+ sessionId: string;
28
+ status: string;
29
+ conclusion: string;
30
+ runType?: string;
31
+ artifacts?: Artifacts | RuntimeArtifacts | Record<string, unknown> | undefined;
32
+ }): Promise<DispatchResult>;
@@ -0,0 +1,72 @@
1
+ import { GatewayError } from "../core/errors.js";
2
+ import { safeDownloadLogArtifact, safeDownloadResultArtifact } from "./downloader.js";
3
+ import { normalizeArtifact } from "./normalizer.js";
4
+ import { extractErrorInfo } from "../utils/type-guards.js";
5
+ /**
6
+ * Build a runtime (hub-worker) DispatchResult from the final session done data.
7
+ */
8
+ export function buildRuntimeResult(done) {
9
+ const arts = done.artifacts;
10
+ if (!arts) {
11
+ throw new GatewayError("Invalid runtime contract: missing artifacts");
12
+ }
13
+ const rawLog = arts?.["log"];
14
+ const log = normalizeArtifact(rawLog);
15
+ const rawError = arts?.["error"];
16
+ const error = extractErrorInfo(rawError);
17
+ const runtimePayload = arts?.["result"] !== undefined ? arts["result"] : arts;
18
+ const normalized = {
19
+ kind: "runtime",
20
+ status: done.status,
21
+ conclusion: done.conclusion ?? done.status,
22
+ data: {
23
+ ok: typeof arts?.["ok"] === "boolean"
24
+ ? arts["ok"]
25
+ : done.conclusion === "success",
26
+ correlationId: done.sessionId,
27
+ log,
28
+ result: runtimePayload,
29
+ ...(error ? { error } : {}),
30
+ },
31
+ };
32
+ return normalized;
33
+ }
34
+ /**
35
+ * Build a GitHub (infra) DispatchResult.
36
+ * Downloads log and result artifacts from the artifact store.
37
+ */
38
+ export async function buildGithubResult(done) {
39
+ const [logBuffer, resultBuffer] = await Promise.all([
40
+ safeDownloadLogArtifact(done.artifacts),
41
+ safeDownloadResultArtifact(done.artifacts),
42
+ ]);
43
+ return {
44
+ kind: "github",
45
+ status: done.status,
46
+ conclusion: done.conclusion ?? done.status,
47
+ ...(done.artifacts?.log !== undefined ? { log: done.artifacts.log } : {}),
48
+ ...(logBuffer !== undefined ? { logBuffer } : {}),
49
+ ...(resultBuffer !== undefined ? { resultBuffer } : {}),
50
+ };
51
+ }
52
+ /**
53
+ * Build the final DispatchResult from a done session.
54
+ * Dispatches to the runtime or GitHub builder based on runType.
55
+ */
56
+ export async function finalizeResult(done) {
57
+ const artifacts = done.artifacts ?? {};
58
+ if (done.runType !== "infra") {
59
+ return buildRuntimeResult({
60
+ sessionId: done.sessionId,
61
+ status: done.status,
62
+ conclusion: done.conclusion,
63
+ artifacts: artifacts,
64
+ });
65
+ }
66
+ return buildGithubResult({
67
+ sessionId: done.sessionId,
68
+ status: done.status,
69
+ conclusion: done.conclusion,
70
+ artifacts: artifacts,
71
+ });
72
+ }
@@ -0,0 +1,28 @@
1
+ import type { Artifacts } from "./types.js";
2
+ export interface DownloadArtifactOptions {
3
+ /**
4
+ * Maximum allowed artifact size in bytes.
5
+ * Default: 50 MB
6
+ */
7
+ maxSizeBytes?: number;
8
+ }
9
+ /**
10
+ * Download artifact from a presigned URL with safety guards.
11
+ *
12
+ * This function:
13
+ * - validates HTTP status
14
+ * - enforces size limits
15
+ * - validates content-type (if provided)
16
+ *
17
+ * It does NOT:
18
+ * - retry
19
+ * - log
20
+ * - exit process
21
+ */
22
+ export declare function downloadArtifact(ref: {
23
+ url: string;
24
+ contentType?: string;
25
+ name?: string;
26
+ }, options?: DownloadArtifactOptions): Promise<Buffer>;
27
+ export declare function safeDownloadLogArtifact(artifacts?: Artifacts): Promise<Buffer | undefined>;
28
+ export declare function safeDownloadResultArtifact(artifacts?: Artifacts): Promise<Buffer | undefined>;
@@ -0,0 +1,102 @@
1
+ import { ArtifactInvalidTypeError, ArtifactNotFoundError, ArtifactTooLargeError } from "../core/errors.js";
2
+ /**
3
+ * Download artifact from a presigned URL with safety guards.
4
+ *
5
+ * This function:
6
+ * - validates HTTP status
7
+ * - enforces size limits
8
+ * - validates content-type (if provided)
9
+ *
10
+ * It does NOT:
11
+ * - retry
12
+ * - log
13
+ * - exit process
14
+ */
15
+ export async function downloadArtifact(ref, options = {}) {
16
+ const { maxSizeBytes = 50 * 1024 * 1024 } = options;
17
+ if (!ref?.url) {
18
+ throw new Error("\nInvalid artifact reference: missing url");
19
+ }
20
+ const res = await fetch(ref.url);
21
+ if (!res.ok) {
22
+ throw new Error(`\nFailed to download artifact: HTTP ${res.status} ${res.statusText}`);
23
+ }
24
+ const contentType = res.headers.get("content-type") || undefined;
25
+ if (ref.contentType && contentType && !contentType.includes(ref.contentType)) {
26
+ throw new Error(`\nArtifact content-type mismatch: expected ${ref.contentType}, got ${contentType}`);
27
+ }
28
+ const contentLengthHeader = res.headers.get("content-length");
29
+ if (contentLengthHeader) {
30
+ const size = Number(contentLengthHeader);
31
+ if (!Number.isNaN(size) && size > maxSizeBytes) {
32
+ throw new Error(`\nArtifact too large: ${size} bytes exceeds limit ${maxSizeBytes}`);
33
+ }
34
+ }
35
+ const reader = res.body?.getReader();
36
+ if (!reader) {
37
+ throw new Error("\nResponse body is not readable");
38
+ }
39
+ const chunks = [];
40
+ let received = 0;
41
+ while (true) {
42
+ const { done, value } = await reader.read();
43
+ if (done)
44
+ break;
45
+ if (value) {
46
+ received += value.byteLength;
47
+ if (received > maxSizeBytes) {
48
+ throw new Error(`\nArtifact too large: received ${received} bytes exceeds limit ${maxSizeBytes}`);
49
+ }
50
+ chunks.push(value);
51
+ }
52
+ }
53
+ return Buffer.concat(chunks.map((c) => Buffer.from(c)));
54
+ }
55
+ // Helpers for artifact download
56
+ export async function safeDownloadLogArtifact(artifacts) {
57
+ if (!artifacts?.log)
58
+ return undefined;
59
+ const art = artifacts.log;
60
+ // Respect artifact status contract
61
+ if (art.status !== "ok")
62
+ return undefined;
63
+ if (!art.downloadUrl)
64
+ return undefined;
65
+ try {
66
+ return await downloadArtifact({
67
+ url: art.downloadUrl,
68
+ contentType: "application/zip",
69
+ name: "log",
70
+ });
71
+ }
72
+ catch (err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ if (msg.includes("too large"))
75
+ throw new ArtifactTooLargeError();
76
+ if (msg.includes("content-type"))
77
+ throw new ArtifactInvalidTypeError();
78
+ throw new ArtifactNotFoundError(undefined, { cause: err });
79
+ }
80
+ }
81
+ export async function safeDownloadResultArtifact(artifacts) {
82
+ if (!artifacts?.result)
83
+ return undefined;
84
+ const art = artifacts.result;
85
+ if (!art.downloadUrl)
86
+ return undefined;
87
+ try {
88
+ return await downloadArtifact({
89
+ url: art.downloadUrl,
90
+ contentType: art.contentType ?? undefined,
91
+ name: "result",
92
+ });
93
+ }
94
+ catch (err) {
95
+ const msg = err instanceof Error ? err.message : String(err);
96
+ if (msg.includes("too large"))
97
+ throw new ArtifactTooLargeError();
98
+ if (msg.includes("content-type"))
99
+ throw new ArtifactInvalidTypeError();
100
+ throw new ArtifactNotFoundError(undefined, { cause: err });
101
+ }
102
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Normalize an artifact array.
3
+ * Accepts both flat arrays and numeric-keyed objects (from different
4
+ * backend serialization shapes).
5
+ */
6
+ export declare function normalizeArtifact(raw: unknown): unknown[];
7
+ /**
8
+ * Build a log artifact object from potentially numeric-keyed raw data.
9
+ */
10
+ export declare function buildLogArtifact(raw: unknown): object | undefined;
11
+ /**
12
+ * Normalize accepted artifacts for result consumption.
13
+ *
14
+ * Runtime payloads already follow their own semantic contract,
15
+ * so they are passed through unchanged. Infra artifacts are
16
+ * normalized to the GitHub artifact shape.
17
+ */
18
+ export declare function normalizeAcceptedArtifacts(artifacts: unknown, runType?: string): Record<string, unknown> | undefined;
19
+ /**
20
+ * Read a string artifact key from a raw artifacts object.
21
+ */
22
+ export declare function readArtifactString(artifacts: Record<string, unknown>, key: string): string | undefined;
@@ -0,0 +1,65 @@
1
+ import { extractString, isObject, } from "../utils/type-guards.js";
2
+ /**
3
+ * Normalize an artifact array.
4
+ * Accepts both flat arrays and numeric-keyed objects (from different
5
+ * backend serialization shapes).
6
+ */
7
+ export function normalizeArtifact(raw) {
8
+ if (Array.isArray(raw)) {
9
+ return raw;
10
+ }
11
+ if (!raw || typeof raw !== "object") {
12
+ return [];
13
+ }
14
+ const obj = raw;
15
+ const numericKeys = Object.keys(obj).filter((k) => !Number.isNaN(Number(k)));
16
+ if (numericKeys.length === 0) {
17
+ return [];
18
+ }
19
+ return numericKeys
20
+ .sort((a, b) => Number(a) - Number(b))
21
+ .map((k) => obj[k]);
22
+ }
23
+ /**
24
+ * Build a log artifact object from potentially numeric-keyed raw data.
25
+ */
26
+ export function buildLogArtifact(raw) {
27
+ if (!isObject(raw) || Array.isArray(raw))
28
+ return undefined;
29
+ return {
30
+ path: extractString(raw, "path") ?? "",
31
+ downloadUrl: extractString(raw, "downloadUrl") ?? "",
32
+ status: extractString(raw, "status") ?? "ok",
33
+ ...raw,
34
+ };
35
+ }
36
+ /**
37
+ * Normalize accepted artifacts for result consumption.
38
+ *
39
+ * Runtime payloads already follow their own semantic contract,
40
+ * so they are passed through unchanged. Infra artifacts are
41
+ * normalized to the GitHub artifact shape.
42
+ */
43
+ export function normalizeAcceptedArtifacts(artifacts, runType) {
44
+ if (!isObject(artifacts)) {
45
+ return undefined;
46
+ }
47
+ // Runtime payloads already follow their own semantic contract.
48
+ if (runType !== "infra") {
49
+ return artifacts;
50
+ }
51
+ const log = buildLogArtifact(artifacts["log"]);
52
+ const result = isObject(artifacts["result"])
53
+ ? { ...artifacts["result"] }
54
+ : undefined;
55
+ return {
56
+ ...(log ? { log } : {}),
57
+ ...(result ? { result } : {}),
58
+ };
59
+ }
60
+ /**
61
+ * Read a string artifact key from a raw artifacts object.
62
+ */
63
+ export function readArtifactString(artifacts, key) {
64
+ return extractString(artifacts, key);
65
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Log artifact produced by GitHub Actions pipeline.
3
+ * This is the SSOT contract used across dispatcher.
4
+ */
5
+ export type LogArtifact = {
6
+ path: string;
7
+ downloadUrl: string;
8
+ status: "ok" | "failed";
9
+ runStatus?: string;
10
+ conclusion?: string;
11
+ triggeredAt?: string;
12
+ startedAt?: string;
13
+ completedAt?: string;
14
+ queueDuration?: string;
15
+ runDuration?: string;
16
+ duration?: string;
17
+ };
18
+ /**
19
+ * Result artifact (JSON output from workflow)
20
+ */
21
+ export type ResultArtifact = {
22
+ path: string;
23
+ downloadUrl: string;
24
+ contentType: string;
25
+ };
26
+ /**
27
+ * Unified artifacts container (SSOT)
28
+ */
29
+ export type Artifacts = {
30
+ log?: LogArtifact;
31
+ result?: ResultArtifact;
32
+ };
33
+ /**
34
+ * Structured log entry for runtime commands.
35
+ * Replaces `unknown[]` with proper typing.
36
+ */
37
+ export interface LogEntry {
38
+ /** ISO timestamp of the log entry */
39
+ timestamp?: string;
40
+ /** Log level */
41
+ level?: 'info' | 'warn' | 'error' | 'debug';
42
+ /** Log message */
43
+ message: string;
44
+ /** Additional structured metadata */
45
+ metadata?: Record<string, unknown>;
46
+ /** Step name if this log belongs to a specific step */
47
+ step?: string;
48
+ /** Duration of the operation if applicable */
49
+ duration?: string;
50
+ }
51
+ /**
52
+ * Structured error for runtime artifacts.
53
+ */
54
+ export interface RuntimeError {
55
+ message: string;
56
+ stack?: string;
57
+ code?: string;
58
+ details?: Record<string, unknown>;
59
+ }
60
+ /**
61
+ * Runtime artifacts with proper typing.
62
+ * Used for hub-worker runtime commands.
63
+ */
64
+ export type RuntimeArtifacts = {
65
+ /** Whether the command succeeded */
66
+ ok?: boolean;
67
+ /** Structured log entries */
68
+ log?: LogEntry[];
69
+ /** Command result payload */
70
+ result?: unknown;
71
+ /** Structured error if command failed */
72
+ error?: RuntimeError;
73
+ };
74
+ /**
75
+ * Artifact reference returned by gateway.
76
+ * Used for lazy loading of artifacts.
77
+ */
78
+ export interface ArtifactRef {
79
+ /** Presigned download URL */
80
+ url: string;
81
+ /** Expected content type */
82
+ contentType?: string;
83
+ /** Human-readable name for diagnostics */
84
+ name?: string;
85
+ }
86
+ /**
87
+ * Gateway artifact format (minimal version).
88
+ * Used for initial response before full artifact is fetched.
89
+ */
90
+ export interface GatewayArtifactRef {
91
+ path: string;
92
+ downloadUrl?: string;
93
+ contentType?: string;
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,8 @@
1
+ import { TokenClaims } from "../session/types.js";
2
+ export interface Identity {
3
+ login: string;
4
+ teams: string[];
5
+ email: string;
6
+ avatarUrl: string;
7
+ }
8
+ export declare function extractIdentity(claims: TokenClaims): Identity;
@@ -0,0 +1,15 @@
1
+ import { normalizeTeams } from "./normalize-teams.js";
2
+ import { validateClaims } from "./validate-claims.js";
3
+ export function extractIdentity(claims) {
4
+ validateClaims(claims);
5
+ const teams = normalizeTeams(claims["teams"]);
6
+ if (teams.length === 0) {
7
+ throw new Error("Authentication completed but teams claim is missing.");
8
+ }
9
+ return {
10
+ login: claims.login,
11
+ teams,
12
+ email: typeof claims["email"] === "string" ? claims["email"] : "",
13
+ avatarUrl: typeof claims["avatarUrl"] === "string" ? claims["avatarUrl"] : "",
14
+ };
15
+ }
@@ -0,0 +1 @@
1
+ export declare function normalizeTeams(teamsClaim: unknown): string[];
@@ -0,0 +1,5 @@
1
+ export function normalizeTeams(teamsClaim) {
2
+ return Array.isArray(teamsClaim)
3
+ ? teamsClaim.filter((team) => typeof team === "string" && team.length > 0)
4
+ : [];
5
+ }
@@ -0,0 +1,2 @@
1
+ import { TokenClaims } from "../session/types.js";
2
+ export declare function validateClaims(claims: TokenClaims): void;
@@ -0,0 +1,5 @@
1
+ export function validateClaims(claims) {
2
+ if (typeof claims.login !== "string" || !claims.login) {
3
+ throw new Error("Authentication completed but login claim is missing.");
4
+ }
5
+ }
@@ -0,0 +1,2 @@
1
+ import { TokenClaims } from "../session/types.js";
2
+ export declare function parseJwtClaims(token: string): TokenClaims;
@@ -0,0 +1,16 @@
1
+ export function parseJwtClaims(token) {
2
+ const parts = token.split(".");
3
+ if (parts.length < 2) {
4
+ return {};
5
+ }
6
+ try {
7
+ const payloadPart = parts[1];
8
+ if (!payloadPart)
9
+ return {};
10
+ const payload = JSON.parse(Buffer.from(payloadPart, "base64url").toString("utf8"));
11
+ return payload && typeof payload === "object" ? payload : {};
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
@@ -0,0 +1 @@
1
+ export declare function isJwtExpired(token: string, skewSeconds?: number): boolean;
@@ -0,0 +1,9 @@
1
+ import { parseJwtClaims } from "./parse.js";
2
+ export function isJwtExpired(token, skewSeconds = 60) {
3
+ const claims = parseJwtClaims(token);
4
+ if (typeof claims.exp !== "number") {
5
+ return false;
6
+ }
7
+ const now = Math.floor(Date.now() / 1000);
8
+ return claims.exp <= now + skewSeconds;
9
+ }
@@ -0,0 +1,12 @@
1
+ export interface StructuredAuthError {
2
+ code: string;
3
+ message: string;
4
+ githubLogin?: string;
5
+ githubUserId?: number;
6
+ suggestion?: string;
7
+ }
8
+ export declare class AuthError extends Error {
9
+ readonly authError: StructuredAuthError;
10
+ constructor(authError: StructuredAuthError);
11
+ }
12
+ export declare function toStructuredAuthError(error: unknown): StructuredAuthError;
@@ -0,0 +1,47 @@
1
+ const MALFORMED_TRANSPORT_ERROR = {
2
+ code: "malformed_auth_error_transport",
3
+ message: "Authentication could not be completed due to an invalid auth error contract.",
4
+ suggestion: "Retry login. If the issue persists, contact support.",
5
+ };
6
+ export class AuthError extends Error {
7
+ authError;
8
+ constructor(authError) {
9
+ super(authError.message);
10
+ this.name = "AuthError";
11
+ this.authError = authError;
12
+ }
13
+ }
14
+ export function toStructuredAuthError(error) {
15
+ // (debug logging removed)
16
+ if (error instanceof AuthError) {
17
+ return error.authError;
18
+ }
19
+ if (isStructuredAuthError(error)) {
20
+ return {
21
+ code: error.code,
22
+ message: error.message,
23
+ ...(typeof error.githubLogin === "string" ? { githubLogin: error.githubLogin } : {}),
24
+ ...(typeof error.githubUserId === "number" ? { githubUserId: error.githubUserId } : {}),
25
+ ...(typeof error.suggestion === "string" ? { suggestion: error.suggestion } : {}),
26
+ };
27
+ }
28
+ if (error instanceof Error) {
29
+ console.error("[auth] malformed auth error transport from Error", {
30
+ name: error.name,
31
+ message: error.message,
32
+ });
33
+ }
34
+ else {
35
+ console.error("[auth] malformed auth error transport", {
36
+ receivedType: typeof error,
37
+ received: error,
38
+ });
39
+ }
40
+ return MALFORMED_TRANSPORT_ERROR;
41
+ }
42
+ function isStructuredAuthError(value) {
43
+ if (!value || typeof value !== "object")
44
+ return false;
45
+ const candidate = value;
46
+ return typeof candidate.code === "string" && typeof candidate.message === "string";
47
+ }
@@ -0,0 +1,30 @@
1
+ import { StructuredAuthError } from "./auth-error.js";
2
+ export type AuthSessionStatus = "pending" | "success" | "failed";
3
+ export interface AuthSession {
4
+ sessionId: string;
5
+ status: AuthSessionStatus;
6
+ login?: string;
7
+ email?: string;
8
+ avatarUrl?: string;
9
+ teams?: string[];
10
+ error?: StructuredAuthError;
11
+ createdAt: number;
12
+ updatedAt: number;
13
+ }
14
+ export interface AuthSessionResultPayload {
15
+ sessionId: string;
16
+ status: "success" | "failed";
17
+ login?: string;
18
+ email?: string;
19
+ avatarUrl?: string;
20
+ teams?: string[];
21
+ error?: StructuredAuthError;
22
+ }
23
+ export declare class AuthSessionStore {
24
+ private static readonly SESSION_TTL_MS;
25
+ private readonly sessions;
26
+ create(sessionId: string): AuthSession;
27
+ get(sessionId: string): AuthSession | undefined;
28
+ applyResult(payload: AuthSessionResultPayload): AuthSession | undefined;
29
+ delete(sessionId: string): void;
30
+ }