brightspace-mcp-server 1.6.2 → 2.0.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 (92) hide show
  1. package/README.md +34 -7
  2. package/build/api/client.d.ts +5 -3
  3. package/build/api/client.d.ts.map +1 -1
  4. package/build/api/client.js +71 -79
  5. package/build/api/client.js.map +1 -1
  6. package/build/api/errors.d.ts +4 -0
  7. package/build/api/errors.d.ts.map +1 -1
  8. package/build/api/errors.js +7 -0
  9. package/build/api/errors.js.map +1 -1
  10. package/build/auth/auth-cooldown.d.ts +16 -0
  11. package/build/auth/auth-cooldown.d.ts.map +1 -0
  12. package/build/auth/auth-cooldown.js +47 -0
  13. package/build/auth/auth-cooldown.js.map +1 -0
  14. package/build/auth/auth-lock.d.ts +7 -0
  15. package/build/auth/auth-lock.d.ts.map +1 -0
  16. package/build/auth/auth-lock.js +114 -0
  17. package/build/auth/auth-lock.js.map +1 -0
  18. package/build/auth/auth-runner.d.ts +17 -7
  19. package/build/auth/auth-runner.d.ts.map +1 -1
  20. package/build/auth/auth-runner.js +122 -26
  21. package/build/auth/auth-runner.js.map +1 -1
  22. package/build/auth/browser-auth.d.ts +19 -67
  23. package/build/auth/browser-auth.d.ts.map +1 -1
  24. package/build/auth/browser-auth.js +258 -574
  25. package/build/auth/browser-auth.js.map +1 -1
  26. package/build/auth/browser-state-store.d.ts +19 -0
  27. package/build/auth/browser-state-store.d.ts.map +1 -0
  28. package/build/auth/browser-state-store.js +101 -0
  29. package/build/auth/browser-state-store.js.map +1 -0
  30. package/build/auth/credential-store.d.ts +29 -0
  31. package/build/auth/credential-store.d.ts.map +1 -0
  32. package/build/auth/credential-store.js +233 -0
  33. package/build/auth/credential-store.js.map +1 -0
  34. package/build/auth/encrypted-store.d.ts +25 -0
  35. package/build/auth/encrypted-store.d.ts.map +1 -0
  36. package/build/auth/encrypted-store.js +61 -0
  37. package/build/auth/encrypted-store.js.map +1 -0
  38. package/build/auth/legacy-profile.d.ts +3 -0
  39. package/build/auth/legacy-profile.d.ts.map +1 -0
  40. package/build/auth/legacy-profile.js +41 -0
  41. package/build/auth/legacy-profile.js.map +1 -0
  42. package/build/auth/legacy-state.d.ts +14 -0
  43. package/build/auth/legacy-state.d.ts.map +1 -0
  44. package/build/auth/legacy-state.js +107 -0
  45. package/build/auth/legacy-state.js.map +1 -0
  46. package/build/auth/purdue-sso.d.ts +18 -36
  47. package/build/auth/purdue-sso.d.ts.map +1 -1
  48. package/build/auth/purdue-sso.js +165 -192
  49. package/build/auth/purdue-sso.js.map +1 -1
  50. package/build/auth/session-store.d.ts +16 -38
  51. package/build/auth/session-store.d.ts.map +1 -1
  52. package/build/auth/session-store.js +157 -152
  53. package/build/auth/session-store.js.map +1 -1
  54. package/build/auth/sso-flow.d.ts +14 -3
  55. package/build/auth/sso-flow.d.ts.map +1 -1
  56. package/build/auth/sso-flow.js +16 -1
  57. package/build/auth/sso-flow.js.map +1 -1
  58. package/build/auth/suny-sso.d.ts +3 -2
  59. package/build/auth/suny-sso.d.ts.map +1 -1
  60. package/build/auth/suny-sso.js +15 -24
  61. package/build/auth/suny-sso.js.map +1 -1
  62. package/build/auth/token-manager.d.ts +10 -3
  63. package/build/auth/token-manager.d.ts.map +1 -1
  64. package/build/auth/token-manager.js +62 -26
  65. package/build/auth/token-manager.js.map +1 -1
  66. package/build/auth/token-mint.d.ts +2 -2
  67. package/build/auth/token-mint.d.ts.map +1 -1
  68. package/build/auth/token-mint.js +31 -9
  69. package/build/auth/token-mint.js.map +1 -1
  70. package/build/auth-cli.d.ts +1 -7
  71. package/build/auth-cli.d.ts.map +1 -1
  72. package/build/auth-cli.js +29 -73
  73. package/build/auth-cli.js.map +1 -1
  74. package/build/index.js +8 -4
  75. package/build/index.js.map +1 -1
  76. package/build/setup.js +14 -8
  77. package/build/setup.js.map +1 -1
  78. package/build/types/index.d.ts +6 -0
  79. package/build/types/index.d.ts.map +1 -1
  80. package/build/utils/config-store.d.ts +1 -0
  81. package/build/utils/config-store.d.ts.map +1 -1
  82. package/build/utils/config-store.js +3 -0
  83. package/build/utils/config-store.js.map +1 -1
  84. package/build/utils/config.d.ts +2 -1
  85. package/build/utils/config.d.ts.map +1 -1
  86. package/build/utils/config.js +30 -10
  87. package/build/utils/config.js.map +1 -1
  88. package/build/utils/secure-config.d.ts +6 -0
  89. package/build/utils/secure-config.d.ts.map +1 -0
  90. package/build/utils/secure-config.js +57 -0
  91. package/build/utils/secure-config.js.map +1 -0
  92. package/package.json +4 -2
