@falkenslab/moodle-driver 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +131 -2
- package/dist/areas/activities/build.d.ts +98 -0
- package/dist/areas/activities/build.js +248 -0
- package/dist/areas/activities/build.js.map +1 -0
- package/dist/areas/activities/content.d.ts +53 -0
- package/dist/areas/activities/content.js +113 -0
- package/dist/areas/activities/content.js.map +1 -0
- package/dist/areas/assignments/grader.d.ts +68 -0
- package/dist/areas/assignments/grader.js +169 -0
- package/dist/areas/assignments/grader.js.map +1 -0
- package/dist/areas/assignments/grading.d.ts +86 -0
- package/dist/areas/assignments/grading.js +162 -0
- package/dist/areas/assignments/grading.js.map +1 -0
- package/dist/areas/assignments/student.d.ts +57 -0
- package/dist/areas/assignments/student.js +115 -0
- package/dist/areas/assignments/student.js.map +1 -0
- package/dist/areas/calendar/calendar.d.ts +43 -0
- package/dist/areas/calendar/calendar.js +84 -0
- package/dist/areas/calendar/calendar.js.map +1 -0
- package/dist/areas/completion/completion.d.ts +50 -0
- package/dist/areas/completion/completion.js +112 -0
- package/dist/areas/completion/completion.js.map +1 -0
- package/dist/areas/courses/list.d.ts +59 -0
- package/dist/areas/courses/list.js +74 -0
- package/dist/areas/courses/list.js.map +1 -0
- package/dist/areas/courses/overview.d.ts +83 -0
- package/dist/areas/courses/overview.js +137 -0
- package/dist/areas/courses/overview.js.map +1 -0
- package/dist/areas/courses/state.d.ts +51 -0
- package/dist/areas/courses/state.js +45 -0
- package/dist/areas/courses/state.js.map +1 -0
- package/dist/areas/courses/structure.d.ts +89 -0
- package/dist/areas/courses/structure.js +104 -0
- package/dist/areas/courses/structure.js.map +1 -0
- package/dist/areas/forums/discussions.d.ts +57 -0
- package/dist/areas/forums/discussions.js +119 -0
- package/dist/areas/forums/discussions.js.map +1 -0
- package/dist/areas/forums/posts.d.ts +83 -0
- package/dist/areas/forums/posts.js +114 -0
- package/dist/areas/forums/posts.js.map +1 -0
- package/dist/areas/grades/gradebook.d.ts +104 -0
- package/dist/areas/grades/gradebook.js +250 -0
- package/dist/areas/grades/gradebook.js.map +1 -0
- package/dist/areas/grades/user-report.d.ts +26 -0
- package/dist/areas/grades/user-report.js +71 -0
- package/dist/areas/grades/user-report.js.map +1 -0
- package/dist/areas/h5p/h5p.d.ts +33 -0
- package/dist/areas/h5p/h5p.js +104 -0
- package/dist/areas/h5p/h5p.js.map +1 -0
- package/dist/areas/lessons/lessons.d.ts +60 -0
- package/dist/areas/lessons/lessons.js +145 -0
- package/dist/areas/lessons/lessons.js.map +1 -0
- package/dist/areas/messages/messages.d.ts +68 -0
- package/dist/areas/messages/messages.js +84 -0
- package/dist/areas/messages/messages.js.map +1 -0
- package/dist/areas/participants/participants.d.ts +57 -0
- package/dist/areas/participants/participants.js +135 -0
- package/dist/areas/participants/participants.js.map +1 -0
- package/dist/areas/questions/banks.d.ts +83 -0
- package/dist/areas/questions/banks.js +239 -0
- package/dist/areas/questions/banks.js.map +1 -0
- package/dist/areas/quizzes/attempts.d.ts +123 -0
- package/dist/areas/quizzes/attempts.js +300 -0
- package/dist/areas/quizzes/attempts.js.map +1 -0
- package/dist/areas/quizzes/results.d.ts +36 -0
- package/dist/areas/quizzes/results.js +93 -0
- package/dist/areas/quizzes/results.js.map +1 -0
- package/dist/areas/quizzes/structure.d.ts +42 -0
- package/dist/areas/quizzes/structure.js +100 -0
- package/dist/areas/quizzes/structure.js.map +1 -0
- package/dist/areas/scorm/scorm.d.ts +60 -0
- package/dist/areas/scorm/scorm.js +171 -0
- package/dist/areas/scorm/scorm.js.map +1 -0
- package/dist/areas/site/info.d.ts +25 -0
- package/dist/areas/site/info.js +45 -0
- package/dist/areas/site/info.js.map +1 -0
- package/dist/areas/site/users.d.ts +56 -0
- package/dist/areas/site/users.js +89 -0
- package/dist/areas/site/users.js.map +1 -0
- package/dist/areas/surveys/choice.d.ts +38 -0
- package/dist/areas/surveys/choice.js +105 -0
- package/dist/areas/surveys/choice.js.map +1 -0
- package/dist/areas/surveys/surveys.d.ts +51 -0
- package/dist/areas/surveys/surveys.js +201 -0
- package/dist/areas/surveys/surveys.js.map +1 -0
- package/dist/browser/chromium.d.ts +15 -0
- package/dist/browser/chromium.js +53 -0
- package/dist/browser/chromium.js.map +1 -0
- package/dist/browser/cookies.d.ts +11 -0
- package/dist/browser/cookies.js +31 -0
- package/dist/browser/cookies.js.map +1 -0
- package/dist/browser/manager.d.ts +57 -0
- package/dist/browser/manager.js +177 -0
- package/dist/browser/manager.js.map +1 -0
- package/dist/browser.d.ts +53 -0
- package/dist/browser.js +167 -0
- package/dist/browser.js.map +1 -0
- package/dist/categories.d.ts +28 -0
- package/dist/categories.js +225 -0
- package/dist/categories.js.map +1 -0
- package/dist/chromiumInstaller.d.ts +15 -0
- package/dist/chromiumInstaller.js +52 -0
- package/dist/chromiumInstaller.js.map +1 -0
- package/dist/cli/args.d.ts +33 -0
- package/dist/cli/args.js +194 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.js +2217 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/config.d.ts +46 -0
- package/dist/cli/config.js +107 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +19 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/output.d.ts +4 -0
- package/dist/cli/output.js +71 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/run.d.ts +15 -0
- package/dist/cli/run.js +194 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spec.d.ts +42 -0
- package/dist/cli/spec.js +4 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/client.d.ts +224 -0
- package/dist/client.js +182 -0
- package/dist/client.js.map +1 -0
- package/dist/core/ajax.d.ts +25 -0
- package/dist/core/ajax.js +85 -0
- package/dist/core/ajax.js.map +1 -0
- package/dist/core/editor.d.ts +18 -0
- package/dist/core/editor.js +51 -0
- package/dist/core/editor.js.map +1 -0
- package/dist/core/errors.d.ts +12 -0
- package/dist/core/errors.js +10 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/files.d.ts +16 -0
- package/dist/core/files.js +58 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/forms.d.ts +45 -0
- package/dist/core/forms.js +200 -0
- package/dist/core/forms.js.map +1 -0
- package/dist/core/http.d.ts +18 -0
- package/dist/core/http.js +22 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/inPage.d.ts +8 -0
- package/dist/core/inPage.js +10 -0
- package/dist/core/inPage.js.map +1 -0
- package/dist/core/login.d.ts +40 -0
- package/dist/core/login.js +83 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/pace.d.ts +4 -0
- package/dist/core/pace.js +21 -0
- package/dist/core/pace.js.map +1 -0
- package/dist/core/session.d.ts +55 -0
- package/dist/core/session.js +157 -0
- package/dist/core/session.js.map +1 -0
- package/dist/core/site.d.ts +8 -0
- package/dist/core/site.js +36 -0
- package/dist/core/site.js.map +1 -0
- package/dist/core/text.d.ts +21 -0
- package/dist/core/text.js +113 -0
- package/dist/core/text.js.map +1 -0
- package/dist/home.d.ts +19 -0
- package/dist/home.js +54 -0
- package/dist/home.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +15 -0
- package/dist/internals.js.map +1 -0
- package/dist/moodle/activities.d.ts +94 -0
- package/dist/moodle/activities.js +240 -0
- package/dist/moodle/activities.js.map +1 -0
- package/dist/moodle/ajax.d.ts +22 -0
- package/dist/moodle/ajax.js +91 -0
- package/dist/moodle/ajax.js.map +1 -0
- package/dist/moodle/assign.d.ts +65 -0
- package/dist/moodle/assign.js +162 -0
- package/dist/moodle/assign.js.map +1 -0
- package/dist/moodle/calendar.d.ts +41 -0
- package/dist/moodle/calendar.js +80 -0
- package/dist/moodle/calendar.js.map +1 -0
- package/dist/moodle/choice.d.ts +33 -0
- package/dist/moodle/choice.js +100 -0
- package/dist/moodle/choice.js.map +1 -0
- package/dist/moodle/completion.d.ts +48 -0
- package/dist/moodle/completion.js +108 -0
- package/dist/moodle/completion.js.map +1 -0
- package/dist/moodle/content.d.ts +43 -0
- package/dist/moodle/content.js +105 -0
- package/dist/moodle/content.js.map +1 -0
- package/dist/moodle/course.d.ts +51 -0
- package/dist/moodle/course.js +45 -0
- package/dist/moodle/course.js.map +1 -0
- package/dist/moodle/courses.d.ts +57 -0
- package/dist/moodle/courses.js +70 -0
- package/dist/moodle/courses.js.map +1 -0
- package/dist/moodle/editor.d.ts +18 -0
- package/dist/moodle/editor.js +50 -0
- package/dist/moodle/editor.js.map +1 -0
- package/dist/moodle/files.d.ts +16 -0
- package/dist/moodle/files.js +57 -0
- package/dist/moodle/files.js.map +1 -0
- package/dist/moodle/forms.d.ts +44 -0
- package/dist/moodle/forms.js +199 -0
- package/dist/moodle/forms.js.map +1 -0
- package/dist/moodle/forum.d.ts +55 -0
- package/dist/moodle/forum.js +115 -0
- package/dist/moodle/forum.js.map +1 -0
- package/dist/moodle/forumPosts.d.ts +83 -0
- package/dist/moodle/forumPosts.js +113 -0
- package/dist/moodle/forumPosts.js.map +1 -0
- package/dist/moodle/gradebook.d.ts +102 -0
- package/dist/moodle/gradebook.js +246 -0
- package/dist/moodle/gradebook.js.map +1 -0
- package/dist/moodle/grades.d.ts +24 -0
- package/dist/moodle/grades.js +67 -0
- package/dist/moodle/grades.js.map +1 -0
- package/dist/moodle/grading.d.ts +92 -0
- package/dist/moodle/grading.js +161 -0
- package/dist/moodle/grading.js.map +1 -0
- package/dist/moodle/h5p.d.ts +31 -0
- package/dist/moodle/h5p.js +100 -0
- package/dist/moodle/h5p.js.map +1 -0
- package/dist/moodle/inPage.d.ts +8 -0
- package/dist/moodle/inPage.js +10 -0
- package/dist/moodle/inPage.js.map +1 -0
- package/dist/moodle/lesson.d.ts +57 -0
- package/dist/moodle/lesson.js +140 -0
- package/dist/moodle/lesson.js.map +1 -0
- package/dist/moodle/login.d.ts +37 -0
- package/dist/moodle/login.js +77 -0
- package/dist/moodle/login.js.map +1 -0
- package/dist/moodle/messages.d.ts +66 -0
- package/dist/moodle/messages.js +80 -0
- package/dist/moodle/messages.js.map +1 -0
- package/dist/moodle/overview.d.ts +83 -0
- package/dist/moodle/overview.js +137 -0
- package/dist/moodle/overview.js.map +1 -0
- package/dist/moodle/pace.d.ts +4 -0
- package/dist/moodle/pace.js +19 -0
- package/dist/moodle/pace.js.map +1 -0
- package/dist/moodle/participants.d.ts +55 -0
- package/dist/moodle/participants.js +131 -0
- package/dist/moodle/participants.js.map +1 -0
- package/dist/moodle/questionBank.d.ts +81 -0
- package/dist/moodle/questionBank.js +235 -0
- package/dist/moodle/questionBank.js.map +1 -0
- package/dist/moodle/quiz.d.ts +117 -0
- package/dist/moodle/quiz.js +291 -0
- package/dist/moodle/quiz.js.map +1 -0
- package/dist/moodle/quizResults.d.ts +36 -0
- package/dist/moodle/quizResults.js +93 -0
- package/dist/moodle/quizResults.js.map +1 -0
- package/dist/moodle/quizStructure.d.ts +42 -0
- package/dist/moodle/quizStructure.js +100 -0
- package/dist/moodle/quizStructure.js.map +1 -0
- package/dist/moodle/session.d.ts +39 -0
- package/dist/moodle/session.js +99 -0
- package/dist/moodle/session.js.map +1 -0
- package/dist/moodle/site.d.ts +8 -0
- package/dist/moodle/site.js +36 -0
- package/dist/moodle/site.js.map +1 -0
- package/dist/moodle/siteInfo.d.ts +25 -0
- package/dist/moodle/siteInfo.js +45 -0
- package/dist/moodle/siteInfo.js.map +1 -0
- package/dist/moodle/structure.d.ts +94 -0
- package/dist/moodle/structure.js +103 -0
- package/dist/moodle/structure.js.map +1 -0
- package/dist/moodle/submission.d.ts +48 -0
- package/dist/moodle/submission.js +109 -0
- package/dist/moodle/submission.js.map +1 -0
- package/dist/moodle/survey.d.ts +47 -0
- package/dist/moodle/survey.js +197 -0
- package/dist/moodle/survey.js.map +1 -0
- package/dist/moodle/text.d.ts +21 -0
- package/dist/moodle/text.js +113 -0
- package/dist/moodle/text.js.map +1 -0
- package/dist/moodle/user.d.ts +54 -0
- package/dist/moodle/user.js +78 -0
- package/dist/moodle/user.js.map +1 -0
- package/dist/options.d.ts +30 -0
- package/dist/options.js +35 -0
- package/dist/options.js.map +1 -0
- package/dist/sessionCookies.d.ts +11 -0
- package/dist/sessionCookies.js +31 -0
- package/dist/sessionCookies.js.map +1 -0
- package/dist/testing/fakeMoodle.d.ts +55 -0
- package/dist/testing/fakeMoodle.js +103 -0
- package/dist/testing/fakeMoodle.js.map +1 -0
- package/dist/testing/gradingPanel.d.ts +6 -0
- package/dist/testing/gradingPanel.js +22 -0
- package/dist/testing/gradingPanel.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.js +18 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +78 -4
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { chromium } from "playwright-core";
|
|
3
|
+
import { installChromium } from "./chromium.js";
|
|
4
|
+
import { SYSTEM_CHANNELS } from "../options.js";
|
|
5
|
+
import { followPages } from "../core/session.js";
|
|
6
|
+
import { restoreSessionCookies, saveSessionCookies } from "./cookies.js";
|
|
7
|
+
import { MoodleError } from "../core/errors.js";
|
|
8
|
+
/** No installed browser could be launched, and Chromium can't be downloaded (ADR-001). */
|
|
9
|
+
export class BrowserNotFoundError extends MoodleError {
|
|
10
|
+
name = "BrowserNotFoundError";
|
|
11
|
+
constructor(message) {
|
|
12
|
+
super(message, "browser-not-found");
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** No browser yet: Chromium is downloading in the background, and the call should be retried later. */
|
|
16
|
+
export class BrowserDownloadingError extends MoodleError {
|
|
17
|
+
name = "BrowserDownloadingError";
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message, "browser-downloading");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** A window was asked for where there's no screen to show it (a container, a server). */
|
|
23
|
+
export class NoDisplayError extends MoodleError {
|
|
24
|
+
name = "NoDisplayError";
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message, "no-display");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether a window can be shown. On Linux it needs an X or Wayland display; without one (Docker, a
|
|
31
|
+
* server) the driver refuses rather than trying, so it never reaches a display it wasn't given.
|
|
32
|
+
*/
|
|
33
|
+
export function canShowWindow(env = process.env, platform = process.platform) {
|
|
34
|
+
return platform !== "linux" || Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
35
|
+
}
|
|
36
|
+
// Playwright's message when a channel isn't installed: "Chromium distribution 'msedge' is not found",
|
|
37
|
+
// with " at <path>" only when it knows where to look (playwright-core 1.64).
|
|
38
|
+
const NOT_INSTALLED = /distribution '[^']+' is not (found|supported)|Executable doesn't exist/i;
|
|
39
|
+
// Moodle pages can take long, above all the first ones after the site starts.
|
|
40
|
+
const NAVIGATION_TIMEOUT_MS = 60_000;
|
|
41
|
+
export function createBrowserManager(config, launch = chromium.launchPersistentContext.bind(chromium), install = installChromium, windowsPossible = canShowWindow) {
|
|
42
|
+
let open;
|
|
43
|
+
// What `setVisible` asked for, over the options; following goes with it.
|
|
44
|
+
let shown;
|
|
45
|
+
let followTab;
|
|
46
|
+
// The download runs in the background, outside the queue: it takes minutes, longer than some
|
|
47
|
+
// callers wait, so calls meanwhile answer at once that it's downloading.
|
|
48
|
+
let download;
|
|
49
|
+
let downloadFailure;
|
|
50
|
+
function startDownload() {
|
|
51
|
+
const retry = downloadFailure ? ` The previous download failed (${downloadFailure}); trying again.` : "";
|
|
52
|
+
downloadFailure = undefined;
|
|
53
|
+
download ??= install().then(() => {
|
|
54
|
+
download = undefined;
|
|
55
|
+
}, (error) => {
|
|
56
|
+
download = undefined;
|
|
57
|
+
downloadFailure = error.message ?? String(error);
|
|
58
|
+
});
|
|
59
|
+
return downloading(retry);
|
|
60
|
+
}
|
|
61
|
+
function downloading(extra = "") {
|
|
62
|
+
return new BrowserDownloadingError(`${config.channel === "chromium" ? "Chromium isn't downloaded yet" : "Neither Microsoft Edge nor Google Chrome is available"}, so it's being downloaded (about 180 MB, into PLAYWRIGHT_BROWSERS_PATH or the user's ms-playwright cache).${extra} Try again in a minute or two.`);
|
|
63
|
+
}
|
|
64
|
+
// One operation at a time: two launches on the same profile would fail.
|
|
65
|
+
let queue = Promise.resolve();
|
|
66
|
+
function exclusive(operation) {
|
|
67
|
+
const result = queue.then(operation, operation);
|
|
68
|
+
queue = result.catch(() => undefined);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
async function launchFirstAvailable(headed) {
|
|
72
|
+
await mkdir(config.profileDir, { recursive: true });
|
|
73
|
+
const channels = config.channel ? [config.channel] : [...SYSTEM_CHANNELS, ...(config.downloadBrowser ? ["chromium"] : [])];
|
|
74
|
+
const missing = [];
|
|
75
|
+
for (const channel of channels) {
|
|
76
|
+
if (channel === "chromium" && download)
|
|
77
|
+
throw downloading();
|
|
78
|
+
let context;
|
|
79
|
+
try {
|
|
80
|
+
// A window keeps its own size; a fixed viewport would leave it half empty.
|
|
81
|
+
context = await launch(config.profileDir, { channel, headless: !headed, ...(headed ? { viewport: null } : {}) });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
const message = error.message ?? String(error);
|
|
85
|
+
if (!NOT_INSTALLED.test(message))
|
|
86
|
+
throw error;
|
|
87
|
+
if (channel === "chromium" && config.downloadBrowser)
|
|
88
|
+
throw startDownload();
|
|
89
|
+
missing.push(channel);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
context.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT_MS);
|
|
93
|
+
followPages(context, (url) => manager.show(url));
|
|
94
|
+
await restoreSessionCookies(context, config.profileDir);
|
|
95
|
+
const opened = { context, channel, headed };
|
|
96
|
+
// The person may close the window: the next call opens the browser again, headless if they had
|
|
97
|
+
// asked to see it (closing it says they're done watching).
|
|
98
|
+
context.on("close", () => {
|
|
99
|
+
if (open !== opened)
|
|
100
|
+
return;
|
|
101
|
+
open = undefined;
|
|
102
|
+
if (opened.headed && shown)
|
|
103
|
+
shown = false;
|
|
104
|
+
});
|
|
105
|
+
return opened;
|
|
106
|
+
}
|
|
107
|
+
throw new BrowserNotFoundError(`No browser found (tried ${missing.join(", ")}). Install Microsoft Edge or Google Chrome${config.downloadBrowser ? "" : ", or allow downloading Chromium (downloadBrowser)"}, or choose another channel.`);
|
|
108
|
+
}
|
|
109
|
+
/** The working tab in `context`: the one already in use, else the window's first empty tab, else a new one. */
|
|
110
|
+
async function workTab(context) {
|
|
111
|
+
if (!followTab || followTab.isClosed())
|
|
112
|
+
followTab = context.pages().find((p) => p.url() === "about:blank") ?? (await context.newPage());
|
|
113
|
+
return followTab;
|
|
114
|
+
}
|
|
115
|
+
async function closeOpen() {
|
|
116
|
+
const current = open;
|
|
117
|
+
open = undefined;
|
|
118
|
+
followTab = undefined;
|
|
119
|
+
if (!current)
|
|
120
|
+
return;
|
|
121
|
+
await saveSessionCookies(current.context, config.profileDir).catch(() => undefined);
|
|
122
|
+
await current.context.close().catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
const manager = {
|
|
125
|
+
context({ headed = false } = {}) {
|
|
126
|
+
return exclusive(async () => {
|
|
127
|
+
const wantHeaded = headed || (shown ?? !config.headless);
|
|
128
|
+
if (open && (open.headed || !wantHeaded))
|
|
129
|
+
return open.context;
|
|
130
|
+
if (wantHeaded && !windowsPossible()) {
|
|
131
|
+
throw new NoDisplayError(`There's no screen to show a browser window here (no DISPLAY or WAYLAND_DISPLAY), so ${headed ? "logging in by hand isn't possible" : "a headed browser can't open"}. Run headless with a profile already logged in, or where there's a screen.`);
|
|
132
|
+
}
|
|
133
|
+
await closeOpen();
|
|
134
|
+
open = await launchFirstAvailable(wantHeaded);
|
|
135
|
+
return open.context;
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
visible: () => shown ?? !config.headless,
|
|
139
|
+
setVisible: (visible) => exclusive(async () => {
|
|
140
|
+
if (visible && !windowsPossible()) {
|
|
141
|
+
throw new NoDisplayError("There's no screen to show a browser window here (no DISPLAY or WAYLAND_DISPLAY). The browser keeps working headless.");
|
|
142
|
+
}
|
|
143
|
+
shown = visible;
|
|
144
|
+
if (visible && !open?.headed) {
|
|
145
|
+
await closeOpen();
|
|
146
|
+
open = await launchFirstAvailable(true);
|
|
147
|
+
}
|
|
148
|
+
else if (!visible && open?.headed) {
|
|
149
|
+
await closeOpen();
|
|
150
|
+
}
|
|
151
|
+
}),
|
|
152
|
+
async show(url) {
|
|
153
|
+
const current = open;
|
|
154
|
+
if (!shown || !current?.headed)
|
|
155
|
+
return;
|
|
156
|
+
try {
|
|
157
|
+
const tab = await workTab(current.context);
|
|
158
|
+
if (tab.url() !== url)
|
|
159
|
+
await tab.goto(url, { waitUntil: "domcontentloaded" });
|
|
160
|
+
await tab.bringToFront();
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Following is a courtesy: a page that doesn't load never changes a function's result.
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
tab: async () => workTab(await manager.context()),
|
|
167
|
+
openChannel: () => open?.channel,
|
|
168
|
+
chromiumDownload: () => (download ? "downloading" : downloadFailure ? "failed" : undefined),
|
|
169
|
+
saveSession: () => exclusive(async () => {
|
|
170
|
+
if (open)
|
|
171
|
+
await saveSessionCookies(open.context, config.profileDir);
|
|
172
|
+
}),
|
|
173
|
+
close: () => exclusive(closeOpen),
|
|
174
|
+
};
|
|
175
|
+
return manager;
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manager.js","sourceRoot":"","sources":["../../src/browser/manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { chromium, type BrowserContext, type Page } from "playwright-core";
|
|
2
|
+
import { type InstallChromium } from "./chromiumInstaller.js";
|
|
3
|
+
import { type BrowserOptions, type Channel } from "./options.js";
|
|
4
|
+
type LaunchPersistentContext = typeof chromium.launchPersistentContext;
|
|
5
|
+
export interface ContextOptions {
|
|
6
|
+
/** A visible window, whatever the options say (logging in by hand). */
|
|
7
|
+
headed?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** The browser's single persistent context, opened on first use (ADR-002). */
|
|
10
|
+
export interface BrowserManager {
|
|
11
|
+
/**
|
|
12
|
+
* The open context, launching it if needed. A headed request relaunches a headless context with
|
|
13
|
+
* a window; a headless request reuses a headed one rather than closing a window the person may be using.
|
|
14
|
+
*/
|
|
15
|
+
context(options?: ContextOptions): Promise<BrowserContext>;
|
|
16
|
+
/** The channel of the open context, or `undefined` while none is open. */
|
|
17
|
+
openChannel(): Channel | undefined;
|
|
18
|
+
/** Whether Chromium is being downloaded, or the last download failed (ADR-001). */
|
|
19
|
+
chromiumDownload(): "downloading" | "failed" | undefined;
|
|
20
|
+
/** Whether the browser shows a window now: what `setVisible` asked for, else the options. */
|
|
21
|
+
visible(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Shows the browser (relaunching it with a window now, session kept) and follows the pages functions open
|
|
24
|
+
* in it, or hides it (closing the window; the next call runs headless). ADR-007.
|
|
25
|
+
*/
|
|
26
|
+
setVisible(visible: boolean): Promise<void>;
|
|
27
|
+
/** While following, brings `url` (a site page) to the front of the window; best effort, never throws. */
|
|
28
|
+
show(url: string): Promise<void>;
|
|
29
|
+
/** The one working tab a program drives itself (ADR-007), the same one following shows; opens the browser if needed. */
|
|
30
|
+
tab(): Promise<Page>;
|
|
31
|
+
/** Saves the session cookies of the open context, if any (ADR-003). */
|
|
32
|
+
saveSession(): Promise<void>;
|
|
33
|
+
close(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/** No installed browser could be launched, and Chromium can't be downloaded (ADR-001). */
|
|
36
|
+
export declare class BrowserNotFoundError extends Error {
|
|
37
|
+
name: string;
|
|
38
|
+
}
|
|
39
|
+
/** No browser yet: Chromium is downloading in the background, and the call should be retried later. */
|
|
40
|
+
export declare class BrowserDownloadingError extends Error {
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
/** A window was asked for where there's no screen to show it (a container, a server). */
|
|
44
|
+
export declare class NoDisplayError extends Error {
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Whether a window can be shown. On Linux it needs an X or Wayland display; without one (Docker, a
|
|
49
|
+
* server) the driver refuses rather than trying, so it never reaches a display it wasn't given.
|
|
50
|
+
*/
|
|
51
|
+
export declare function canShowWindow(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
|
|
52
|
+
export declare function createBrowserManager(config: BrowserOptions, launch?: LaunchPersistentContext, install?: InstallChromium, windowsPossible?: () => boolean): BrowserManager;
|
|
53
|
+
export {};
|
package/dist/browser.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import { chromium } from "playwright-core";
|
|
3
|
+
import { installChromium } from "./chromiumInstaller.js";
|
|
4
|
+
import { SYSTEM_CHANNELS } from "./options.js";
|
|
5
|
+
import { followPages } from "./moodle/session.js";
|
|
6
|
+
import { restoreSessionCookies, saveSessionCookies } from "./sessionCookies.js";
|
|
7
|
+
/** No installed browser could be launched, and Chromium can't be downloaded (ADR-001). */
|
|
8
|
+
export class BrowserNotFoundError extends Error {
|
|
9
|
+
name = "BrowserNotFoundError";
|
|
10
|
+
}
|
|
11
|
+
/** No browser yet: Chromium is downloading in the background, and the call should be retried later. */
|
|
12
|
+
export class BrowserDownloadingError extends Error {
|
|
13
|
+
name = "BrowserDownloadingError";
|
|
14
|
+
}
|
|
15
|
+
/** A window was asked for where there's no screen to show it (a container, a server). */
|
|
16
|
+
export class NoDisplayError extends Error {
|
|
17
|
+
name = "NoDisplayError";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Whether a window can be shown. On Linux it needs an X or Wayland display; without one (Docker, a
|
|
21
|
+
* server) the driver refuses rather than trying, so it never reaches a display it wasn't given.
|
|
22
|
+
*/
|
|
23
|
+
export function canShowWindow(env = process.env, platform = process.platform) {
|
|
24
|
+
return platform !== "linux" || Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
25
|
+
}
|
|
26
|
+
// Playwright's message when a channel isn't installed: "Chromium distribution 'msedge' is not found",
|
|
27
|
+
// with " at <path>" only when it knows where to look (playwright-core 1.64).
|
|
28
|
+
const NOT_INSTALLED = /distribution '[^']+' is not (found|supported)|Executable doesn't exist/i;
|
|
29
|
+
// Moodle pages can take long, above all the first ones after the site starts.
|
|
30
|
+
const NAVIGATION_TIMEOUT_MS = 60_000;
|
|
31
|
+
export function createBrowserManager(config, launch = chromium.launchPersistentContext.bind(chromium), install = installChromium, windowsPossible = canShowWindow) {
|
|
32
|
+
let open;
|
|
33
|
+
// What `setVisible` asked for, over the options; following goes with it.
|
|
34
|
+
let shown;
|
|
35
|
+
let followTab;
|
|
36
|
+
// The download runs in the background, outside the queue: it takes minutes, longer than some
|
|
37
|
+
// callers wait, so calls meanwhile answer at once that it's downloading.
|
|
38
|
+
let download;
|
|
39
|
+
let downloadFailure;
|
|
40
|
+
function startDownload() {
|
|
41
|
+
const retry = downloadFailure ? ` The previous download failed (${downloadFailure}); trying again.` : "";
|
|
42
|
+
downloadFailure = undefined;
|
|
43
|
+
download ??= install().then(() => {
|
|
44
|
+
download = undefined;
|
|
45
|
+
}, (error) => {
|
|
46
|
+
download = undefined;
|
|
47
|
+
downloadFailure = error.message ?? String(error);
|
|
48
|
+
});
|
|
49
|
+
return downloading(retry);
|
|
50
|
+
}
|
|
51
|
+
function downloading(extra = "") {
|
|
52
|
+
return new BrowserDownloadingError(`${config.channel === "chromium" ? "Chromium isn't downloaded yet" : "Neither Microsoft Edge nor Google Chrome is available"}, so it's being downloaded (about 180 MB, into PLAYWRIGHT_BROWSERS_PATH or the user's ms-playwright cache).${extra} Try again in a minute or two.`);
|
|
53
|
+
}
|
|
54
|
+
// One operation at a time: two launches on the same profile would fail.
|
|
55
|
+
let queue = Promise.resolve();
|
|
56
|
+
function exclusive(operation) {
|
|
57
|
+
const result = queue.then(operation, operation);
|
|
58
|
+
queue = result.catch(() => undefined);
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
async function launchFirstAvailable(headed) {
|
|
62
|
+
await mkdir(config.profileDir, { recursive: true });
|
|
63
|
+
const channels = config.channel ? [config.channel] : [...SYSTEM_CHANNELS, ...(config.downloadBrowser ? ["chromium"] : [])];
|
|
64
|
+
const missing = [];
|
|
65
|
+
for (const channel of channels) {
|
|
66
|
+
if (channel === "chromium" && download)
|
|
67
|
+
throw downloading();
|
|
68
|
+
let context;
|
|
69
|
+
try {
|
|
70
|
+
// A window keeps its own size; a fixed viewport would leave it half empty.
|
|
71
|
+
context = await launch(config.profileDir, { channel, headless: !headed, ...(headed ? { viewport: null } : {}) });
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
const message = error.message ?? String(error);
|
|
75
|
+
if (!NOT_INSTALLED.test(message))
|
|
76
|
+
throw error;
|
|
77
|
+
if (channel === "chromium" && config.downloadBrowser)
|
|
78
|
+
throw startDownload();
|
|
79
|
+
missing.push(channel);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
context.setDefaultNavigationTimeout(NAVIGATION_TIMEOUT_MS);
|
|
83
|
+
followPages(context, (url) => manager.show(url));
|
|
84
|
+
await restoreSessionCookies(context, config.profileDir);
|
|
85
|
+
const opened = { context, channel, headed };
|
|
86
|
+
// The person may close the window: the next call opens the browser again, headless if they had
|
|
87
|
+
// asked to see it (closing it says they're done watching).
|
|
88
|
+
context.on("close", () => {
|
|
89
|
+
if (open !== opened)
|
|
90
|
+
return;
|
|
91
|
+
open = undefined;
|
|
92
|
+
if (opened.headed && shown)
|
|
93
|
+
shown = false;
|
|
94
|
+
});
|
|
95
|
+
return opened;
|
|
96
|
+
}
|
|
97
|
+
throw new BrowserNotFoundError(`No browser found (tried ${missing.join(", ")}). Install Microsoft Edge or Google Chrome${config.downloadBrowser ? "" : ", or allow downloading Chromium (downloadBrowser)"}, or choose another channel.`);
|
|
98
|
+
}
|
|
99
|
+
/** The working tab in `context`: the one already in use, else the window's first empty tab, else a new one. */
|
|
100
|
+
async function workTab(context) {
|
|
101
|
+
if (!followTab || followTab.isClosed())
|
|
102
|
+
followTab = context.pages().find((p) => p.url() === "about:blank") ?? (await context.newPage());
|
|
103
|
+
return followTab;
|
|
104
|
+
}
|
|
105
|
+
async function closeOpen() {
|
|
106
|
+
const current = open;
|
|
107
|
+
open = undefined;
|
|
108
|
+
followTab = undefined;
|
|
109
|
+
if (!current)
|
|
110
|
+
return;
|
|
111
|
+
await saveSessionCookies(current.context, config.profileDir).catch(() => undefined);
|
|
112
|
+
await current.context.close().catch(() => undefined);
|
|
113
|
+
}
|
|
114
|
+
const manager = {
|
|
115
|
+
context({ headed = false } = {}) {
|
|
116
|
+
return exclusive(async () => {
|
|
117
|
+
const wantHeaded = headed || (shown ?? !config.headless);
|
|
118
|
+
if (open && (open.headed || !wantHeaded))
|
|
119
|
+
return open.context;
|
|
120
|
+
if (wantHeaded && !windowsPossible()) {
|
|
121
|
+
throw new NoDisplayError(`There's no screen to show a browser window here (no DISPLAY or WAYLAND_DISPLAY), so ${headed ? "logging in by hand isn't possible" : "a headed browser can't open"}. Run headless with a profile already logged in, or where there's a screen.`);
|
|
122
|
+
}
|
|
123
|
+
await closeOpen();
|
|
124
|
+
open = await launchFirstAvailable(wantHeaded);
|
|
125
|
+
return open.context;
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
visible: () => shown ?? !config.headless,
|
|
129
|
+
setVisible: (visible) => exclusive(async () => {
|
|
130
|
+
if (visible && !windowsPossible()) {
|
|
131
|
+
throw new NoDisplayError("There's no screen to show a browser window here (no DISPLAY or WAYLAND_DISPLAY). The browser keeps working headless.");
|
|
132
|
+
}
|
|
133
|
+
shown = visible;
|
|
134
|
+
if (visible && !open?.headed) {
|
|
135
|
+
await closeOpen();
|
|
136
|
+
open = await launchFirstAvailable(true);
|
|
137
|
+
}
|
|
138
|
+
else if (!visible && open?.headed) {
|
|
139
|
+
await closeOpen();
|
|
140
|
+
}
|
|
141
|
+
}),
|
|
142
|
+
async show(url) {
|
|
143
|
+
const current = open;
|
|
144
|
+
if (!shown || !current?.headed)
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
const tab = await workTab(current.context);
|
|
148
|
+
if (tab.url() !== url)
|
|
149
|
+
await tab.goto(url, { waitUntil: "domcontentloaded" });
|
|
150
|
+
await tab.bringToFront();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Following is a courtesy: a page that doesn't load never changes a function's result.
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
tab: async () => workTab(await manager.context()),
|
|
157
|
+
openChannel: () => open?.channel,
|
|
158
|
+
chromiumDownload: () => (download ? "downloading" : downloadFailure ? "failed" : undefined),
|
|
159
|
+
saveSession: () => exclusive(async () => {
|
|
160
|
+
if (open)
|
|
161
|
+
await saveSessionCookies(open.context, config.profileDir);
|
|
162
|
+
}),
|
|
163
|
+
close: () => exclusive(closeOpen),
|
|
164
|
+
};
|
|
165
|
+
return manager;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A kind of data a function reads or writes:
|
|
3
|
+
* - `none`: the session or the site, nothing of anyone's; also functions that only parse what the
|
|
4
|
+
* caller already fetched (a page, HTML, JSON), whose data belongs to whatever fetched it.
|
|
5
|
+
* - `own`: the person's own data and actions (their courses, attempts, submissions, grades).
|
|
6
|
+
* - `course-content`: what teachers make (structure, activities' content and settings, rubrics' definitions, questions).
|
|
7
|
+
* - `others-aggregate`: counts about others that identify no one ("2 of 4 submitted").
|
|
8
|
+
* - `others-personal`: others' data or work (names, roles, submissions, posts, attempts, responses, grades).
|
|
9
|
+
* - `others-write`: deciding about others (grades, feedback, extensions, overrides, completion).
|
|
10
|
+
* - `communication`: writing to or before others (forum posts, messages).
|
|
11
|
+
* - `any`: generic primitives that reach whatever they're pointed at (services, pages, forms).
|
|
12
|
+
*/
|
|
13
|
+
export declare const ALL_DATA_CATEGORIES: readonly ["none", "own", "course-content", "others-aggregate", "others-personal", "others-write", "communication", "any"];
|
|
14
|
+
export type DataCategory = (typeof ALL_DATA_CATEGORIES)[number];
|
|
15
|
+
/**
|
|
16
|
+
* The data categories of every function exported from the package, and of the browser manager's
|
|
17
|
+
* methods (`BrowserManager.<method>`) and the Moodle object's own (`Moodle.<method>`; its other methods are
|
|
18
|
+
* the functions of the same name). A function that may touch several kinds lists them all; a
|
|
19
|
+
* program takes the strictest. Functions that read activities of any module answer per module
|
|
20
|
+
* through `moduleCategories()`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const DATA_CATEGORIES: Readonly<Record<string, readonly DataCategory[]>>;
|
|
23
|
+
/**
|
|
24
|
+
* The data a teacher reads when reading an activity of `module` with everything in it: course
|
|
25
|
+
* content alone for what teachers make (pages, books, files…), also others' personal data where
|
|
26
|
+
* participants leave work or responses. An unknown module (a plugin) could hold anything: `any`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function moduleCategories(module: string): readonly DataCategory[];
|