@falkenslab/moodle-driver 0.4.1 → 0.5.1

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 (185) hide show
  1. package/README.md +3 -2
  2. package/dist/areas/activities/build.d.ts +47 -10
  3. package/dist/areas/activities/build.js +157 -24
  4. package/dist/areas/activities/build.js.map +1 -1
  5. package/dist/areas/activities/conditions.d.ts +77 -0
  6. package/dist/areas/activities/conditions.js +216 -0
  7. package/dist/areas/activities/conditions.js.map +1 -0
  8. package/dist/areas/activities/content.d.ts +3 -0
  9. package/dist/areas/activities/content.js +1 -0
  10. package/dist/areas/activities/content.js.map +1 -1
  11. package/dist/areas/assignments/grader.d.ts +32 -0
  12. package/dist/areas/assignments/grader.js +66 -2
  13. package/dist/areas/assignments/grader.js.map +1 -1
  14. package/dist/areas/assignments/grading.d.ts +69 -6
  15. package/dist/areas/assignments/grading.js +173 -13
  16. package/dist/areas/assignments/grading.js.map +1 -1
  17. package/dist/areas/assignments/student.d.ts +14 -0
  18. package/dist/areas/assignments/student.js +43 -2
  19. package/dist/areas/assignments/student.js.map +1 -1
  20. package/dist/areas/backup/backup.d.ts +86 -0
  21. package/dist/areas/backup/backup.js +368 -0
  22. package/dist/areas/backup/backup.js.map +1 -0
  23. package/dist/areas/calendar/calendar.d.ts +27 -0
  24. package/dist/areas/calendar/calendar.js +115 -9
  25. package/dist/areas/calendar/calendar.js.map +1 -1
  26. package/dist/areas/completion/completion.d.ts +3 -0
  27. package/dist/areas/completion/completion.js.map +1 -1
  28. package/dist/areas/courses/list.d.ts +2 -0
  29. package/dist/areas/courses/list.js.map +1 -1
  30. package/dist/areas/courses/reset.d.ts +51 -0
  31. package/dist/areas/courses/reset.js +123 -0
  32. package/dist/areas/courses/reset.js.map +1 -0
  33. package/dist/areas/courses/state.d.ts +17 -0
  34. package/dist/areas/courses/state.js +37 -6
  35. package/dist/areas/courses/state.js.map +1 -1
  36. package/dist/areas/courses/structure.d.ts +14 -2
  37. package/dist/areas/courses/structure.js +11 -5
  38. package/dist/areas/courses/structure.js.map +1 -1
  39. package/dist/areas/forums/discussions.d.ts +8 -0
  40. package/dist/areas/forums/discussions.js +10 -0
  41. package/dist/areas/forums/discussions.js.map +1 -1
  42. package/dist/areas/forums/grading.d.ts +49 -0
  43. package/dist/areas/forums/grading.js +125 -0
  44. package/dist/areas/forums/grading.js.map +1 -0
  45. package/dist/areas/forums/posts.d.ts +37 -0
  46. package/dist/areas/forums/posts.js +141 -6
  47. package/dist/areas/forums/posts.js.map +1 -1
  48. package/dist/areas/grades/export.d.ts +38 -0
  49. package/dist/areas/grades/export.js +88 -0
  50. package/dist/areas/grades/export.js.map +1 -0
  51. package/dist/areas/grades/gradebook.d.ts +50 -0
  52. package/dist/areas/grades/gradebook.js +120 -0
  53. package/dist/areas/grades/gradebook.js.map +1 -1
  54. package/dist/areas/grades/singleview.d.ts +39 -0
  55. package/dist/areas/grades/singleview.js +186 -0
  56. package/dist/areas/grades/singleview.js.map +1 -0
  57. package/dist/areas/h5p/h5p.d.ts +2 -0
  58. package/dist/areas/h5p/h5p.js +12 -7
  59. package/dist/areas/h5p/h5p.js.map +1 -1
  60. package/dist/areas/lessons/lessons.d.ts +1 -0
  61. package/dist/areas/lessons/lessons.js.map +1 -1
  62. package/dist/areas/messages/messages.d.ts +32 -0
  63. package/dist/areas/messages/messages.js +44 -2
  64. package/dist/areas/messages/messages.js.map +1 -1
  65. package/dist/areas/participants/enrolments.d.ts +60 -0
  66. package/dist/areas/participants/enrolments.js +218 -0
  67. package/dist/areas/participants/enrolments.js.map +1 -0
  68. package/dist/areas/participants/groups.d.ts +75 -0
  69. package/dist/areas/participants/groups.js +201 -0
  70. package/dist/areas/participants/groups.js.map +1 -0
  71. package/dist/areas/participants/methods.d.ts +56 -0
  72. package/dist/areas/participants/methods.js +154 -0
  73. package/dist/areas/participants/methods.js.map +1 -0
  74. package/dist/areas/participants/participants.d.ts +11 -0
  75. package/dist/areas/participants/participants.js +24 -19
  76. package/dist/areas/participants/participants.js.map +1 -1
  77. package/dist/areas/questions/banks.d.ts +36 -1
  78. package/dist/areas/questions/banks.js +140 -17
  79. package/dist/areas/questions/banks.js.map +1 -1
  80. package/dist/areas/quizzes/attempts.d.ts +2 -0
  81. package/dist/areas/quizzes/attempts.js +9 -0
  82. package/dist/areas/quizzes/attempts.js.map +1 -1
  83. package/dist/areas/quizzes/grading.d.ts +50 -0
  84. package/dist/areas/quizzes/grading.js +131 -0
  85. package/dist/areas/quizzes/grading.js.map +1 -0
  86. package/dist/areas/quizzes/results.d.ts +3 -0
  87. package/dist/areas/quizzes/results.js +2 -1
  88. package/dist/areas/quizzes/results.js.map +1 -1
  89. package/dist/areas/quizzes/structure.d.ts +65 -9
  90. package/dist/areas/quizzes/structure.js +222 -23
  91. package/dist/areas/quizzes/structure.js.map +1 -1
  92. package/dist/areas/ratings/ratings.d.ts +26 -0
  93. package/dist/areas/ratings/ratings.js +108 -0
  94. package/dist/areas/ratings/ratings.js.map +1 -0
  95. package/dist/areas/reports/dates.d.ts +18 -0
  96. package/dist/areas/reports/dates.js +134 -0
  97. package/dist/areas/reports/dates.js.map +1 -0
  98. package/dist/areas/reports/reports.d.ts +143 -0
  99. package/dist/areas/reports/reports.js +267 -0
  100. package/dist/areas/reports/reports.js.map +1 -0
  101. package/dist/areas/scorm/scorm.d.ts +1 -0
  102. package/dist/areas/scorm/scorm.js.map +1 -1
  103. package/dist/areas/site/users.d.ts +7 -1
  104. package/dist/areas/site/users.js +9 -2
  105. package/dist/areas/site/users.js.map +1 -1
  106. package/dist/areas/surveys/choice.d.ts +2 -0
  107. package/dist/areas/surveys/choice.js +6 -3
  108. package/dist/areas/surveys/choice.js.map +1 -1
  109. package/dist/areas/surveys/surveys.d.ts +3 -0
  110. package/dist/areas/surveys/surveys.js +1 -0
  111. package/dist/areas/surveys/surveys.js.map +1 -1
  112. package/dist/areas/workshops/workshops.d.ts +133 -0
  113. package/dist/areas/workshops/workshops.js +335 -0
  114. package/dist/areas/workshops/workshops.js.map +1 -0
  115. package/dist/browser/background.d.ts +43 -0
  116. package/dist/browser/background.js +164 -0
  117. package/dist/browser/background.js.map +1 -0
  118. package/dist/browser/host.d.ts +30 -0
  119. package/dist/browser/host.js +373 -0
  120. package/dist/browser/host.js.map +1 -0
  121. package/dist/browser/manager.d.ts +9 -0
  122. package/dist/browser/manager.js +8 -0
  123. package/dist/browser/manager.js.map +1 -1
  124. package/dist/categories.d.ts +1 -0
  125. package/dist/categories.js +67 -0
  126. package/dist/categories.js.map +1 -1
  127. package/dist/cli/args.d.ts +8 -0
  128. package/dist/cli/args.js +49 -0
  129. package/dist/cli/args.js.map +1 -1
  130. package/dist/cli/background.d.ts +23 -0
  131. package/dist/cli/background.js +128 -0
  132. package/dist/cli/background.js.map +1 -0
  133. package/dist/cli/command.d.ts +66 -0
  134. package/dist/cli/command.js +328 -0
  135. package/dist/cli/command.js.map +1 -0
  136. package/dist/cli/commands.generated.js +2635 -577
  137. package/dist/cli/commands.generated.js.map +1 -1
  138. package/dist/cli/config.d.ts +2 -0
  139. package/dist/cli/config.js +2 -0
  140. package/dist/cli/config.js.map +1 -1
  141. package/dist/cli/main.js +30 -10
  142. package/dist/cli/main.js.map +1 -1
  143. package/dist/cli/run.d.ts +3 -14
  144. package/dist/cli/run.js +9 -185
  145. package/dist/cli/run.js.map +1 -1
  146. package/dist/cli/shell.d.ts +9 -0
  147. package/dist/cli/shell.js +233 -0
  148. package/dist/cli/shell.js.map +1 -0
  149. package/dist/client.d.ts +10 -120
  150. package/dist/client.js +77 -8
  151. package/dist/client.js.map +1 -1
  152. package/dist/core/errors.d.ts +1 -1
  153. package/dist/core/errors.js.map +1 -1
  154. package/dist/core/files.d.ts +5 -0
  155. package/dist/core/files.js +7 -3
  156. package/dist/core/files.js.map +1 -1
  157. package/dist/core/forms.d.ts +33 -3
  158. package/dist/core/forms.js +105 -8
  159. package/dist/core/forms.js.map +1 -1
  160. package/dist/core/http.d.ts +7 -0
  161. package/dist/core/http.js +28 -7
  162. package/dist/core/http.js.map +1 -1
  163. package/dist/core/login.d.ts +3 -0
  164. package/dist/core/login.js +2 -0
  165. package/dist/core/login.js.map +1 -1
  166. package/dist/core/session.d.ts +12 -1
  167. package/dist/core/session.js +12 -1
  168. package/dist/core/session.js.map +1 -1
  169. package/dist/index.d.ts +35 -11
  170. package/dist/index.js +32 -12
  171. package/dist/index.js.map +1 -1
  172. package/dist/internals.d.ts +9 -1
  173. package/dist/internals.js +9 -4
  174. package/dist/internals.js.map +1 -1
  175. package/dist/moodle-methods.generated.d.ts +1127 -0
  176. package/dist/moodle-methods.generated.js +4 -0
  177. package/dist/moodle-methods.generated.js.map +1 -0
  178. package/dist/options.d.ts +2 -0
  179. package/dist/options.js.map +1 -1
  180. package/dist/testing/fakeMoodle.d.ts +9 -2
  181. package/dist/testing/fakeMoodle.js +5 -3
  182. package/dist/testing/fakeMoodle.js.map +1 -1
  183. package/dist/testing/index.d.ts +7 -0
  184. package/dist/testing/index.js.map +1 -1
  185. package/package.json +2 -1