@@ -0,0 +1,114 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ import { hostname } from "node:os";
4
+ import { randomUUID } from "node:crypto";
5
+ export class AuthenticationInProgressError extends Error {
6
+ code = "AUTH_IN_PROGRESS";
7
+ constructor() {
8
+ super("Authentication already in progress. Retry after the current authentication finishes.");
9
+ this.name = "AuthenticationInProgressError";
10
+ }
11
+ }
12
+ async function readOwner(lockPath) {
13
+ try {
14
+ const owner = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf8"));
15
+ if (Number.isSafeInteger(owner.pid) && owner.pid > 0 && typeof owner.host === "string" && typeof owner.nonce === "string")
16
+ return owner;
17
+ }
18
+ catch {
19
+ // An incomplete or unknown lock is not proof that its owner is gone.
20
+ }
21
+ return undefined;
22
+ }
23
+ function isDead(owner) {
24
+ // Session directories are local-only. DHCP can change the host's name
25
+ // without changing this process table, so only PID liveness decides.
26
+ try {
27
+ process.kill(owner.pid, 0);
28
+ return false;
29
+ }
30
+ catch (error) {
31
+ return error.code === "ESRCH";
32
+ }
33
+ }
34
+ /** Remove only our fixed metadata names, after the directory was retired. */
35
+ async function cleanRetired(lockPath) {
36
+ await fs.unlink(path.join(lockPath, "owner.json")).catch(() => { });
37
+ await cleanChild(path.join(lockPath, "reclaim.lock"));
38
+ await fs.rmdir(lockPath).catch(() => { });
39
+ }
40
+ async function cleanChild(childPath) {
41
+ try {
42
+ if ((await fs.lstat(childPath)).isDirectory())
43
+ await cleanRetired(childPath);
44
+ }
45
+ catch { /* Already absent. */ }
46
+ }
47
+ class Lease {
48
+ directory;
49
+ owner;
50
+ constructor(directory, owner) {
51
+ this.directory = directory;
52
+ this.owner = owner;
53
+ }
54
+ async release() {
55
+ const current = await readOwner(this.directory);
56
+ if (current?.nonce !== this.owner.nonce)
57
+ return;
58
+ const retired = `${this.directory}.released.${this.owner.nonce}`;
59
+ await fs.rename(this.directory, retired);
60
+ await cleanRetired(retired);
61
+ }
62
+ }
63
+ /** Acquire a process-shared lock without waiting or relying on its age. */
64
+ export async function acquireProcessLock(lockPath) {
65
+ await fs.mkdir(path.dirname(lockPath), { recursive: true, mode: 0o700 });
66
+ const lease = await acquireLease(lockPath, 0);
67
+ return () => lease.release();
68
+ }
69
+ async function acquireLease(lockPath, depth) {
70
+ if (depth > 16)
71
+ throw new AuthenticationInProgressError();
72
+ const owner = { pid: process.pid, host: hostname(), nonce: randomUUID() };
73
+ for (let attempt = 0; attempt < 3; attempt += 1) {
74
+ try {
75
+ await fs.mkdir(lockPath, { mode: 0o700 });
76
+ try {
77
+ await fs.writeFile(path.join(lockPath, "owner.json"), JSON.stringify(owner), { flag: "wx", mode: 0o600 });
78
+ }
79
+ catch (error) {
80
+ await fs.rmdir(lockPath).catch(() => { });
81
+ throw error;
82
+ }
83
+ return new Lease(lockPath, owner);
84
+ }
85
+ catch (error) {
86
+ if (error.code === "ENOENT")
87
+ throw new AuthenticationInProgressError();
88
+ if (error.code !== "EEXIST")
89
+ throw error;
90
+ }
91
+ const stale = await readOwner(lockPath);
92
+ if (!stale || !isDead(stale))
93
+ throw new AuthenticationInProgressError();
94
+ // Recovery gets its own process lock within the stale directory. If a
95
+ // recovering process crashes, the same dead-owner rules recover its lock.
96
+ // Keep this claim in the directory during rename to serialize contenders.
97
+ const claim = await acquireLease(path.join(lockPath, "reclaim.lock"), depth + 1);
98
+ try {
99
+ const current = await readOwner(lockPath);
100
+ if (current?.nonce !== stale.nonce || !isDead(current))
101
+ throw new AuthenticationInProgressError();
102
+ const retired = `${lockPath}.stale.${owner.nonce}`;
103
+ await fs.rename(lockPath, retired);
104
+ claim.directory = path.join(retired, "reclaim.lock");
105
+ await claim.release();
106
+ await cleanRetired(retired);
107
+ }
108
+ finally {
109
+ await claim.release();
110
+ }
111
+ }
112
+ throw new AuthenticationInProgressError();
113
+ }
114
+ //# sourceMappingURL=auth-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-lock.js","sourceRoot":"","sources":["../../src/auth/auth-lock.ts"],"names":[],"mappings":"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"}
@@ -3,23 +3,33 @@
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
+ import { AuthError } from "../utils/errors.js";
7
+ export type AuthFailureKind = "busy" | "cooldown" | "unsupported" | "secureStorage" | "transport" | "timeout" | "failed";
8
+ export declare class AuthProcessError extends AuthError {
9
+ readonly kind: AuthFailureKind;
10
+ constructor(kind: AuthFailureKind, message: string);
11
+ }
12
+ export interface AuthRunnerOptions {
13
+ timeoutMs?: number;
14
+ onProgress?: (line: string) => void;
15
+ }
6
16
  /**
7
17
  * Launches the brightspace-auth CLI as a child process to
8
18
  * re-authenticate when the current session has expired.
9
19
  *
10
- * The child process inherits the parent's environment (so .env credentials
11
- * are available via dotenv in the auth CLI) and runs with the project root
12
- * as CWD (so dotenv can find the .env file).
20
+ * The child inherits the parent's resolved environment and working directory,
21
+ * so both processes read the same account configuration and .env file.
13
22
  */
