@cosmocoder/mcp-web-docs 2.1.6 → 2.1.7
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/build/crawler/auth.js +18 -3
- package/build/crawler/auth.js.map +1 -1
- package/build/crawler/auth.test.js +88 -2
- package/build/crawler/auth.test.js.map +1 -1
- package/build/crawler/crawlee-crawler.d.ts +14 -3
- package/build/crawler/crawlee-crawler.js +82 -39
- package/build/crawler/crawlee-crawler.js.map +1 -1
- package/build/crawler/crawlee-crawler.test.js +173 -0
- package/build/crawler/crawlee-crawler.test.js.map +1 -1
- package/build/crawler/login-page-signals.d.ts +20 -0
- package/build/crawler/login-page-signals.js +34 -0
- package/build/crawler/login-page-signals.js.map +1 -0
- package/build/crawler/login-page-signals.test.d.ts +1 -0
- package/build/crawler/login-page-signals.test.js +91 -0
- package/build/crawler/login-page-signals.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Read in the page, so it must not reference anything outside itself. */
|
|
2
|
+
export declare function readLoginPageSignals(): {
|
|
3
|
+
text: string;
|
|
4
|
+
hasPasswordInput: boolean;
|
|
5
|
+
headings: string[];
|
|
6
|
+
frameSources: string[];
|
|
7
|
+
};
|
|
8
|
+
/** A login shell is nothing but a frame or a button; a docs page carrying a sign-in box has an article */
|
|
9
|
+
export declare const LOGIN_SHELL_MAX_TEXT = 200;
|
|
10
|
+
/** Three of the detector's six indicators. Stricter than its own two, which it never applies here. */
|
|
11
|
+
export declare const LOGIN_PAGE_CONFIDENCE = 0.5;
|
|
12
|
+
/**
|
|
13
|
+
* What tells this page apart from the next one, or null when nothing does. A theme rendering its
|
|
14
|
+
* headings as styled divs would otherwise make every page of the site one page.
|
|
15
|
+
*
|
|
16
|
+
* The page's own address comes out first, because a login page names where it is sending you back
|
|
17
|
+
* to. Only its own: removing anything else would fold pages differing by exactly that. Accepted
|
|
18
|
+
* cost: `POST /api/login` at /api/login and `POST /api/logout` at /api/logout fold into one.
|
|
19
|
+
*/
|
|
20
|
+
export declare function pageIdentity(headings: string[], text: string, currentUrl: string): string | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
/** Read in the page, so it must not reference anything outside itself. */
|
|
3
|
+
export function readLoginPageSignals() {
|
|
4
|
+
const collapse = (value) => value.replace(/\s+/g, ' ').trim();
|
|
5
|
+
return {
|
|
6
|
+
text: collapse(document.body?.textContent || ''),
|
|
7
|
+
hasPasswordInput: document.querySelector('input[type="password"]') !== null,
|
|
8
|
+
headings: [...document.querySelectorAll('h1, h2')].slice(0, 5).map((heading) => collapse(heading.textContent || '')),
|
|
9
|
+
frameSources: [...document.querySelectorAll('iframe')].map((frame) => frame.getAttribute('src') || '').filter(Boolean),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/** A login shell is nothing but a frame or a button; a docs page carrying a sign-in box has an article */
|
|
13
|
+
export const LOGIN_SHELL_MAX_TEXT = 200;
|
|
14
|
+
/** Three of the detector's six indicators. Stricter than its own two, which it never applies here. */
|
|
15
|
+
export const LOGIN_PAGE_CONFIDENCE = 0.5;
|
|
16
|
+
/**
|
|
17
|
+
* What tells this page apart from the next one, or null when nothing does. A theme rendering its
|
|
18
|
+
* headings as styled divs would otherwise make every page of the site one page.
|
|
19
|
+
*
|
|
20
|
+
* The page's own address comes out first, because a login page names where it is sending you back
|
|
21
|
+
* to. Only its own: removing anything else would fold pages differing by exactly that. Accepted
|
|
22
|
+
* cost: `POST /api/login` at /api/login and `POST /api/logout` at /api/logout fold into one.
|
|
23
|
+
*/
|
|
24
|
+
export function pageIdentity(headings, text, currentUrl) {
|
|
25
|
+
const { pathname } = new URL(currentUrl);
|
|
26
|
+
const identifying = headings.map((heading) => heading.replaceAll(currentUrl, '').replaceAll(pathname, '').trim()).filter(Boolean);
|
|
27
|
+
if (identifying.length > 0) {
|
|
28
|
+
return createHash('sha1').update(JSON.stringify(identifying)).digest('hex');
|
|
29
|
+
}
|
|
30
|
+
// A page that is barely anything is still recognisable as that; one with an article and no
|
|
31
|
+
// headings could be any page.
|
|
32
|
+
return text.length < LOGIN_SHELL_MAX_TEXT ? 'shell' : null;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=login-page-signals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-page-signals.js","sourceRoot":"","sources":["../../src/crawler/login-page-signals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,0EAA0E;AAC1E,MAAM,UAAU,oBAAoB;IAMlC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACtE,OAAO;QACL,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,IAAI,EAAE,CAAC;QAChD,gBAAgB,EAAE,QAAQ,CAAC,aAAa,CAAC,wBAAwB,CAAC,KAAK,IAAI;QAC3E,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;QACpH,YAAY,EAAE,CAAC,GAAG,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;KACvH,CAAC;AACJ,CAAC;AAED,0GAA0G;AAC1G,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,sGAAsG;AACtG,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAEzC;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,QAAkB,EAAE,IAAY,EAAE,UAAkB;IAC/E,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAClI,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9E,CAAC;IACD,2FAA2F;IAC3F,8BAA8B;IAC9B,OAAO,IAAI,CAAC,MAAM,GAAG,oBAAoB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { LOGIN_SHELL_MAX_TEXT, pageIdentity, readLoginPageSignals } from './login-page-signals.js';
|
|
3
|
+
function render(html) {
|
|
4
|
+
document.body.innerHTML = html;
|
|
5
|
+
return readLoginPageSignals();
|
|
6
|
+
}
|
|
7
|
+
const LOGIN_FORM = `
|
|
8
|
+
<h1>Sign in</h1>
|
|
9
|
+
<form action="/login?next=%2Fdocs%2F42">
|
|
10
|
+
<input name="username" type="text">
|
|
11
|
+
<input name="password" type="password">
|
|
12
|
+
</form>`;
|
|
13
|
+
describe('readLoginPageSignals', () => {
|
|
14
|
+
it('reads the text, the password input and the page shape', () => {
|
|
15
|
+
expect(render(LOGIN_FORM)).toEqual({
|
|
16
|
+
text: 'Sign in',
|
|
17
|
+
hasPasswordInput: true,
|
|
18
|
+
headings: ['Sign in'],
|
|
19
|
+
frameSources: [],
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
it('is the same for one login page answering different URLs', () => {
|
|
23
|
+
const first = render(`${LOGIN_FORM}<p>Continue to /docs/1. Attempt 1 of 5.</p>`);
|
|
24
|
+
const second = render(`${LOGIN_FORM.replace('%2Fdocs%2F42', '%2Fdocs%2F7')}<p>Continue to /docs/7. Attempt 2 of 5.</p>`);
|
|
25
|
+
expect(first.text).not.toEqual(second.text);
|
|
26
|
+
expect({ ...first, text: '' }).toEqual({ ...second, text: '' });
|
|
27
|
+
});
|
|
28
|
+
it('differs between documentation pages built from one template', () => {
|
|
29
|
+
const shapes = ['login', 'logout', 'refresh'].map((endpoint) => render(`<h1>POST /api/${endpoint}</h1><p>Sign in with your username and password.</p>`).headings);
|
|
30
|
+
expect(new Set(shapes.map((headings) => headings.join())).size).toBe(3);
|
|
31
|
+
});
|
|
32
|
+
it('reports an empty shape and no password input for an ordinary page', () => {
|
|
33
|
+
expect(render('<p>Ordinary content</p>')).toEqual({
|
|
34
|
+
text: 'Ordinary content',
|
|
35
|
+
hasPasswordInput: false,
|
|
36
|
+
headings: [],
|
|
37
|
+
frameSources: [],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
it('reports the frames a page embeds, and only those with a source', () => {
|
|
41
|
+
expect(render('<iframe src="/sso/login"></iframe><iframe></iframe>').frameSources).toEqual(['/sso/login']);
|
|
42
|
+
});
|
|
43
|
+
// Rebuilding it from its own source is the only way a reference to anything outside it shows up
|
|
44
|
+
// here rather than in the browser page.evaluate sends it to
|
|
45
|
+
it('reads nothing from outside itself, as page.evaluate requires', () => {
|
|
46
|
+
document.body.innerHTML = LOGIN_FORM;
|
|
47
|
+
const detached = new Function(`return (${readLoginPageSignals.toString()})()`);
|
|
48
|
+
expect(detached()).toEqual(readLoginPageSignals());
|
|
49
|
+
});
|
|
50
|
+
it('collapses whitespace and caps the headings it reports', () => {
|
|
51
|
+
const signals = render(`<h1> Sign\n in </h1>${[1, 2, 3, 4, 5, 6].map((n) => `<h2>Section ${n}</h2>`).join('')}`);
|
|
52
|
+
expect(signals.headings).toEqual(['Sign in', 'Section 1', 'Section 2', 'Section 3', 'Section 4']);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('pageIdentity', () => {
|
|
56
|
+
const url = 'https://example.com/docs/42';
|
|
57
|
+
const article = 'x'.repeat(LOGIN_SHELL_MAX_TEXT);
|
|
58
|
+
// A theme rendering its headings as styled divs leaves nothing to go on
|
|
59
|
+
it('has no answer for a page with an article on it and no headings', () => {
|
|
60
|
+
expect(pageIdentity([], article, url)).toBeNull();
|
|
61
|
+
expect(pageIdentity([' '], article, url)).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
// A page that is barely anything is still recognisable as that
|
|
64
|
+
it('recognises a bare page with no headings', () => {
|
|
65
|
+
expect(pageIdentity([], 'Continue with SSO', url)).toEqual(pageIdentity([], 'Continue', 'https://example.com/docs/7'));
|
|
66
|
+
expect(pageIdentity([], 'Continue', url)).not.toBeNull();
|
|
67
|
+
});
|
|
68
|
+
// A login shell is not always wordless - it explains why you are looking at it
|
|
69
|
+
it('still recognises a bare page that explains itself', () => {
|
|
70
|
+
expect(pageIdentity([], 'Your session has expired. Please sign in again to continue.', url)).not.toBeNull();
|
|
71
|
+
expect(pageIdentity([], 'x'.repeat(LOGIN_SHELL_MAX_TEXT - 1), url)).not.toBeNull();
|
|
72
|
+
expect(pageIdentity([], 'x'.repeat(LOGIN_SHELL_MAX_TEXT), url)).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
// A login page names where it is sending you back to, so that part is not what the page is
|
|
75
|
+
it('is the same for one login page naming different URLs', () => {
|
|
76
|
+
expect(pageIdentity(['Sign in to continue to /docs/42'], article, url)).toEqual(pageIdentity(['Sign in to continue to /docs/7'], article, 'https://example.com/docs/7'));
|
|
77
|
+
});
|
|
78
|
+
it('differs between pages that are actually about different things', () => {
|
|
79
|
+
expect(pageIdentity(['Installing'], article, url)).not.toEqual(pageIdentity(['Configuring'], article, url));
|
|
80
|
+
});
|
|
81
|
+
// Only the page's own address comes out - a heading that IS an address elsewhere still counts
|
|
82
|
+
it('keeps an address that is not this page', () => {
|
|
83
|
+
expect(pageIdentity(['POST /api/login'], article, url)).not.toEqual(pageIdentity(['POST /api/logout'], article, url));
|
|
84
|
+
});
|
|
85
|
+
// Accepted: strip a heading that is only the page's own address and nothing is left. It takes a
|
|
86
|
+
// password field on each of these pages to matter, which the caller asks before this.
|
|
87
|
+
it('folds pages whose heading is only their own address', () => {
|
|
88
|
+
expect(pageIdentity(['POST /api/login'], article, 'https://example.com/api/login')).toEqual(pageIdentity(['POST /api/logout'], article, 'https://example.com/api/logout'));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=login-page-signals.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-page-signals.test.js","sourceRoot":"","sources":["../../src/crawler/login-page-signals.test.ts"],"names":[],"mappings":"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"}
|