amenbo 0.1.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 +171 -0
- package/dist/adapters/index.d.ts +22 -0
- package/dist/adapters/index.js +51 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/types.d.ts +21 -0
- package/dist/adapters/types.js +2 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cache.d.ts +90 -0
- package/dist/cache.js +286 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +46 -0
- package/dist/cli.js +327 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +78 -0
- package/dist/core.js +649 -0
- package/dist/core.js.map +1 -0
- package/dist/diff.d.ts +20 -0
- package/dist/diff.js +44 -0
- package/dist/diff.js.map +1 -0
- package/dist/errors.d.ts +97 -0
- package/dist/errors.js +209 -0
- package/dist/errors.js.map +1 -0
- package/dist/extract/dataSources.d.ts +10 -0
- package/dist/extract/dataSources.js +115 -0
- package/dist/extract/dataSources.js.map +1 -0
- package/dist/extract/geometry.d.ts +63 -0
- package/dist/extract/geometry.js +77 -0
- package/dist/extract/geometry.js.map +1 -0
- package/dist/extract/markdown.d.ts +24 -0
- package/dist/extract/markdown.js +221 -0
- package/dist/extract/markdown.js.map +1 -0
- package/dist/extract/outline.d.ts +33 -0
- package/dist/extract/outline.js +128 -0
- package/dist/extract/outline.js.map +1 -0
- package/dist/extract/pdf.d.ts +22 -0
- package/dist/extract/pdf.js +95 -0
- package/dist/extract/pdf.js.map +1 -0
- package/dist/extract/preview.d.ts +19 -0
- package/dist/extract/preview.js +94 -0
- package/dist/extract/preview.js.map +1 -0
- package/dist/extract/pruning.d.ts +61 -0
- package/dist/extract/pruning.js +101 -0
- package/dist/extract/pruning.js.map +1 -0
- package/dist/extract/qualityScore.d.ts +67 -0
- package/dist/extract/qualityScore.js +98 -0
- package/dist/extract/qualityScore.js.map +1 -0
- package/dist/fetcher/browser.d.ts +85 -0
- package/dist/fetcher/browser.js +343 -0
- package/dist/fetcher/browser.js.map +1 -0
- package/dist/fetcher/http.d.ts +105 -0
- package/dist/fetcher/http.js +538 -0
- package/dist/fetcher/http.js.map +1 -0
- package/dist/fetcher/index.d.ts +41 -0
- package/dist/fetcher/index.js +92 -0
- package/dist/fetcher/index.js.map +1 -0
- package/dist/jp/consentBanner.d.ts +27 -0
- package/dist/jp/consentBanner.js +48 -0
- package/dist/jp/consentBanner.js.map +1 -0
- package/dist/jp/normalize.d.ts +30 -0
- package/dist/jp/normalize.js +65 -0
- package/dist/jp/normalize.js.map +1 -0
- package/dist/links.d.ts +20 -0
- package/dist/links.js +223 -0
- package/dist/links.js.map +1 -0
- package/dist/politeness.d.ts +66 -0
- package/dist/politeness.js +170 -0
- package/dist/politeness.js.map +1 -0
- package/dist/screenshot.d.ts +38 -0
- package/dist/screenshot.js +90 -0
- package/dist/screenshot.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.js +111 -0
- package/dist/server.js.map +1 -0
- package/dist/templateLearning.d.ts +11 -0
- package/dist/templateLearning.js +42 -0
- package/dist/templateLearning.js.map +1 -0
- package/dist/tokens.d.ts +47 -0
- package/dist/tokens.js +141 -0
- package/dist/tokens.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetcher/browser.ts — Playwright chromium共有インスタンス。
|
|
3
|
+
*
|
|
4
|
+
* 二段フェッチ(fetcher/index.ts)でSPAと判定された場合のみ起動する遅延初期化。
|
|
5
|
+
* プロセス終了シグナルでクリーンアップする。
|
|
6
|
+
*/
|
|
7
|
+
import { chromium, } from "playwright";
|
|
8
|
+
import { AmenboError, BrowserLaunchError, FetchTimeoutError, InvalidUrlError } from "../errors.js";
|
|
9
|
+
import { guardPublicAddress, USER_AGENT } from "./http.js";
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
11
|
+
let browserPromise = null;
|
|
12
|
+
// 改善キュー対応: 同梱Chromium(Playwrightがpinするビルド)がERR_HTTP2_PROTOCOL_ERROR系で
|
|
13
|
+
// ナビゲーションに失敗するサイト(実機確認: initial.inc)向けのフォールバック専用インスタンス。
|
|
14
|
+
// 低負荷優先の設計原則に従い、実際にフォールバックが発生するまでは起動しない(遅延初期化)。
|
|
15
|
+
let systemChromeBrowserPromise = null;
|
|
16
|
+
// code-reviewer指摘: システムChromeの実行ファイル自体が存在しない(未インストール)場合、
|
|
17
|
+
// HTTP2エラーが起きる度に無駄なlaunch試行(失敗が確定しているのに毎回レイテンシを払う)を
|
|
18
|
+
// 繰り返さないよう、プロセス寿命内で「無い」と分かった事実をキャッシュする。
|
|
19
|
+
// (launch失敗が実行ファイル不在以外の理由の場合はキャッシュせず、以後も再試行を許す。)
|
|
20
|
+
let systemChromeExecutableMissing = false;
|
|
21
|
+
let cleanupRegistered = false;
|
|
22
|
+
/**
|
|
23
|
+
* Playwrightがchannel指定の実行ファイルを見つけられない場合に投げるエラーメッセージ
|
|
24
|
+
* ("Chromium distribution 'chrome' is not found at ...")のパターン。この場合のみ
|
|
25
|
+
* systemChromeExecutableMissingへ恒久的にキャッシュする(それ以外の起動失敗は一時的な
|
|
26
|
+
* 可能性があるため、次回のHTTP2エラー発生時に再試行を許す)。
|
|
27
|
+
*/
|
|
28
|
+
const CHROME_EXECUTABLE_MISSING_PATTERN = /is not found/i;
|
|
29
|
+
function isMissingExecutableError(error) {
|
|
30
|
+
return error instanceof Error && CHROME_EXECUTABLE_MISSING_PATTERN.test(error.message);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* M2: 以前はcleanup内でclose後にprocess.exit()を呼んでおらず、chromium起動後は
|
|
34
|
+
* SIGINTの既定動作(即終了)がこのリスナーに上書きされたままハングしていた
|
|
35
|
+
* (closeBrowser()はPromiseを返すだけで、'exit'イベントは同期処理しか待てないため
|
|
36
|
+
* 実質何もせず終了していた)。close完了を待ってから明示的にprocess.exit()する。
|
|
37
|
+
*/
|
|
38
|
+
function registerCleanupHandlers() {
|
|
39
|
+
if (cleanupRegistered)
|
|
40
|
+
return;
|
|
41
|
+
cleanupRegistered = true;
|
|
42
|
+
const shutdown = () => {
|
|
43
|
+
void closeBrowser().finally(() => {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
process.once("SIGINT", shutdown);
|
|
48
|
+
process.once("SIGTERM", shutdown);
|
|
49
|
+
}
|
|
50
|
+
/** 共有chromiumインスタンスを取得する(未起動なら遅延起動)。 */
|
|
51
|
+
export async function getBrowser() {
|
|
52
|
+
if (!browserPromise) {
|
|
53
|
+
registerCleanupHandlers();
|
|
54
|
+
browserPromise = chromium.launch({ headless: true }).catch((cause) => {
|
|
55
|
+
browserPromise = null;
|
|
56
|
+
throw new BrowserLaunchError("chromiumの起動に失敗しました", { cause });
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return browserPromise;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* システムにインストール済みのGoogle Chrome(channel: "chrome")を遅延起動する。
|
|
63
|
+
* 同梱Chromiumのpage.goto()がERR_HTTP2_PROTOCOL_ERROR系で失敗した場合のみ呼ばれる想定
|
|
64
|
+
* (isChromiumHttp2NavigationError参照)。未インストール環境ではchromium.launch自体が
|
|
65
|
+
* 失敗するため、呼び出し元(openPageAndNavigate)でcatchし元のエラーへフォールスルーする。
|
|
66
|
+
* 実行ファイル不在と判明した場合はプロセス寿命内でキャッシュし、以後は起動を試みない
|
|
67
|
+
* (毎回のlaunch失敗レイテンシを避ける)。
|
|
68
|
+
*/
|
|
69
|
+
async function getSystemChromeBrowser() {
|
|
70
|
+
if (systemChromeExecutableMissing) {
|
|
71
|
+
throw new BrowserLaunchError("システムのGoogle Chromeが見つかりません(未インストールと判明済みのため再試行しません)");
|
|
72
|
+
}
|
|
73
|
+
if (!systemChromeBrowserPromise) {
|
|
74
|
+
registerCleanupHandlers();
|
|
75
|
+
systemChromeBrowserPromise = chromium.launch({ headless: true, channel: "chrome" }).catch((cause) => {
|
|
76
|
+
systemChromeBrowserPromise = null;
|
|
77
|
+
if (isMissingExecutableError(cause)) {
|
|
78
|
+
systemChromeExecutableMissing = true;
|
|
79
|
+
}
|
|
80
|
+
throw new BrowserLaunchError("システムのGoogle Chromeの起動に失敗しました", { cause });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return systemChromeBrowserPromise;
|
|
84
|
+
}
|
|
85
|
+
async function closeBrowserPromise(promise) {
|
|
86
|
+
if (!promise)
|
|
87
|
+
return;
|
|
88
|
+
try {
|
|
89
|
+
const browser = await promise;
|
|
90
|
+
await browser.close();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// 起動に失敗していた場合等は無視(既にクローズ済み/未起動)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/** 共有chromium/システムChromeインスタンスを終了する(プロセス終了時・テストのクリーンアップ用)。 */
|
|
97
|
+
export async function closeBrowser() {
|
|
98
|
+
const primary = browserPromise;
|
|
99
|
+
const systemChrome = systemChromeBrowserPromise;
|
|
100
|
+
browserPromise = null;
|
|
101
|
+
systemChromeBrowserPromise = null;
|
|
102
|
+
await Promise.all([closeBrowserPromise(primary), closeBrowserPromise(systemChrome)]);
|
|
103
|
+
}
|
|
104
|
+
// ---- Phase 4: ジオメトリ抽出用のデータ採取(型はextract/geometry.tsを正とする) ----
|
|
105
|
+
const EMPTY_GEOMETRY = { textBlocks: [], visualElements: [], pageWidth: 0, pageHeight: 0 };
|
|
106
|
+
/**
|
|
107
|
+
* Phase 4 ジオメトリ抽出用に、テキストを直接持つリーフ要素の bounding box・フォントサイズ・
|
|
108
|
+
* 可視性と、表/canvas/svgの bounding box を採取する。
|
|
109
|
+
*
|
|
110
|
+
* 対象要素には一時属性 `data-amenbo-gid` を付与する。これは page.content() 呼び出し前に
|
|
111
|
+
* 実行することで、後段の抽出処理(extract/markdown.ts, linkedomでの再パース)が
|
|
112
|
+
* 同じ要素をCSSセレクタで再選択できるようにするため(ブラウザとNode間でDOM参照を
|
|
113
|
+
* 直接受け渡せないので、属性経由で紐付ける)。
|
|
114
|
+
*/
|
|
115
|
+
export async function collectPageGeometry(page) {
|
|
116
|
+
return page.evaluate(() => {
|
|
117
|
+
const leafSelector = "p, li, td, th, h1, h2, h3, h4, h5, h6, blockquote, dd, dt, figcaption";
|
|
118
|
+
const textBlocks = [];
|
|
119
|
+
let gid = 0;
|
|
120
|
+
for (const el of Array.from(document.querySelectorAll(leafSelector))) {
|
|
121
|
+
const text = (el.textContent ?? "").trim();
|
|
122
|
+
if (text.length < 2)
|
|
123
|
+
continue;
|
|
124
|
+
const style = window.getComputedStyle(el);
|
|
125
|
+
if (style.display === "none" || style.visibility === "hidden" || Number(style.opacity || "1") === 0)
|
|
126
|
+
continue;
|
|
127
|
+
const rect = el.getBoundingClientRect();
|
|
128
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
129
|
+
continue;
|
|
130
|
+
gid++;
|
|
131
|
+
el.setAttribute("data-amenbo-gid", String(gid));
|
|
132
|
+
textBlocks.push({
|
|
133
|
+
id: gid,
|
|
134
|
+
x: Math.round(rect.left + window.scrollX),
|
|
135
|
+
y: Math.round(rect.top + window.scrollY),
|
|
136
|
+
width: Math.round(rect.width),
|
|
137
|
+
height: Math.round(rect.height),
|
|
138
|
+
textLength: text.length,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
const visualElements = [];
|
|
142
|
+
for (const el of Array.from(document.querySelectorAll("table, canvas, svg"))) {
|
|
143
|
+
const style = window.getComputedStyle(el);
|
|
144
|
+
if (style.display === "none" || style.visibility === "hidden")
|
|
145
|
+
continue;
|
|
146
|
+
const rect = el.getBoundingClientRect();
|
|
147
|
+
if (rect.width <= 0 || rect.height <= 0)
|
|
148
|
+
continue;
|
|
149
|
+
visualElements.push({
|
|
150
|
+
tag: el.tagName.toLowerCase(),
|
|
151
|
+
x: Math.round(rect.left + window.scrollX),
|
|
152
|
+
y: Math.round(rect.top + window.scrollY),
|
|
153
|
+
width: Math.round(rect.width),
|
|
154
|
+
height: Math.round(rect.height),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
textBlocks,
|
|
159
|
+
visualElements,
|
|
160
|
+
pageWidth: Math.ceil(document.documentElement.scrollWidth),
|
|
161
|
+
pageHeight: Math.ceil(document.documentElement.scrollHeight),
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* J8: 国内同意バナー・アプリ誘導インタースティシャルを実レンダリング結果から隠す。
|
|
167
|
+
*
|
|
168
|
+
* jp/consentBanner.tsの静的DOM版と同じ判定パターンだが、page.evaluate内は
|
|
169
|
+
* ブラウザコンテキストで実行されNode側のクロージャを参照できないため、判定パターンを
|
|
170
|
+
* self-containedな関数として定義し直している(多少の重複はやむを得ない設計判断)。
|
|
171
|
+
* 実ブラウザではcomputed styleが取得できるため、position:fixed等の判定も加えている。
|
|
172
|
+
* DOMから除去するのではなく非表示にする(スクリーンショット/SPA判定への影響を抑えつつ
|
|
173
|
+
* 視覚的な妨げだけを取り除く)。
|
|
174
|
+
*/
|
|
175
|
+
export async function hideConsentBanners(page) {
|
|
176
|
+
return page.evaluate(() => {
|
|
177
|
+
const textPatterns = [
|
|
178
|
+
/同意して閉じる/,
|
|
179
|
+
/同意する/,
|
|
180
|
+
/Cookie.{0,10}(の使用に|に)?同意/i,
|
|
181
|
+
/このサイトはCookieを使用/,
|
|
182
|
+
/アプリで(開く|見る|読む)/,
|
|
183
|
+
/アプリをダウンロード/,
|
|
184
|
+
/アプリ内で開く/,
|
|
185
|
+
/ストアで見る/,
|
|
186
|
+
];
|
|
187
|
+
const idClassPattern = /cookie|consent|cmp[-_]|gdpr|app[-_]?banner|interstitial|smart-?banner/i;
|
|
188
|
+
const maxTextLength = 400;
|
|
189
|
+
let hidden = 0;
|
|
190
|
+
const candidates = Array.from(document.querySelectorAll("div, section, aside, dialog"));
|
|
191
|
+
for (const el of candidates) {
|
|
192
|
+
const text = el.textContent ?? "";
|
|
193
|
+
if (text.length === 0 || text.length > maxTextLength)
|
|
194
|
+
continue;
|
|
195
|
+
const idClassMatch = idClassPattern.test(`${el.id} ${el.className}`);
|
|
196
|
+
const textMatch = textPatterns.some((pattern) => pattern.test(text));
|
|
197
|
+
if (!(idClassMatch && textMatch))
|
|
198
|
+
continue;
|
|
199
|
+
const style = window.getComputedStyle(el);
|
|
200
|
+
const isOverlayish = style.position === "fixed" || style.position === "sticky" || Number(style.zIndex || "0") >= 100;
|
|
201
|
+
if (isOverlayish) {
|
|
202
|
+
el.style.setProperty("display", "none", "important");
|
|
203
|
+
hidden++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return hidden;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// ---- C1/C2: SSRFガード付きナビゲーション ----
|
|
210
|
+
const NAVIGATION_ROUTE_PATTERN = "**/*";
|
|
211
|
+
/**
|
|
212
|
+
* C1: browser.ts(fetchWithBrowser)・screenshot.ts(captureTiledScreenshot)は
|
|
213
|
+
* fetcher/http.tsのSSRF/スキーム検証(guardPublicAddress)を経由せずpage.goto()していたため、
|
|
214
|
+
* `file:///etc/passwd` や `http://169.254.169.254/...` が素通りしていた。
|
|
215
|
+
*
|
|
216
|
+
* 対応:
|
|
217
|
+
* 1. page.goto()の前にguardPublicAddressで初回遷移先を検証する
|
|
218
|
+
* 2. context.route()で全リクエストを横取りし、メインフレームのナビゲーション
|
|
219
|
+
* リクエスト(初回遷移・各リダイレクトホップの両方が該当する)についてのみ
|
|
220
|
+
* 再度guardPublicAddressを通す。private/予約アドレスやhttp(s)以外のスキームへの
|
|
221
|
+
* 遷移が検出された場合はそのリクエスト自体をabortし(=実接続前に遮断)、
|
|
222
|
+
* 型付きエラー(PrivateAddressError/InvalidUrlError)に変換してgoto()の失敗を報告する
|
|
223
|
+
*
|
|
224
|
+
* サブリソース(img/xhr等)は対象外(スコープはユーザー指定URLへの「遷移」の安全性)。
|
|
225
|
+
* 完全なTOCTOU対策(fetcher/http.tsのssrfSafeLookupのような接続時点での検証)は
|
|
226
|
+
* Playwright側では現実的に組み込めないため、遷移前検証+リダイレクト時の再検証という
|
|
227
|
+
* 「可能な範囲」の対策にとどめる。
|
|
228
|
+
*/
|
|
229
|
+
export async function navigateSafely(page, url, timeoutMs) {
|
|
230
|
+
await guardPublicAddress(url);
|
|
231
|
+
let blockedError = null;
|
|
232
|
+
const handler = async (route, request) => {
|
|
233
|
+
if (!request.isNavigationRequest() || request.frame() !== page.mainFrame()) {
|
|
234
|
+
await route.continue();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
try {
|
|
238
|
+
await guardPublicAddress(request.url());
|
|
239
|
+
await route.continue();
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
blockedError = error instanceof AmenboError ? error : new InvalidUrlError(request.url(), "ナビゲーション検証に失敗しました");
|
|
243
|
+
await route.abort();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
await page.route(NAVIGATION_ROUTE_PATTERN, handler);
|
|
247
|
+
try {
|
|
248
|
+
return await page.goto(url, { waitUntil: "networkidle", timeout: timeoutMs });
|
|
249
|
+
}
|
|
250
|
+
catch (cause) {
|
|
251
|
+
if (blockedError)
|
|
252
|
+
throw blockedError;
|
|
253
|
+
if (cause instanceof Error && /timeout/i.test(cause.message)) {
|
|
254
|
+
throw new FetchTimeoutError(url, timeoutMs);
|
|
255
|
+
}
|
|
256
|
+
throw cause;
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
await page.unroute(NAVIGATION_ROUTE_PATTERN, handler).catch(() => { });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// ---- 改善キュー対応: 同梱ChromiumのHTTP/2非互換に対するシステムChromeフォールバック ----
|
|
263
|
+
const HTTP2_NAVIGATION_ERROR_PATTERN = /ERR_HTTP2_/;
|
|
264
|
+
/**
|
|
265
|
+
* page.goto()の失敗が、ERR_HTTP2_PROTOCOL_ERROR系(実機検証: initial.inc等でPlaywright
|
|
266
|
+
* pinビルドのChromiumのみ発生し、素のcurl --http2やシステムのGoogle Chromeでは成功する
|
|
267
|
+
* ブラウザビルド固有のHTTP/2非互換)かどうかを判定する。この場合のみシステムChromeへの
|
|
268
|
+
* フォールバックを試みる(接続拒否/タイムアウト等の一般的なネットワークエラーは対象外。
|
|
269
|
+
* 常時Chrome起動という追加コストを、明確にビルド固有と分かっているケース以外では払わない)。
|
|
270
|
+
*/
|
|
271
|
+
export function isChromiumHttp2NavigationError(error) {
|
|
272
|
+
return error instanceof Error && HTTP2_NAVIGATION_ERROR_PATTERN.test(error.message);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* 共有chromiumでcontext/pageを作りnavigateSafelyでナビゲーションする。ERR_HTTP2_PROTOCOL_ERROR系
|
|
276
|
+
* で失敗した場合のみ、システムのGoogle Chromeへ1回だけフォールバックする。
|
|
277
|
+
*
|
|
278
|
+
* フォールバックが行われないケース(元のエラーをそのまま投げ、呼び出し元の既存の
|
|
279
|
+
* エラー分類・メッセージ生成をそのまま活かす):
|
|
280
|
+
* - HTTP2以外のエラー(接続拒否/タイムアウト等)
|
|
281
|
+
* - システムにChromeが無くgetSystemChromeBrowser自体が失敗した場合
|
|
282
|
+
* Chromeでの再試行も失敗した場合は、そちらのエラー(Chrome経由での実際の失敗内容)を投げる。
|
|
283
|
+
*/
|
|
284
|
+
export async function openPageAndNavigate(url, timeoutMs, contextOptions = {}) {
|
|
285
|
+
const primaryBrowser = await getBrowser();
|
|
286
|
+
const primaryContext = await primaryBrowser.newContext(contextOptions);
|
|
287
|
+
try {
|
|
288
|
+
const page = await primaryContext.newPage();
|
|
289
|
+
const response = await navigateSafely(page, url, timeoutMs);
|
|
290
|
+
return { context: primaryContext, page, response };
|
|
291
|
+
}
|
|
292
|
+
catch (primaryError) {
|
|
293
|
+
await primaryContext.close().catch(() => { });
|
|
294
|
+
if (!isChromiumHttp2NavigationError(primaryError))
|
|
295
|
+
throw primaryError;
|
|
296
|
+
let chromeBrowser;
|
|
297
|
+
try {
|
|
298
|
+
chromeBrowser = await getSystemChromeBrowser();
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// システムChrome未インストール等: 元のエラー(分類済みメッセージ)にフォールスルーする
|
|
302
|
+
throw primaryError;
|
|
303
|
+
}
|
|
304
|
+
let chromeContext;
|
|
305
|
+
try {
|
|
306
|
+
chromeContext = await chromeBrowser.newContext(contextOptions);
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// code-reviewer指摘: newContext自体をtry内に含める。前回のフォールバック使用後に
|
|
310
|
+
// システムChromeが切断されていた場合等、newContextが素の例外を投げうるため、
|
|
311
|
+
// getSystemChromeBrowser失敗時と同様に元のエラー(分類済みメッセージ)へ
|
|
312
|
+
// フォールスルーする(Chrome側の生の例外で覆い隠さない)。
|
|
313
|
+
throw primaryError;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const page = await chromeContext.newPage();
|
|
317
|
+
const response = await navigateSafely(page, url, timeoutMs);
|
|
318
|
+
return { context: chromeContext, page, response };
|
|
319
|
+
}
|
|
320
|
+
catch (fallbackError) {
|
|
321
|
+
await chromeContext.close().catch(() => { });
|
|
322
|
+
throw fallbackError;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/** Playwrightでページをレンダリングし、レンダリング後のHTMLを取得する。 */
|
|
327
|
+
export async function fetchWithBrowser(url, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
328
|
+
const { context, page, response } = await openPageAndNavigate(url, timeoutMs, { userAgent: USER_AGENT });
|
|
329
|
+
try {
|
|
330
|
+
await hideConsentBanners(page).catch(() => {
|
|
331
|
+
// ページ評価に失敗しても取得自体は継続する(ベストエフォート)
|
|
332
|
+
});
|
|
333
|
+
// Phase 4: ジオメトリ抽出用のデータ採取(data-amenbo-gid付与)は、それがHTMLへ反映される
|
|
334
|
+
// page.content()より必ず先に行う
|
|
335
|
+
const geometry = await collectPageGeometry(page).catch(() => EMPTY_GEOMETRY);
|
|
336
|
+
const html = await page.content();
|
|
337
|
+
return { finalUrl: page.url(), html, status: response?.status() ?? 200, geometry };
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
await context.close();
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../src/fetcher/browser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EACL,QAAQ,GAQT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEnG,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAI3D,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,IAAI,cAAc,GAA4B,IAAI,CAAC;AACnD,sEAAsE;AACtE,wDAAwD;AACxD,gDAAgD;AAChD,IAAI,0BAA0B,GAA4B,IAAI,CAAC;AAC/D,yDAAyD;AACzD,oDAAoD;AACpD,wCAAwC;AACxC,iDAAiD;AACjD,IAAI,6BAA6B,GAAG,KAAK,CAAC;AAC1C,IAAI,iBAAiB,GAAG,KAAK,CAAC;AAE9B;;;;;GAKG;AACH,MAAM,iCAAiC,GAAG,eAAe,CAAC;AAE1D,SAAS,wBAAwB,CAAC,KAAc;IAC9C,OAAO,KAAK,YAAY,KAAK,IAAI,iCAAiC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,SAAS,uBAAuB;IAC9B,IAAI,iBAAiB;QAAE,OAAO;IAC9B,iBAAiB,GAAG,IAAI,CAAC;IACzB,MAAM,QAAQ,GAAG,GAAS,EAAE;QAC1B,KAAK,YAAY,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;YAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACjC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;AACpC,CAAC;AAED,wCAAwC;AACxC,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,uBAAuB,EAAE,CAAC;QAC1B,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC5E,cAAc,GAAG,IAAI,CAAC;YACtB,MAAM,IAAI,kBAAkB,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,sBAAsB;IACnC,IAAI,6BAA6B,EAAE,CAAC;QAClC,MAAM,IAAI,kBAAkB,CAAC,oDAAoD,CAAC,CAAC;IACrF,CAAC;IAED,IAAI,CAAC,0BAA0B,EAAE,CAAC;QAChC,uBAAuB,EAAE,CAAC;QAC1B,0BAA0B,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC3G,0BAA0B,GAAG,IAAI,CAAC;YAClC,IAAI,wBAAwB,CAAC,KAAK,CAAC,EAAE,CAAC;gBACpC,6BAA6B,GAAG,IAAI,CAAC;YACvC,CAAC;YACD,MAAM,IAAI,kBAAkB,CAAC,8BAA8B,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,0BAA0B,CAAC;AACpC,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,OAAgC;IACjE,IAAI,CAAC,OAAO;QAAE,OAAO;IACrB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;QAC9B,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,gCAAgC;IAClC,CAAC;AACH,CAAC;AAED,8DAA8D;AAC9D,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,MAAM,OAAO,GAAG,cAAc,CAAC;IAC/B,MAAM,YAAY,GAAG,0BAA0B,CAAC;IAChD,cAAc,GAAG,IAAI,CAAC;IACtB,0BAA0B,GAAG,IAAI,CAAC;IAClC,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,EAAE,mBAAmB,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;AACvF,CAAC;AASD,gEAAgE;AAEhE,MAAM,cAAc,GAAyB,EAAE,UAAU,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;AAEjH;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,IAAU;IAClD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACxB,MAAM,YAAY,GAAG,uEAAuE,CAAC;QAC7F,MAAM,UAAU,GAAmG,EAAE,CAAC;QACtH,IAAI,GAAG,GAAG,CAAC,CAAC;QAEZ,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC;YACrE,MAAM,IAAI,GAAG,CAAC,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;gBAAE,SAAS;YAE9B,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS;YAE9G,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;gBAAE,SAAS;YAElD,GAAG,EAAE,CAAC;YACN,EAAE,CAAC,YAAY,CAAC,iBAAiB,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAChD,UAAU,CAAC,IAAI,CAAC;gBACd,EAAE,EAAE,GAAG;gBACP,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC;gBACzC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;gBACxC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;gBAC7B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;gBAC/B,UAAU,EAAE,IAAI,CAAC,MAAM;aACxB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,cAAc,GAAgF,EAAE,CAAC;QACvG,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,EAAE,CAAC;YAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC1C,IAAI,KAAK,CAAC,OAAO,KAAK,MAAM,IAAI,KAAK,CAAC,UAAU,KAAK,QAAQ;gBAAE,SAAS;YAExE,MAAM,IAAI,GAAG,EAAE,CAAC,qBAAqB,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC;gBAAE,SAAS;YAElD,cAAc,CAAC,IAAI,CAAC;gBAClB,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE;gBAC7B,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC;gBACzC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;gBACxC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;gBAC7B,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;aAChC,CAAC,CAAC;QACL,CAAC;QAED,OAAO;YACL,UAAU;YACV,cAAc;YACd,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,WAAW,CAAC;YAC1D,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,YAAY,CAAC;SAC7D,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,IAAU;IACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE;QACxB,MAAM,YAAY,GAAG;YACnB,SAAS;YACT,MAAM;YACN,2BAA2B;YAC3B,iBAAiB;YACjB,gBAAgB;YAChB,YAAY;YACZ,SAAS;YACT,QAAQ;SACT,CAAC;QACF,MAAM,cAAc,GAAG,wEAAwE,CAAC;QAChG,MAAM,aAAa,GAAG,GAAG,CAAC;QAE1B,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC,6BAA6B,CAAC,CAAC,CAAC;QACxF,KAAK,MAAM,EAAE,IAAI,UAAU,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,IAAI,EAAE,CAAC;YAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,aAAa;gBAAE,SAAS;YAE/D,MAAM,YAAY,GAAG,cAAc,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC;YACrE,MAAM,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACrE,IAAI,CAAC,CAAC,YAAY,IAAI,SAAS,CAAC;gBAAE,SAAS;YAE3C,MAAM,KAAK,GAAG,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;YAC1C,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,KAAK,OAAO,IAAI,KAAK,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG,CAAC;YAErH,IAAI,YAAY,EAAE,CAAC;gBAChB,EAAkB,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;gBACtE,MAAM,EAAE,CAAC;YACX,CAAC;QACH,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,oCAAoC;AAEpC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAExC;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAU,EAAE,GAAW,EAAE,SAAiB;IAC7E,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAE9B,IAAI,YAAY,GAAuB,IAAI,CAAC;IAC5C,MAAM,OAAO,GAAG,KAAK,EAAE,KAAY,EAAE,OAAgB,EAAiB,EAAE;QACtE,IAAI,CAAC,OAAO,CAAC,mBAAmB,EAAE,IAAI,OAAO,CAAC,KAAK,EAAE,KAAK,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC;YAC3E,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;YACvB,OAAO;QACT,CAAC;QACD,IAAI,CAAC;YACH,MAAM,kBAAkB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;YACxC,MAAM,KAAK,CAAC,QAAQ,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,YAAY,GAAG,KAAK,YAAY,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,kBAAkB,CAAC,CAAC;YAC7G,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,IAAI,CAAC,KAAK,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,YAAY;YAAE,MAAM,YAA2B,CAAC;QACpD,IAAI,KAAK,YAAY,KAAK,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7D,MAAM,IAAI,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC9C,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,CAAC,OAAO,CAAC,wBAAwB,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,+DAA+D;AAE/D,MAAM,8BAA8B,GAAG,YAAY,CAAC;AAEpD;;;;;;GAMG;AACH,MAAM,UAAU,8BAA8B,CAAC,KAAc;IAC3D,OAAO,KAAK,YAAY,KAAK,IAAI,8BAA8B,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;AACtF,CAAC;AAQD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW,EACX,SAAiB,EACjB,cAAc,GAA0B,EAAE;IAE1C,MAAM,cAAc,GAAG,MAAM,UAAU,EAAE,CAAC;IAC1C,MAAM,cAAc,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;IACvE,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5D,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACrD,CAAC;IAAC,OAAO,YAAY,EAAE,CAAC;QACtB,MAAM,cAAc,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,8BAA8B,CAAC,YAAY,CAAC;YAAE,MAAM,YAAY,CAAC;QAEtE,IAAI,aAAsB,CAAC;QAC3B,IAAI,CAAC;YACH,aAAa,GAAG,MAAM,sBAAsB,EAAE,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,iDAAiD;YACjD,MAAM,YAAY,CAAC;QACrB,CAAC;QAED,IAAI,aAA6B,CAAC;QAClC,IAAI,CAAC;YACH,aAAa,GAAG,MAAM,aAAa,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,wDAAwD;YACxD,gDAAgD;YAChD,iDAAiD;YACjD,kCAAkC;YAClC,MAAM,YAAY,CAAC;QACrB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;YAC5D,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;QACpD,CAAC;QAAC,OAAO,aAAa,EAAE,CAAC;YACvB,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC5C,MAAM,aAAa,CAAC;QACtB,CAAC;IACH,CAAC;AACH,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW,EAAE,SAAS,GAAW,kBAAkB;IACxF,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IACzG,IAAI,CAAC;QACH,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;YACxC,iCAAiC;QACnC,CAAC,CAAC,CAAC;QACH,4DAA4D;QAC5D,yBAAyB;QACzB,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,cAAc,CAAC,CAAC;QAC7E,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QAClC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,CAAC;IACrF,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export declare const USER_AGENT = "amenbo/0.1 (+https://github.com/Rererr/amenbo)";
|
|
2
|
+
/** IPアドレス(v4/v6)がprivate/loopback/link-local等の予約アドレスかどうか判定する。 */
|
|
3
|
+
export declare function isPrivateOrReservedIp(ip: string): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* URLがhttp(s)スキームかどうかだけを検証する軽量チェック(DNS解決は行わない)。
|
|
6
|
+
*
|
|
7
|
+
* server.ts(fetch/screenshotツール)・links.tsの各エントリポイントで、
|
|
8
|
+
* politeness.guard(robots.txt取得)やブラウザ起動より前に最初に呼ぶことを想定している。
|
|
9
|
+
* これを怠ると、例えば file:// のようなURLに対して politeness 側が
|
|
10
|
+
* `new URL(url).origin` (file:等では "null" になる)から不正なrobots URL
|
|
11
|
+
* (`null/robots.txt`)を組み立ててしまい、guardPublicAddress内の `new URL()` が
|
|
12
|
+
* 生のTypeErrorを投げてスタックトレースがstderrに漏れる問題があった(公開品質バグ)。
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertHttpScheme(urlStr: string): URL;
|
|
15
|
+
/**
|
|
16
|
+
* URLのホストを解決し、private/予約アドレスであれば拒否する(SSRF対策)。
|
|
17
|
+
* http(s)以外のスキーム(file:等)も拒否する。
|
|
18
|
+
*
|
|
19
|
+
* fetcher/browser.ts・screenshot.tsのPlaywrightナビゲーション(page.goto前・
|
|
20
|
+
* リダイレクト再検証)からも共通利用する(C1: screenshot/browser層がSSRF/スキーム
|
|
21
|
+
* 検証を経由していなかった問題への対応)。
|
|
22
|
+
*
|
|
23
|
+
* 注意(TOCTOU): この関数の検証と実際の接続(fetch/ブラウザのTCP接続)は別のDNS解決を
|
|
24
|
+
* 行うため、その間にDNS rebindingが起きると理論上すり抜けうる。httpGet/httpGetBinaryの
|
|
25
|
+
* 実接続については下記「TOCTOU対策」のssrfSafeLookupで接続時点そのものを検証しており、
|
|
26
|
+
* この関数はスキーム検証と早期の分かりやすいエラーメッセージのための一次防御にあたる。
|
|
27
|
+
*/
|
|
28
|
+
export declare function guardPublicAddress(urlStr: string): Promise<void>;
|
|
29
|
+
export interface DecodedHtml {
|
|
30
|
+
text: string;
|
|
31
|
+
/** 表示用のエンコーディング名(例: "UTF-8" / "Shift_JIS")。 */
|
|
32
|
+
encoding: string;
|
|
33
|
+
}
|
|
34
|
+
/** J1: Content-Typeヘッダ→metaタグ→encoding-japanese自動判定の順でHTMLをデコードする。 */
|
|
35
|
+
export declare function decodeHtmlBytes(bytes: Uint8Array, contentTypeHeader: string | null): DecodedHtml;
|
|
36
|
+
/** 環境変数 AMENBO_MAX_BODY_BYTES で既定のボディサイズ上限を調整できる。 */
|
|
37
|
+
export declare function resolveDefaultMaxBodyBytes(): number;
|
|
38
|
+
export interface HttpGetOptions {
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
/** 条件付きGET用の追加ヘッダ(If-None-Match/If-Modified-Since等)。 */
|
|
41
|
+
headers?: Record<string, string>;
|
|
42
|
+
/** レスポンスボディのサイズ上限(バイト)。既定は環境変数AMENBO_MAX_BODY_BYTES、未設定なら20MB。 */
|
|
43
|
+
maxBytes?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface HttpGetResult {
|
|
46
|
+
finalUrl: string;
|
|
47
|
+
status: number;
|
|
48
|
+
headers: Headers;
|
|
49
|
+
/** status 304 の場合は空文字。 */
|
|
50
|
+
html: string;
|
|
51
|
+
/** status 304 の場合は空文字。 */
|
|
52
|
+
encoding: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* SSRF対策付きの素のHTTP GET(テキスト/HTML用)。リダイレクトは手動で追跡し、毎ホップでSSRFガードを行う。
|
|
56
|
+
* status 304(未変更)はエラーにせずそのまま返す(キャッシュ再検証用)。
|
|
57
|
+
* 200以外(304を除く)の非2xxはHttpStatusErrorを投げる。
|
|
58
|
+
*/
|
|
59
|
+
export declare function httpGet(url: string, options?: HttpGetOptions): Promise<HttpGetResult>;
|
|
60
|
+
export type HttpGetBinaryOptions = HttpGetOptions;
|
|
61
|
+
export interface HttpGetBinaryResult {
|
|
62
|
+
finalUrl: string;
|
|
63
|
+
status: number;
|
|
64
|
+
headers: Headers;
|
|
65
|
+
/** status 304 の場合は空。 */
|
|
66
|
+
bytes: Uint8Array;
|
|
67
|
+
contentType: string | null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* SSRF対策付きの素のHTTP GET(バイナリ用、PDF等)。デコードせずバイト列のまま返す。
|
|
71
|
+
* Content-Lengthヘッダ・実バイト数の両方でmaxBytesを検証する(ヘッダ詐称対策として実サイズも確認)。
|
|
72
|
+
* status 304(未変更)はhttpGetと同様エラーにせずそのまま返す(条件付きGETを行う将来の呼び出し元向け)。
|
|
73
|
+
*/
|
|
74
|
+
export declare function httpGetBinary(url: string, options?: HttpGetBinaryOptions): Promise<HttpGetBinaryResult>;
|
|
75
|
+
export type HttpGetRoutedResult = {
|
|
76
|
+
kind: "notModified";
|
|
77
|
+
status: 304;
|
|
78
|
+
finalUrl: string;
|
|
79
|
+
headers: Headers;
|
|
80
|
+
} | {
|
|
81
|
+
kind: "html";
|
|
82
|
+
status: number;
|
|
83
|
+
finalUrl: string;
|
|
84
|
+
headers: Headers;
|
|
85
|
+
html: string;
|
|
86
|
+
encoding: string;
|
|
87
|
+
} | {
|
|
88
|
+
kind: "handoff";
|
|
89
|
+
status: number;
|
|
90
|
+
finalUrl: string;
|
|
91
|
+
headers: Headers;
|
|
92
|
+
contentType: string | null;
|
|
93
|
+
bytes: Uint8Array;
|
|
94
|
+
/** Content-Lengthヘッダ由来の宣言サイズ(無ければnull)。 */
|
|
95
|
+
declaredSize: number | null;
|
|
96
|
+
/** プレビュー上限で本文を打ち切った場合true(ファイル全体は取得していない)。 */
|
|
97
|
+
truncated: boolean;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* 機能B: content-typeを見て、HTML本文の全体読み取り(既存のhttpGetと同じ挙動)か、
|
|
101
|
+
* 非HTMLハンドオフ用のプレビュー読み取り(既定256KBで打ち切り)かを1回のfetchで振り分ける。
|
|
102
|
+
* リダイレクト先を含め毎ホップでcontent-typeが確定するのは最終応答のみなので、
|
|
103
|
+
* guardedFetchで得たヘッダを見てから初めてボディの読み方を決める。
|
|
104
|
+
*/
|
|
105
|
+
export declare function httpGetRouted(url: string, options?: HttpGetOptions): Promise<HttpGetRoutedResult>;
|