@lunora/browser 1.0.0-alpha.5 → 1.0.0-alpha.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/dist/index.d.mts
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
10
|
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
-
* consumes it.
|
|
12
|
-
*
|
|
11
|
+
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
+
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
+
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
+
* misuse at the call site instead of deferring to an opaque launch error.
|
|
13
15
|
*/
|
|
14
16
|
interface BrowserBindingLike {
|
|
15
|
-
readonly fetch
|
|
17
|
+
readonly fetch: (...args: never[]) => unknown;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
package/dist/index.d.ts
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* It is intentionally opaque: callers never touch the binding directly, they
|
|
10
10
|
* hand it to {@link LunoraBrowserOptions.binding} and the Playwright layer
|
|
11
|
-
* consumes it.
|
|
12
|
-
*
|
|
11
|
+
* consumes it. `fetch` is REQUIRED (the real binding is a `Fetcher`, so it
|
|
12
|
+
* always has one) so the marker actually excludes an arbitrary value like `{}` —
|
|
13
|
+
* a bare object fails to type-check where a binding is required, catching the
|
|
14
|
+
* misuse at the call site instead of deferring to an opaque launch error.
|
|
13
15
|
*/
|
|
14
16
|
interface BrowserBindingLike {
|
|
15
|
-
readonly fetch
|
|
17
|
+
readonly fetch: (...args: never[]) => unknown;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* Minimal projection of a Playwright `Route` (the argument the `page.route`
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createBrowser } from './packem_shared/createBrowser-
|
|
1
|
+
export { createBrowser } from './packem_shared/createBrowser-CZmfQ38r.mjs';
|
|
@@ -129,19 +129,19 @@ const isPrivateTarget = (parsed) => {
|
|
|
129
129
|
};
|
|
130
130
|
const validateUrl = (url, allowPrivateTargets, allowedHosts) => {
|
|
131
131
|
if (typeof url !== "string" || url.length === 0) {
|
|
132
|
-
throw new
|
|
132
|
+
throw new LunoraError("BAD_REQUEST", "@lunora/browser: url must be a non-empty string");
|
|
133
133
|
}
|
|
134
134
|
let parsed;
|
|
135
135
|
try {
|
|
136
136
|
parsed = new URL(url);
|
|
137
137
|
} catch {
|
|
138
|
-
throw new
|
|
138
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/browser: url must be an absolute http(s) URL (got "${url}")`);
|
|
139
139
|
}
|
|
140
140
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
141
|
-
throw new
|
|
141
|
+
throw new LunoraError("BAD_REQUEST", `@lunora/browser: url protocol must be http(s) (got "${parsed.protocol}")`);
|
|
142
142
|
}
|
|
143
143
|
if (parsed.username !== "" || parsed.password !== "") {
|
|
144
|
-
throw new LunoraError("
|
|
144
|
+
throw new LunoraError("BAD_REQUEST", "@lunora/browser: url must not embed credentials (strip the `user:pass@` userinfo)");
|
|
145
145
|
}
|
|
146
146
|
if (allowedHosts && allowedHosts.length > 0) {
|
|
147
147
|
const host = normalizeHost(parsed.hostname);
|
|
@@ -151,7 +151,7 @@ const validateUrl = (url, allowPrivateTargets, allowedHosts) => {
|
|
|
151
151
|
}
|
|
152
152
|
if (!allowPrivateTargets && isPrivateTarget(parsed)) {
|
|
153
153
|
throw new LunoraError(
|
|
154
|
-
"
|
|
154
|
+
"FORBIDDEN",
|
|
155
155
|
`@lunora/browser: url host "${parsed.hostname}" is a private/internal address; pass createBrowser({ …, allowPrivateTargets: true }) to allow it`
|
|
156
156
|
);
|
|
157
157
|
}
|
|
@@ -174,6 +174,27 @@ const resolveTimeout = (callTimeout, factoryTimeout) => {
|
|
|
174
174
|
const safe = Number.isFinite(requested) ? requested : DEFAULT_TIMEOUT_MS;
|
|
175
175
|
return Math.min(Math.max(1, Math.floor(safe)), MAX_TIMEOUT_MS);
|
|
176
176
|
};
|
|
177
|
+
const withDeadline = async (operation, timeoutMs) => {
|
|
178
|
+
let timer;
|
|
179
|
+
try {
|
|
180
|
+
return await Promise.race([
|
|
181
|
+
operation(),
|
|
182
|
+
new Promise((_resolve, reject) => {
|
|
183
|
+
timer = setTimeout(() => {
|
|
184
|
+
reject(
|
|
185
|
+
new LunoraError("BROWSER_TIMEOUT", `@lunora/browser: navigation + operation exceeded the ${String(timeoutMs)}ms timeout budget`, {
|
|
186
|
+
status: 504
|
|
187
|
+
})
|
|
188
|
+
);
|
|
189
|
+
}, timeoutMs);
|
|
190
|
+
})
|
|
191
|
+
]);
|
|
192
|
+
} finally {
|
|
193
|
+
if (timer !== void 0) {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
177
198
|
const createBrowser = (options) => {
|
|
178
199
|
if (!options.binding) {
|
|
179
200
|
throw new TypeError("@lunora/browser: `binding` is required (env.BROWSER)");
|
|
@@ -209,18 +230,40 @@ const createBrowser = (options) => {
|
|
|
209
230
|
}
|
|
210
231
|
const assertNavigationAllowed = async (requestUrl) => {
|
|
211
232
|
validateUrl(requestUrl, allowPrivateTargets, options.allowedHosts);
|
|
212
|
-
if (resolveDns) {
|
|
233
|
+
if (!allowPrivateTargets && resolveDns) {
|
|
213
234
|
await assertResolvedHostIsPublic(requestUrl, dohTimeout);
|
|
214
235
|
}
|
|
215
236
|
};
|
|
237
|
+
const isBlockedSubresource = (rawUrl) => {
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = new URL(rawUrl);
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (options.allowedHosts && options.allowedHosts.length > 0) {
|
|
248
|
+
const host = normalizeHost(parsed.hostname);
|
|
249
|
+
if (!options.allowedHosts.some((entry) => normalizeHost(entry) === host)) {
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return isPrivateTarget(parsed);
|
|
254
|
+
};
|
|
216
255
|
return withBrowser(async (browser) => {
|
|
217
256
|
const context = await browser.newContext();
|
|
218
257
|
const page = await context.newPage();
|
|
219
|
-
if (!allowPrivateTargets
|
|
258
|
+
if (page.route && (!allowPrivateTargets || (options.allowedHosts?.length ?? 0) > 0)) {
|
|
220
259
|
await page.route("**/*", async (route) => {
|
|
221
260
|
const request = route.request();
|
|
222
261
|
const isNavigation = request.isNavigationRequest?.() ?? true;
|
|
223
262
|
if (!isNavigation) {
|
|
263
|
+
if (isBlockedSubresource(request.url())) {
|
|
264
|
+
await route.abort("blockedbyclient");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
224
267
|
await route.continue();
|
|
225
268
|
return;
|
|
226
269
|
}
|
|
@@ -236,8 +279,10 @@ const createBrowser = (options) => {
|
|
|
236
279
|
if (viewport && page.setViewportSize) {
|
|
237
280
|
await page.setViewportSize(clampViewport(viewport));
|
|
238
281
|
}
|
|
239
|
-
|
|
240
|
-
|
|
282
|
+
return withDeadline(async () => {
|
|
283
|
+
await page.goto(target, { timeout, waitUntil: navigate.waitUntil ?? "load" });
|
|
284
|
+
return use(page);
|
|
285
|
+
}, timeout);
|
|
241
286
|
});
|
|
242
287
|
};
|
|
243
288
|
const screenshot = async (url, screenshotOptions = {}) => withPage(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/browser",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.7",
|
|
4
4
|
"description": "Cloudflare Browser Rendering for Lunora: ctx.browser screenshots, PDF, and scraping in actions",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"browser-rendering",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
49
|
+
"@lunora/errors": "1.0.0-alpha.4"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"@cloudflare/playwright": ">=1.0.0"
|