@@ -0,0 +1,164 @@
1
+ // Working on a browser another process keeps open (ADR-014): where to find it, from the file it leaves
2
+ // in its profile, and a browser manager that connects to it instead of launching one.
3
+ import { existsSync, readFileSync, rmSync } from "node:fs";
4
+ import { request } from "node:http";
5
+ import path from "node:path";
6
+ import { chromium } from "playwright-core";
7
+ import { MoodleError } from "../core/errors.js";
8
+ import { followPages } from "../core/session.js";
9
+ import { saveSessionCookies } from "./cookies.js";
10
+ import { NoDisplayError } from "./manager.js";
11
+ /** The file a background browser leaves in its profile: where it is and its token, readable only by the person. */
12
+ export const BACKGROUND_FILE = ".browser.json";
13
+ const NAVIGATION_TIMEOUT_MS = 60_000;
14
+ const alive = (pid) => {
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch (error) {
20
+ // EPERM: it's there, someone else's.
21
+ return error.code === "EPERM";
22
+ }
23
+ };
24
+ /** Asks the background browser how it is: `undefined` when nothing answers on its port. */
25
+ function status(endpoint, token) {
26
+ const url = new URL("/status", endpoint.replace(/^ws/, "http"));
27
+ return new Promise((resolve, reject) => {
28
+ const req = request(url, { headers: { authorization: `Bearer ${token}` }, timeout: 5000 }, (response) => {
29
+ let body = "";
30
+ response.setEncoding("utf8");
31
+ response.on("data", (chunk) => (body += chunk));
32
+ response.on("end", () => (response.statusCode === 200 ? resolve(JSON.parse(body)) : resolve(undefined)));
33
+ });
34
+ req.on("timeout", () => req.destroy(new MoodleError("The background browser doesn't answer.", "background-browser")));
35
+ req.on("error", (error) => (error.code === "ECONNREFUSED" ? resolve(undefined) : reject(error)));
36
+ req.end();
37
+ });
38
+ }
39
+ /**
40
+ * The browser another process keeps open on `profileDir` (the `moodle open` command's), or `undefined`
41
+ * when there's none. A `.browser.json` left by a process that's gone is removed.
42
+ */
43
+ export async function backgroundBrowser(profileDir) {
44
+ const file = path.join(profileDir, BACKGROUND_FILE);
45
+ let found;
46
+ try {
47
+ found = JSON.parse(readFileSync(file, "utf8"));
48
+ }
49
+ catch {
50
+ return undefined;
51
+ }
52
+ if (typeof found.pid === "number" && alive(found.pid) && (await status(found.endpoint, found.token)))
53
+ return found;
54
+ rmSync(file, { force: true });
55
+ return undefined;
56
+ }
57
+ /**
58
+ * Asks the background browser on `profileDir` to save the session and close, and waits until it has;
59
+ * `false` when there's none.
60
+ */
61
+ export async function closeBackgroundBrowser(profileDir, timeoutMs = 20_000) {
62
+ const found = await backgroundBrowser(profileDir);
63
+ if (!found)
64
+ return false;
65
+ await new Promise((resolve, reject) => {
66
+ const req = request(new URL("/close", found.endpoint.replace(/^ws/, "http")), { method: "POST", headers: { authorization: `Bearer ${found.token}` } }, (response) => {
67
+ response.resume();
68
+ response.on("end", resolve);
69
+ });
70
+ req.on("error", reject);
71
+ req.end();
72
+ });
73
+ const until = Date.now() + timeoutMs;
74
+ // The process removes its file once the browser is closed and the session saved.
75
+ const file = path.join(profileDir, BACKGROUND_FILE);
76
+ while (existsSync(file) && alive(found.pid) && Date.now() < until)
77
+ await new Promise((resolve) => setTimeout(resolve, 100));
78
+ rmSync(file, { force: true });
79
+ return true;
80
+ }
81
+ /**
82
+ * A browser manager on a browser another process keeps open (`backgroundBrowser`), for a program to
83
+ * share it: it connects on first use, works in the browser's own context, and `close()` only
84
+ * disconnects, leaving the browser open. With a window, the pages functions open are followed in its
85
+ * first tab (ADR-007); it can't add or remove a window from a browser it didn't launch.
86
+ */
87
+ export function connectBrowserManager(options) {
88
+ const headed = options.headed ?? false;
89
+ let browser;
90
+ let context;
91
+ let following = headed;
92
+ let connecting;
93
+ async function connect() {
94
+ if (context && browser?.isConnected())
95
+ return context;
96
+ connecting ??= (async () => {
97
+ try {
98
+ browser = await chromium.connectOverCDP(options.endpoint, { headers: { authorization: `Bearer ${options.token}` } });
99
+ }
100
+ catch (error) {
101
+ throw new MoodleError(`Can't connect to the browser at ${options.endpoint}: ${error.message.split("\n")[0]}`, "background-browser");
102
+ }
103
+ const opened = browser.contexts()[0];
104
+ if (!opened)
105
+ throw new MoodleError("The browser connected to has no context to work in.", "background-browser");
106
+ opened.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT_MS);
107
+ followPages(opened, (url) => manager.show(url));
108
+ browser.on("disconnected", () => {
109
+ browser = undefined;
110
+ context = undefined;
111
+ });
112
+ context = opened;
113
+ return opened;
114
+ })().finally(() => (connecting = undefined));
115
+ return connecting;
116
+ }
117
+ /** The window's tab: the first one the browser has, else a new one. */
118
+ async function workTab(current) {
119
+ return current.pages().find((p) => !p.isClosed()) ?? (await current.newPage());
120
+ }
121
+ const manager = {
122
+ async context({ headed: wantHeaded = false } = {}) {
123
+ if (wantHeaded && !headed)
124
+ throw new NoDisplayError("The browser connected to was opened without a window, so it can't show one (to log in, say).");
125
+ return connect();
126
+ },
127
+ openChannel: () => (context ? options.channel : undefined),
128
+ chromiumDownload: () => undefined,
129
+ visible: () => headed && following,
130
+ async setVisible(visible) {
131
+ if (visible && !headed)
132
+ throw new NoDisplayError("The browser connected to was opened without a window: it can't show one now.");
133
+ following = visible;
134
+ },
135
+ async show(url) {
136
+ if (!following || !headed || !context)
137
+ return;
138
+ try {
139
+ const tab = await workTab(context);
140
+ if (tab.url() !== url)
141
+ await tab.goto(url, { waitUntil: "domcontentloaded" });
142
+ await tab.bringToFront();
143
+ }
144
+ catch {
145
+ // Following is a courtesy: a page that doesn't load never changes a function's result.
146
+ }
147
+ },
148
+ tab: async () => workTab(await connect()),
149
+ async saveSession() {
150
+ if (context && options.profileDir)
151
+ await saveSessionCookies(context, options.profileDir);
152
+ },
153
+ async close() {
154
+ const current = browser;
155
+ await manager.saveSession().catch(() => undefined);
156
+ browser = undefined;
157
+ context = undefined;
158
+ // Disconnects: the browser and its window stay open for the next one.
159
+ await current?.close().catch(() => undefined);
160
+ },
161
+ };
162
+ return manager;
163
+ }
164
+ //# sourceMappingURL=background.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"background.js","sourceRoot":"","sources":["../../src/browser/background.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import { type Channel } from "../options.js";
2
+ export interface HostOptions {
3
+ /** The browser profile: the person's session (ADR-002). */
4
+ profileDir: string;
5
+ /** A window, or headless. */
6
+ headed?: boolean;
7
+ /** One browser only; else Edge, Chrome, then Chromium (downloaded if missing) as `createBrowserManager` tries them. */
8
+ channel?: Channel;
9
+ }
10
+ /** A browser hosted for other processes, and how to reach it. */
11
+ export interface BrowserHost {
12
+ /** The WebSocket to connect to (`connectBrowserManager`), on 127.0.0.1. */
13
+ endpoint: string;
14
+ /** What a client sends as `Authorization: Bearer <token>`: nothing without it reaches the browser. */
15
+ token: string;
16
+ /** The browser's process. */
17
+ pid: number;
18
+ channel: Channel;
19
+ headed: boolean;
20
+ /** Settles once the browser is closed: asked to, or the person closed its window. */
21
+ closed: Promise<void>;
22
+ /** Saves the session cookies and closes the browser. */
23
+ close(): Promise<void>;
24
+ }
25
+ /**
26
+ * Launches a browser on `profileDir` and offers it to other processes (`connectBrowserManager`): one
27
+ * at a time, each with the token, on 127.0.0.1 only. Restores the session cookies (ADR-003) and saves
28
+ * them after every client and when it closes. Throws `NoDisplayError` for a window where there's no screen.
29
+ */
30
+ export declare function hostBrowser(options: HostOptions): Promise<BrowserHost>;
@@ -0,0 +1,373 @@
1
+ // A browser left open for other processes (ADR-014): launched with its debugging connection on a pipe
2
+ // only this process holds, and offered to one client at a time through a WebSocket on 127.0.0.1 that
3
+ // requires a random token. Chromium's own debugging port has no such check: anything on the machine
4
+ // that reached it would drive the browser, logged in to Moodle as the person.
5
+ import { spawn } from "node:child_process";
6
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
7
+ import { readFile, writeFile } from "node:fs/promises";
8
+ import { createServer } from "node:http";
9
+ import path from "node:path";
10
+ import { chromium } from "playwright-core";
11
+ import { MoodleError } from "../core/errors.js";
12
+ import { SYSTEM_CHANNELS } from "../options.js";
13
+ import { installChromium } from "./chromium.js";
14
+ import { SESSION_COOKIES_FILE } from "./cookies.js";
15
+ import { BrowserNotFoundError, NoDisplayError, canShowWindow } from "./manager.js";
16
+ // Playwright's message when a channel isn't installed (as in manager.ts).
17
+ const NOT_INSTALLED = /distribution '[^']+' is not (found|supported)|Executable doesn't exist/i;
18
+ // A CDP message as large as a full-page screenshot fits; anything larger is refused, not buffered.
19
+ const MAX_MESSAGE = 256 * 1024 * 1024;
20
+ const WS_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
21
+ /**
22
+ * The executable and switches Playwright launches a channel with, from a browser server it starts and
23
+ * closes at once (with `--no-startup-window`, no window shows): the same switches as every other launch,
24
+ * without reaching into Playwright's internals.
25
+ */
26
+ async function launchCommand(channel, headed) {
27
+ const server = await chromium.launchServer({ channel, headless: !headed });
28
+ try {
29
+ const process = server.process();
30
+ const args = process.spawnargs.slice(1).filter((arg) => arg.startsWith("-") && !/^--(user-data-dir|remote-debugging-pipe|remote-debugging-port|no-startup-window)\b/.test(arg));
31
+ return { executable: process.spawnfile, args };
32
+ }
33
+ finally {
34
+ await server.close();
35
+ }
36
+ }
37
+ async function findBrowser(options, headed) {
38
+ const channels = options.channel ? [options.channel] : [...SYSTEM_CHANNELS, "chromium"];
39
+ for (const channel of channels) {
40
+ try {
41
+ return { channel, ...(await launchCommand(channel, headed)) };
42
+ }
43
+ catch (error) {
44
+ if (!NOT_INSTALLED.test(error.message ?? ""))
45
+ throw error;
46
+ if (channel !== "chromium")
47
+ continue;
48
+ // Opening a browser to keep is worth waiting for the download (ADR-001).
49
+ await installChromium();
50
+ return { channel, ...(await launchCommand(channel, headed)) };
51
+ }
52
+ }
53
+ throw new BrowserNotFoundError(`No browser found (tried ${channels.join(", ")}).`);
54
+ }
55
+ /** Chrome's debugging protocol on the pipe it was given: JSON messages ending in a NUL byte. */
56
+ class CdpPipe {
57
+ out;
58
+ chunks = [];
59
+ constructor(out, input, onMessage) {
60
+ this.out = out;
61
+ input.on("data", (data) => {
62
+ let start = 0;
63
+ for (let end = data.indexOf(0); end >= 0; end = data.indexOf(0, start)) {
64
+ this.chunks.push(data.subarray(start, end));
65
+ onMessage(Buffer.concat(this.chunks).toString("utf8"));
66
+ this.chunks = [];
67
+ start = end + 1;
68
+ }
69
+ if (start < data.length)
70
+ this.chunks.push(data.subarray(start));
71
+ });
72
+ input.on("error", () => undefined);
73
+ out.on("error", () => undefined);
74
+ }
75
+ send(text) {
76
+ this.out.write(`${text}\0`);
77
+ }
78
+ }
79
+ /** A WebSocket accepted on `socket` (RFC 6455, text messages, no extensions): just what CDP needs. */
80
+ function acceptWebSocket(request, socket, head, onMessage, onClose) {
81
+ const key = request.headers["sec-websocket-key"];
82
+ const accept = createHash("sha1").update(`${key}${WS_GUID}`).digest("base64");
83
+ socket.setNoDelay(true);
84
+ socket.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`);
85
+ let buffer = head;
86
+ let fragments = [];
87
+ let fragmentsLength = 0;
88
+ let closed = false;
89
+ const frame = (opcode, payload) => {
90
+ if (closed || socket.destroyed)
91
+ return;
92
+ const length = payload.length;
93
+ const header = length < 126 ? Buffer.from([0x80 | opcode, length]) : length < 65536 ? Buffer.alloc(4) : Buffer.alloc(10);
94
+ if (length >= 126 && length < 65536) {
95
+ header[0] = 0x80 | opcode;
96
+ header[1] = 126;
97
+ header.writeUInt16BE(length, 2);
98
+ }
99
+ else if (length >= 65536) {
100
+ header[0] = 0x80 | opcode;
101
+ header[1] = 127;
102
+ header.writeBigUInt64BE(BigInt(length), 2);
103
+ }
104
+ socket.write(Buffer.concat([header, payload]));
105
+ };
106
+ const close = (code = 1000) => {
107
+ if (closed)
108
+ return;
109
+ const payload = Buffer.alloc(2);
110
+ payload.writeUInt16BE(code);
111
+ frame(0x8, payload);
112
+ closed = true;
113
+ socket.end();
114
+ };
115
+ const parse = () => {
116
+ while (buffer.length >= 2) {
117
+ const fin = (buffer[0] & 0x80) !== 0;
118
+ const opcode = buffer[0] & 0x0f;
119
+ // A client masks every frame (RFC 6455 5.1).
120
+ if ((buffer[1] & 0x80) === 0)
121
+ return close(1002);
122
+ let length = buffer[1] & 0x7f;
123
+ let offset = 2;
124
+ if (length === 126) {
125
+ if (buffer.length < 4)
126
+ return;
127
+ length = buffer.readUInt16BE(2);
128
+ offset = 4;
129
+ }
130
+ else if (length === 127) {
131
+ if (buffer.length < 10)
132
+ return;
133
+ const big = buffer.readBigUInt64BE(2);
134
+ if (big > BigInt(MAX_MESSAGE))
135
+ return close(1009);
136
+ length = Number(big);
137
+ offset = 10;
138
+ }
139
+ if (length > MAX_MESSAGE || fragmentsLength + length > MAX_MESSAGE)
140
+ return close(1009);
141
+ if (buffer.length < offset + 4 + length)
142
+ return;
143
+ const mask = buffer.subarray(offset, offset + 4);
144
+ const payload = Buffer.from(buffer.subarray(offset + 4, offset + 4 + length));
145
+ for (let i = 0; i < payload.length; i++)
146
+ payload[i] ^= mask[i & 3];
147
+ buffer = buffer.subarray(offset + 4 + length);
148
+ if (opcode === 0x0 || opcode === 0x1 || opcode === 0x2) {
149
+ fragments.push(payload);
150
+ fragmentsLength += payload.length;
151
+ if (fin) {
152
+ const message = Buffer.concat(fragments).toString("utf8");
153
+ fragments = [];
154
+ fragmentsLength = 0;
155
+ onMessage(message);
156
+ }
157
+ }
158
+ else if (opcode === 0x8)
159
+ return close();
160
+ else if (opcode === 0x9)
161
+ frame(0xa, payload);
162
+ else if (opcode !== 0xa)
163
+ return close(1002);
164
+ }
165
+ };
166
+ socket.on("data", (data) => {
167
+ buffer = buffer.length > 0 ? Buffer.concat([buffer, data]) : data;
168
+ parse();
169
+ });
170
+ socket.on("close", onClose);
171
+ socket.on("error", () => socket.destroy());
172
+ if (buffer.length > 0)
173
+ parse();
174
+ return { send: (text) => frame(0x1, Buffer.from(text, "utf8")), close };
175
+ }
176
+ /** Whether the token in `Authorization: Bearer …` is `token`, compared in constant time. */
177
+ function hasToken(request, token) {
178
+ const given = /^Bearer (\S+)$/.exec(request.headers.authorization ?? "")?.[1];
179
+ if (!given)
180
+ return false;
181
+ const a = Buffer.from(given);
182
+ const b = Buffer.from(token);
183
+ return a.length === b.length && timingSafeEqual(a, b);
184
+ }
185
+ /**
186
+ * Launches a browser on `profileDir` and offers it to other processes (`connectBrowserManager`): one
187
+ * at a time, each with the token, on 127.0.0.1 only. Restores the session cookies (ADR-003) and saves
188
+ * them after every client and when it closes. Throws `NoDisplayError` for a window where there's no screen.
189
+ */
190
+ export async function hostBrowser(options) {
191
+ const headed = options.headed ?? false;
192
+ if (headed && !canShowWindow())
193
+ throw new NoDisplayError("There's no screen to show a browser window here (no DISPLAY or WAYLAND_DISPLAY).");
194
+ const { channel, executable, args } = await findBrowser(options, headed);
195
+ // Chrome reads commands from fd 3 and writes to fd 4 (--remote-debugging-pipe); its own output is of no use here.
196
+ const child = spawn(executable, [...args, `--user-data-dir=${options.profileDir}`, "--remote-debugging-pipe", "about:blank"], {
197
+ stdio: ["ignore", "ignore", "ignore", "pipe", "pipe"],
198
+ });
199
+ const exited = new Promise((resolve) => child.once("exit", () => resolve()));
200
+ const killOnExit = () => child.kill();
201
+ process.on("exit", killOnExit);
202
+ const pending = new Map();
203
+ let nextId = 0;
204
+ let client;
205
+ // The sessions a client attached to targets: detached when it leaves, so the next one starts clean.
206
+ const sessions = new Set();
207
+ const pipe = new CdpPipe(child.stdio[3], child.stdio[4], (text) => {
208
+ let message;
209
+ try {
210
+ message = JSON.parse(text);
211
+ }
212
+ catch {
213
+ return;
214
+ }
215
+ if (typeof message.id === "number") {
216
+ const waiting = pending.get(message.id);
217
+ pending.delete(message.id);
218
+ if (!waiting)
219
+ return;
220
+ if (waiting.own) {
221
+ if (message.error)
222
+ waiting.reject(new MoodleError(message.error.message, "background-browser"));
223
+ else
224
+ waiting.resolve(message.result);
225
+ }
226
+ else if (waiting.client === client)
227
+ client.send(JSON.stringify({ ...message, id: waiting.id }));
228
+ return;
229
+ }
230
+ if (message.method === "Target.attachedToTarget" && message.params?.sessionId)
231
+ sessions.add(message.params.sessionId);
232
+ if (message.method === "Target.detachedFromTarget" && message.params?.sessionId)
233
+ sessions.delete(message.params.sessionId);
234
+ client?.send(text);
235
+ });
236
+ const call = (method, params = {}) => new Promise((resolve, reject) => {
237
+ const id = ++nextId;
238
+ pending.set(id, { own: true, resolve: resolve, reject });
239
+ pipe.send(JSON.stringify({ id, method, params }));
240
+ });
241
+ const timeout = (promise, ms, what) => Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(new MoodleError(`The browser didn't ${what} in ${ms / 1000} s.`, "background-browser")), ms).unref())]);
242
+ async function saveCookies() {
243
+ const { cookies } = await call("Storage.getCookies");
244
+ const session = cookies.filter((c) => c.session).map((c) => ({ name: c.name, value: c.value, domain: c.domain, path: c.path, expires: -1, httpOnly: c.httpOnly, secure: c.secure, sameSite: c.sameSite ?? "Lax" }));
245
+ await writeFile(path.join(options.profileDir, SESSION_COOKIES_FILE), JSON.stringify(session), { mode: 0o600 });
246
+ }
247
+ async function restoreCookies() {
248
+ let saved;
249
+ try {
250
+ saved = JSON.parse(await readFile(path.join(options.profileDir, SESSION_COOKIES_FILE), "utf8"));
251
+ }
252
+ catch {
253
+ return;
254
+ }
255
+ if (!Array.isArray(saved) || saved.length === 0)
256
+ return;
257
+ // Cookies the browser kept itself are newer than the file's.
258
+ const { cookies } = await call("Storage.getCookies");
259
+ const kept = new Set(cookies.map((c) => `${c.name}|${c.domain}|${c.path}`));
260
+ const missing = saved.filter((c) => !kept.has(`${c.name}|${c.domain}|${c.path}`)).map((c) => ({ name: c.name, value: c.value, domain: c.domain, path: c.path, httpOnly: c.httpOnly, secure: c.secure, sameSite: c.sameSite }));
261
+ if (missing.length > 0)
262
+ await call("Storage.setCookies", { cookies: missing }).catch(() => undefined);
263
+ }
264
+ /** What a client left behind: its sessions attached, auto-attaching, its downloads folder. */
265
+ function cleanUp() {
266
+ for (const sessionId of sessions)
267
+ call("Target.detachFromTarget", { sessionId }).catch(() => undefined);
268
+ sessions.clear();
269
+ call("Target.setAutoAttach", { autoAttach: false, waitForDebuggerOnStart: false, flatten: true }).catch(() => undefined);
270
+ call("Target.setDiscoverTargets", { discover: false }).catch(() => undefined);
271
+ call("Browser.setDownloadBehavior", { behavior: "default" }).catch(() => undefined);
272
+ saveCookies().catch(() => undefined);
273
+ }
274
+ try {
275
+ await timeout(Promise.race([call("Browser.getVersion"), exited.then(() => Promise.reject(new MoodleError("The browser closed as it started: is another browser using this profile?", "background-browser")))]), 30_000, "start");
276
+ await restoreCookies();
277
+ }
278
+ catch (error) {
279
+ child.kill();
280
+ process.off("exit", killOnExit);
281
+ throw error;
282
+ }
283
+ const token = randomBytes(32).toString("base64url");
284
+ let closing;
285
+ const server = createServer();
286
+ // Only this machine, and only a client that isn't a web page: a page always sends Origin, and a
287
+ // name other than 127.0.0.1 in Host would be DNS rebinding.
288
+ const allowed = (request) => request.headers.origin === undefined && request.headers.host === host && hasToken(request, token);
289
+ server.on("request", (request, response) => {
290
+ if (!allowed(request))
291
+ return void response.writeHead(401).end();
292
+ if (request.method === "GET" && request.url === "/status") {
293
+ response.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ pid: child.pid, channel, headed, busy: client !== undefined }));
294
+ }
295
+ else if (request.method === "POST" && request.url === "/close") {
296
+ response.writeHead(200).end();
297
+ void browserHost.close();
298
+ }
299
+ else
300
+ response.writeHead(404).end();
301
+ });
302
+ server.on("upgrade", (request, socket, head) => {
303
+ const refuse = (status) => socket.end(`HTTP/1.1 ${status}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
304
+ if (!allowed(request))
305
+ return refuse("401 Unauthorized");
306
+ if (request.url !== "/devtools/browser" || request.headers.upgrade?.toLowerCase() !== "websocket" || !request.headers["sec-websocket-key"])
307
+ return refuse("400 Bad Request");
308
+ if (client || closing)
309
+ return refuse("409 Conflict");
310
+ const accepted = acceptWebSocket(request, socket, head, (text) => {
311
+ let message;
312
+ try {
313
+ message = JSON.parse(text);
314
+ }
315
+ catch {
316
+ return;
317
+ }
318
+ if (typeof message.id !== "number")
319
+ return;
320
+ // The browser outlives its clients: one closing it only disconnects (the host closes it, asked to).
321
+ if (message.method === "Browser.close") {
322
+ accepted.send(JSON.stringify({ id: message.id, result: {} }));
323
+ accepted.close();
324
+ return;
325
+ }
326
+ // Every client numbers its commands from 1: the browser sees ids of this process's own.
327
+ const id = ++nextId;
328
+ pending.set(id, { own: false, client: accepted, id: message.id });
329
+ pipe.send(JSON.stringify({ ...message, id }));
330
+ }, () => {
331
+ if (client !== accepted)
332
+ return;
333
+ client = undefined;
334
+ if (!closing)
335
+ cleanUp();
336
+ });
337
+ client = accepted;
338
+ });
339
+ await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
340
+ const port = server.address().port;
341
+ const host = `127.0.0.1:${port}`;
342
+ const closed = exited.then(() => {
343
+ process.off("exit", killOnExit);
344
+ client?.close(1001);
345
+ client = undefined;
346
+ for (const waiting of pending.values())
347
+ if (waiting.own)
348
+ waiting.reject(new MoodleError("The browser closed.", "background-browser"));
349
+ pending.clear();
350
+ server.close();
351
+ server.closeAllConnections();
352
+ });
353
+ const browserHost = {
354
+ endpoint: `ws://${host}/devtools/browser`,
355
+ token,
356
+ pid: child.pid,
357
+ channel,
358
+ headed,
359
+ closed,
360
+ close() {
361
+ closing ??= (async () => {
362
+ client?.close(1001);
363
+ await timeout(saveCookies(), 10_000, "give its cookies").catch(() => undefined);
364
+ await timeout(call("Browser.close"), 10_000, "close").catch(() => child.kill());
365
+ await timeout(exited, 10_000, "exit").catch(() => child.kill("SIGKILL"));
366
+ await closed;
367
+ })();
368
+ return closing;
369
+ },
370
+ };
371
+ return browserHost;
372
+ }
373
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/browser/host.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { type InstallChromium } from "./chromium.js";
3
3
  import { type BrowserOptions, type Channel } from "../options.js";
4
4
  import { MoodleError } from "../core/errors.js";
5
5
  type LaunchPersistentContext = typeof chromium.launchPersistentContext;
6
+ /** What `BrowserManager.context()` is asked for. */
6
7
  export interface ContextOptions {
7
8
  /** A visible window, whatever the options say (logging in by hand). */
8
9
  headed?: boolean;
@@ -53,5 +54,13 @@ export declare class NoDisplayError extends MoodleError {
53
54
  * server) the driver refuses rather than trying, so it never reaches a display it wasn't given.
54
55
  */
55
56
  export declare function canShowWindow(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
57
+ /**
58
+ * A browser manager that launches the browser itself, in the persistent profile of `config`
59
+ * (ADR-002), on first use: nothing opens until a function needs the browser. It tries the
60
+ * installed browsers and, if allowed, downloads Chromium in the background (ADR-001). Its
61
+ * `context()` throws `BrowserNotFoundError` when no browser can be launched,
62
+ * `BrowserDownloadingError` while Chromium downloads, and `NoDisplayError` for a window without a
63
+ * screen. The other parameters are for tests.
64
+ */
56
65
  export declare function createBrowserManager(config: BrowserOptions, launch?: LaunchPersistentContext, install?: InstallChromium, windowsPossible?: () => boolean): BrowserManager;
57
66
  export {};
@@ -38,6 +38,14 @@ export function canShowWindow(env = process.env, platform = process.platform) {
38
38
  const NOT_INSTALLED = /distribution '[^']+' is not (found|supported)|Executable doesn't exist/i;
39
39
  // Moodle pages can take long, above all the first ones after the site starts.
40
40
  const NAVIGATION_TIMEOUT_MS = 60_000;
41
+ /**
42
+ * A browser manager that launches the browser itself, in the persistent profile of `config`
43
+ * (ADR-002), on first use: nothing opens until a function needs the browser. It tries the
44
+ * installed browsers and, if allowed, downloads Chromium in the background (ADR-001). Its
45
+ * `context()` throws `BrowserNotFoundError` when no browser can be launched,
46
+ * `BrowserDownloadingError` while Chromium downloads, and `NoDisplayError` for a window without a
47
+ * screen. The other parameters are for tests.
48
+ */
41
49
  export function createBrowserManager(config, launch = chromium.launchPersistentContext.bind(chromium), install = installChromium, windowsPossible = canShowWindow) {
42
50
  let open;
43
51
  // What `setVisible` asked for, over the options; following goes with it.