14
23
  export declare class AuthRunner {
15
24
  private running;
16
25
  private readonly scriptPath;
17
- private readonly projectRoot;
18
- constructor();
26
+ private readonly timeoutMs;
27
+ private readonly onProgress?;
28
+ constructor(options?: AuthRunnerOptions);
19
29
  /**
20
30
  * Spawn the auth CLI and wait for it to complete.
21
- * Returns true if authentication succeeded, false otherwise.
22
- * Prevents concurrent auth attempts via a simple mutex.
31
+ * Returns true on success and throws a useful error on failure.
32
+ * Prevents concurrent attempts within this MCP process.
23
33
  */
24
34
  run(): Promise<boolean>;
25
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth-runner.d.ts","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA4CH;;;;;;;GAOG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;;IASrC;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAwD9B"}
1
+ {"version":3,"file":"auth-runner.d.ts","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAW/C,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,UAAU,GAAG,aAAa,GAAG,eAAe,GAAG,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEzH,qBAAa,gBAAiB,SAAQ,SAAS;aACjB,IAAI,EAAE,eAAe;gBAArB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM;CAInE;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAkDD;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAyB;gBAEzC,OAAO,GAAE,iBAAsB;IAQ3C;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;CAyG9B"}
@@ -3,17 +3,46 @@
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
4
  * Licensed under MIT — see LICENSE file for details.
5
5
  */
6
- import { spawn } from "node:child_process";
6
+ import { spawn, execFileSync } from "node:child_process";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import * as path from "node:path";
9
9
  import { log } from "../utils/logger.js";
10
+ import { AuthError } from "../utils/errors.js";
10
11
  /**
11
12
  * Timeout for the auth process. It has to outlast the child's own MFA wait,
12
13
  * which is five minutes: a person has to find their phone, unlock it, and read
13
14
  * a number off the screen. A shorter parent budget would kill the child in the
14
15
  * middle of a sign-in the user was still completing.
15
16
  */
16
- const AUTH_TIMEOUT_MS = 6 * 60 * 1000; // 6 minutes
17
+ const AUTH_TIMEOUT_MS = 8 * 60 * 1000;
18
+ const KILL_GRACE_MS = 5000;
19
+ export class AuthProcessError extends AuthError {
20
+ kind;
21
+ constructor(kind, message) {
22
+ super(message);
23
+ this.kind = kind;
24
+ this.name = "AuthProcessError";
25
+ }
26
+ }
27
+ /** Chromium can lead a separate process group, so a forced stop needs its PID. */
28
+ function descendantPids(parentPid) {
29
+ const rows = execFileSync("ps", ["-eo", "pid=,ppid="], {
30
+ encoding: "utf8", timeout: 1000,
31
+ }).trim().split("\n").map(line => line.trim().split(/\s+/).map(Number));
32
+ const descendants = [];
33
+ const visited = new Set([parentPid]);
34
+ const visit = (parent) => {
35
+ for (const [pid, ppid] of rows) {
36
+ if (ppid === parent && Number.isInteger(pid) && pid > 1 && !visited.has(pid)) {
37
+ visited.add(pid);
38
+ visit(pid);
39
+ descendants.push(pid);
40
+ }
41
+ }
42
+ };
43
+ visit(parentPid);
44
+ return descendants;
45
+ }
17
46
  /**
18
47
  * Forward a child stream to the server log, one line at a time.
19
48
  *
@@ -45,67 +74,134 @@ function forwardLines(stream, emit) {
45
74
  * Launches the brightspace-auth CLI as a child process to
46
75
  * re-authenticate when the current session has expired.
47
76
  *
48
- * The child process inherits the parent's environment (so .env credentials
49
- * are available via dotenv in the auth CLI) and runs with the project root
50
- * as CWD (so dotenv can find the .env file).
77
+ * The child inherits the parent's resolved environment and working directory,
78
+ * so both processes read the same account configuration and .env file.
51
79
  */
52
80
  export class AuthRunner {
53
81
  running = false;
54
82
  scriptPath;
55
- projectRoot;
56
- constructor() {
83
+ timeoutMs;
84
+ onProgress;
85
+ constructor(options = {}) {
57
86
  // Resolve paths relative to this file's compiled location (build/auth/auth-runner.js)
58
87
  const thisDir = path.dirname(fileURLToPath(import.meta.url));
59
88
  this.scriptPath = path.resolve(thisDir, "..", "auth-cli.js");
60
- this.projectRoot = path.resolve(thisDir, "..", "..");
89
+ this.timeoutMs = options.timeoutMs ?? AUTH_TIMEOUT_MS;
90
+ this.onProgress = options.onProgress;
61
91
  }
62
92
  /**
63
93
  * Spawn the auth CLI and wait for it to complete.
64
- * Returns true if authentication succeeded, false otherwise.
65
- * Prevents concurrent auth attempts via a simple mutex.
94
+ * Returns true on success and throws a useful error on failure.
95
+ * Prevents concurrent attempts within this MCP process.
66
96
  */
67
97
  async run() {
68
98
  if (this.running) {
69
- log("DEBUG", "Auth already running, skipping duplicate attempt");
70
- return false;
99
+ throw new AuthProcessError("busy", "Authentication already in progress. Complete the existing attempt, then retry.");
71
100
  }
72
101
  this.running = true;
73
102
  try {
74
103
  log("INFO", "Auto-launching brightspace-auth...");
75
- return await new Promise((resolve) => {
104
+ return await new Promise((resolve, reject) => {
76
105
  const child = spawn(process.execPath, // use the same Node binary
77
- [this.scriptPath], {
78
- cwd: this.projectRoot,
106
+ [this.scriptPath, "--automatic"], {
107
+ cwd: process.cwd(),
79
108
  env: { ...process.env },
80
109
  stdio: ["ignore", "pipe", "pipe"],
110
+ detached: process.platform !== "win32",
81
111
  });
82
112
  let timedOut = false;
113
+ let settled = false;
114
+ let killTimer;
115
+ const kill = (signal) => {
116
+ try {
117
+ if (child.pid && process.platform === "win32") {
118
+ execFileSync("taskkill", ["/pid", String(child.pid), "/t", "/f"], {
119
+ stdio: "ignore", timeout: 5000,
120
+ });
121
+ }
122
+ else if (child.pid) {
123
+ if (signal === "SIGKILL") {
124
+ try {
125
+ for (const pid of descendantPids(child.pid)) {
126
+ try {
127
+ process.kill(pid, "SIGKILL");
128
+ }
129
+ catch { /* Already exited. */ }
130
+ }
131
+ }
132
+ catch { /* Still terminate the owned process group if ps is unavailable. */ }
133
+ }
134
+ process.kill(-child.pid, signal);
135
+ }
136
+ else
137
+ child.kill(signal);
138
+ }
139
+ catch {
140
+ // The child may already have exited between close and cleanup.
141
+ }
142
+ };
143
+ const onExit = () => kill("SIGTERM");
144
+ process.once("exit", onExit);
145
+ const finish = (error) => {
146
+ if (settled)
147
+ return;
148
+ settled = true;
149
+ clearTimeout(timer);
150
+ if (killTimer)
151
+ clearTimeout(killTimer);
152
+ process.off("exit", onExit);
153
+ if (error)
154
+ reject(error);
155
+ else
156
+ resolve(true);
157
+ };
83
158
  const timer = setTimeout(() => {
84
159
  timedOut = true;
85
- child.kill("SIGTERM");
86
- }, AUTH_TIMEOUT_MS);
87
- forwardLines(child.stderr, (line) => log("INFO", line));
160
+ kill("SIGTERM");
161
+ killTimer = setTimeout(() => {
162
+ kill("SIGKILL");
163
+ finish(new AuthProcessError("timeout", "Authentication timed out. Run brightspace-auth to try again."));
164
+ }, KILL_GRACE_MS);
165
+ }, this.timeoutMs);
166
+ forwardLines(child.stderr, (line) => {
167
+ log("INFO", line);
168
+ try {
169
+ this.onProgress?.(line);
170
+ }
171
+ catch { /* Logging must not interrupt authentication. */ }
172
+ });
88
173
  // Piped and drained rather than ignored: a full stdout pipe would
89
174
  // block the child mid-login.
90
175
  forwardLines(child.stdout, (line) => log("DEBUG", line));
91
176
  child.on("error", (error) => {
92
- clearTimeout(timer);
177
+ if (settled)
178
+ return;
93
179
  log("ERROR", "Auto-auth process failed", error.message);
94
- resolve(false);
180
+ kill("SIGKILL");
181
+ finish(new AuthProcessError("failed", "Could not start authentication. Run brightspace-auth for details."));
95
182
  });
96
183
  child.on("close", (code) => {
97
- clearTimeout(timer);
184
+ if (settled)
185
+ return;
98
186
  if (timedOut) {
99
- log("ERROR", `Auto-auth timed out after ${AUTH_TIMEOUT_MS / 60000} minutes`);
100
- resolve(false);
187
+ kill("SIGKILL");
188
+ finish(new AuthProcessError("timeout", "Authentication timed out. Run brightspace-auth to try again."));
101
189
  }
102
190
  else if (code === 0) {
103
191
  log("INFO", "Auto-auth completed successfully");
104
- resolve(true);
192
+ finish();
105
193
  }
106
194
  else {
107
- log("ERROR", `Auto-auth process failed with exit code ${code}`);
108
- resolve(false);
195
+ const failures = {
196
+ 2: ["busy", "Authentication already in progress in another process. Complete that attempt, then retry."],
197
+ 3: ["cooldown", "Automatic MFA is paused after an unsuccessful attempt. Run brightspace-auth to retry immediately."],
198
+ 4: ["unsupported", "This identity provider cannot complete headless authentication. See the authentication logs."],
199
+ 5: ["secureStorage", "The native credential store is unavailable or locked. Unlock it and retry."],
200
+ 6: ["transport", "Brightspace authentication is temporarily unavailable because of a network or server failure. Your saved session was preserved. Try again later."],
201
+ };
202
+ const [kind, message] = failures[code ?? -1] ?? ["failed", "Authentication failed. Run brightspace-auth to try again."];
203
+ kill("SIGKILL");
204
+ finish(new AuthProcessError(kind, message));
109
205
  }
110
206
  });
111
207
  });
@@ -1 +1 @@
1
- {"version":3,"file":"auth-runner.js","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth-runner.js","sourceRoot":"","sources":["../../src/auth/auth-runner.ts"],"names":[],"mappings":"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"}
@@ -1,44 +1,27 @@
1
1
  /**
2
- * Purdue Brightspace MCP Server
2
+ * Brightspace MCP Server
3
3
  * Copyright (c) 2026 Rohan Muppa. All rights reserved.
4
- * Licensed under MIT — see LICENSE file for details.
4
+ * Licensed under MIT: see LICENSE file for details.
5
5
  */
6
6
  import type { AppConfig, TokenData } from "../types/index.js";
7
+ export declare class BrowserAuthTransportError extends Error {
8
+ readonly code = "AUTH_TRANSPORT";
9
+ constructor(message: string, options?: ErrorOptions);
10
+ }
11
+ export interface AuthenticateOptions {
12
+ automatic?: boolean;
13
+ /** Persist the token before releasing the shared authentication lock. */
14
+ onAuthenticated?: (token: TokenData) => Promise<void>;
15
+ }
7
16
  export declare class BrowserAuth {
8
17
  private config;
9
18
  private ssoFlow;
19
+ private readonly stateStore;
20
+ private readonly cooldown;
10
21
  constructor(config: AppConfig);
11
- /**
12
- * Detect if running inside WSL (Windows Subsystem for Linux) or Docker.
13
- * These environments require --no-sandbox for Chromium to launch.
14
- */
15
22
  private static isWSLOrDocker;
16
- /**
17
- * Build Chromium launch args based on the current platform and environment.
18
- */
19
- private static buildChromiumArgs;
20
- /**
21
- * Prevent Chrome for Testing from SIGTRAP'ing on launch on macOS (issue #10).
22
- *
23
- * Three layers, all idempotent and cheap:
24
- * 1. ApplePersistenceIgnoreState — disables NSPersistentUIRestorer's crash-prompt
25
- * modal, which Chrome for Testing's AppKit bridge cannot handle.
26
- * 2. IIO_LaunchInfo=0 — resets LaunchServices' per-app crash counter so macOS
27
- * stops triggering the recovery pathway in the first place.
28
- * 3. Nuke the Cocoa saved-application-state bundle — if it exists, AppKit tries
29
- * to replay window state during launch and can crash the browser process.
30
- *
31
- * Runs before every launch. None of these are destructive to user data; Chrome
32
- * for Testing is a disposable test profile.
33
- */
34
- private static applyMacOSCrashGuard;
35
- /**
36
- * Recovery step: the current browser-data profile is corrupted in a way that
37
- * makes Chromium SIGTRAP during launch (issue #10). Move it aside so the next
38
- * launch attempt gets a clean profile. Cheap — user just re-authenticates.
39
- */
40
- private quarantineBrowserDataDir;
41
- authenticate(): Promise<TokenData>;
23
+ authenticate(options?: AuthenticateOptions): Promise<TokenData>;
24
+ private attemptAuthentication;
42
25
  /**
43
26
  * Run the token extraction strategy chain, cheapest to most invasive.
44
27
  * Each strategy validates against /users/whoami before returning.
@@ -50,23 +33,14 @@ export declare class BrowserAuth {
50
33
  * Returns true if the token is accepted by D2L, false otherwise.
51
34
  */
52
35
  private validateToken;
53
- /**
54
- * Set up passive network request listener to capture Bearer token.
55
- * MUST be called BEFORE page.goto() to avoid race condition.
56
- */
57
- private setupTokenInterception;
58
- /**
59
- * Navigate to Brightspace and login if needed.
60
- * Returns true if already authenticated (cookies valid), false if SSO login was performed.
61
- */
36
+ /** Restore first, and only attempt credentials after a real sign-in is needed. */
62
37
  private navigateAndLogin;
63
- /** Let the page finish loading, without letting a slow one fail the run. */
64
- private settle;
65
38
  /**
66
39
  * Give the SSO chain a bounded window to finish with no human input.
67
40
  * Returns true only when the session proves itself live.
68
41
  */
69
42
  private awaitSilentSSO;
43
+ private hasCredentialPrompt;
70
44
  /**
71
45
  * A POSITIVE session check, never "the URL doesn't look like a login page".
72
46
  * Institutions bounce through intermediate SAML hops with a perfectly live
@@ -81,6 +55,8 @@ export declare class BrowserAuth {
81
55
  private clickSilentSurfaces;
82
56
  /** Visibility without the actionability wait, so a poll keeps its rhythm. */
83
57
  private isOnScreen;
58
+ /** Check each selector independently so a hidden earlier match cannot mask a visible one. */
59
+ private isAnyOnScreen;
84
60
  /**
85
61
  * Try to extract Bearer token from D2L's localStorage.
86
62
  * D2L stores API tokens in localStorage under "D2L.Fetch.Tokens".
@@ -109,29 +85,5 @@ export declare class BrowserAuth {
109
85
  * Constructs a cookie header string from d2lSessionVal and d2lSecureSessionVal.
110
86
  */
111
87
  private extractCookieToken;
112
- /**
113
- * Load previously saved storage state (cookies + localStorage).
114
- * Workaround for Playwright bug #36139: session cookies don't persist in persistent context.
115
- */
116
- private loadStorageState;
117
- private saveStorageState;
118
- /**
119
- * Launch browser with retry logic.
120
- * Windows is prone to 180s launch timeouts (Playwright issue #22117) caused by
121
- * lingering Chromium processes, antivirus interference, or resource contention.
122
- * On timeout, we clear lock files and retry once.
123
- */
124
- private launchBrowserWithRetry;
125
- /**
126
- * Validate and remove stale Chromium lock files from the browser data directory.
127
- * Playwright's persistent context uses Chromium's SingletonLock mechanism.
128
- * If the browser is killed unexpectedly (antivirus, force close, crash),
129
- * these lock files persist and block all future launch attempts.
130
- *
131
- * For SingletonLock (a symlink whose target is "hostname-pid"), we check
132
- * whether the owning process is still alive before removing it. This avoids
133
- * deleting a lock held by a legitimate running instance.
134
- */
135
- private validateAndClearLockFiles;
136
88
  }
137
89
  //# sourceMappingURL=browser-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AASH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AA2B9D,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAU;gBAEb,MAAM,EAAE,SAAS;IAK7B;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAwB5B;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAoBhC;;;;;;;;;;;;;OAaG;mBACkB,oBAAoB;IA8CzC;;;;OAIG;YACW,wBAAwB;IAoBhC,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IA6KxC;;;;OAIG;YACW,eAAe;IAyD7B;;;OAGG;YACW,aAAa;IAmC3B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+B9B;;;OAGG;YACW,gBAAgB;IA8C9B,4EAA4E;YAC9D,MAAM;IAQpB;;;OAGG;YACW,cAAc;IAoB5B;;;;;OAKG;YACW,cAAc;IAiB5B;;;OAGG;YACW,mBAAmB;IAsCjC,6EAA6E;YAC/D,UAAU;IAIxB;;;OAGG;YACW,wBAAwB;IAyCtC;;;;;OAKG;YACW,sBAAsB;IAoCpC;;;;OAIG;YACW,eAAe;IAyC7B;;;OAGG;YACW,gBAAgB;IAgE9B;;;OAGG;YACW,kBAAkB;IAiChC;;;OAGG;YACW,gBAAgB;YA8FhB,gBAAgB;IAc9B;;;;;OAKG;YACW,sBAAsB;IA+DpC;;;;;;;;;OASG;YACW,yBAAyB;CAiExC"}
1
+ {"version":3,"file":"browser-auth.d.ts","sourceRoot":"","sources":["../../src/auth/browser-auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAuB9D,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,oBAAoB;gBACrB,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;CAIpD;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,yEAAyE;IACzE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAe;gBAE5B,MAAM,EAAE,SAAS;IAO7B,OAAO,CAAC,MAAM,CAAC,aAAa;IAetB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,SAAS,CAAC;YAgB3D,qBAAqB;IA+DnC;;;;OAIG;YACW,eAAe;IA0D7B;;;OAGG;YACW,aAAa;IA0B3B,kFAAkF;YACpE,gBAAgB;IAmD9B;;;OAGG;YACW,cAAc;YAmDd,mBAAmB;IAKjC;;;;;OAKG;YACW,cAAc;IAsB5B;;;OAGG;YACW,mBAAmB;IA0CjC,6EAA6E;YAC/D,UAAU;IAQxB,6FAA6F;YAC/E,aAAa;IAO3B;;;OAGG;YACW,wBAAwB;IAyCtC;;;;;OAKG;YACW,sBAAsB;IAoCpC;;;;OAIG;YACW,eAAe;IAoD7B;;;OAGG;YACW,gBAAgB;IAgE9B;;;OAGG;YACW,kBAAkB;CAiCjC"}