Skip to content

Commit b5e1006

Browse files
committed
feat: NextJS Utils
1 parent 585df90 commit b5e1006

File tree

5 files changed

+142
-58
lines changed

5 files changed

+142
-58
lines changed

README.md

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ export async function POST(req: Request) {
121121
}
122122
```
123123

124+
## Advanced Usage
125+
124126
## Handling errors
125127

126128
When the library is unable to connect to the API,
@@ -197,8 +199,6 @@ On timeout, an `APIConnectionTimeoutError` is thrown.
197199

198200
Note that requests which time out will be [retried twice by default](#retries).
199201

200-
## Advanced Usage
201-
202202
### Accessing raw Response data (e.g., headers)
203203

204204
The "raw" `Response` returned by `fetch()` can be accessed through the `.asResponse()` method on the `APIPromise` type that all methods return.
@@ -277,49 +277,6 @@ const client = new BrowserUse({
277277
});
278278
```
279279

280-
### Making custom/undocumented requests
281-
282-
This library is typed for convenient access to the documented API. If you need to access undocumented
283-
endpoints, params, or response properties, the library can still be used.
284-
285-
#### Undocumented endpoints
286-
287-
To make requests to undocumented endpoints, you can use `client.get`, `client.post`, and other HTTP verbs.
288-
Options on the client, such as retries, will be respected when making these requests.
289-
290-
```ts
291-
await client.post('/some/path', {
292-
body: { some_prop: 'foo' },
293-
query: { some_query_arg: 'bar' },
294-
});
295-
```
296-
297-
#### Undocumented request params
298-
299-
To make requests using undocumented parameters, you may use `// @ts-expect-error` on the undocumented
300-
parameter. This library doesn't validate at runtime that the request matches the type, so any extra values you
301-
send will be sent as-is.
302-
303-
```ts
304-
client.tasks.create({
305-
// ...
306-
// @ts-expect-error baz is not yet public
307-
baz: 'undocumented option',
308-
});
309-
```
310-
311-
For requests with the `GET` verb, any extra params will be in the query, all other requests will send the
312-
extra param in the body.
313-
314-
If you want to explicitly send an extra argument, you can do so with the `query`, `body`, and `headers` request
315-
options.
316-
317-
#### Undocumented response properties
318-
319-
To access undocumented response properties, you may access the response object with `// @ts-expect-error` on
320-
the response object, or cast the response object to the requisite type. Like the request params, we do not
321-
validate or strip extra properties from the response from the API.
322-
323280
### Customizing the fetch client
324281

325282
By default, this library expects a global `fetch` function is defined.
@@ -401,18 +358,6 @@ const client = new BrowserUse({
401358

402359
## Frequently Asked Questions
403360

404-
## Semantic versioning
405-
406-
This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) conventions, though certain backwards-incompatible changes may be released as minor versions:
407-
408-
1. Changes that only affect static types, without breaking runtime behavior.
409-
2. Changes to library internals which are technically public but not intended or documented for external use. _(Please open a GitHub issue to let us know if you are relying on such internals.)_
410-
3. Changes that we do not expect to impact the vast majority of users in practice.
411-
412-
We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience.
413-
414-
We are keen for your feedback; please open an [issue](https://www.github.com/browser-use/browser-use-node/issues) with questions, bugs, or suggestions.
415-
416361
## Requirements
417362

418363
TypeScript >= 4.9 is supported.

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@swc/jest": "^0.2.29",
4242
"@types/jest": "^29.4.0",
4343
"@types/node": "^24.3.0",
44+
"@types/react": "^19.1.10",
4445
"@typescript-eslint/eslint-plugin": "8.31.1",
4546
"@typescript-eslint/parser": "8.31.1",
4647
"commander": "^14.0.0",
@@ -51,6 +52,8 @@
5152
"jest": "^29.4.0",
5253
"prettier": "^3.0.0",
5354
"publint": "^0.2.12",
55+
"react": "^19.1.1",
56+
"react-dom": "^19.1.1",
5457
"ts-jest": "^29.1.0",
5558
"ts-node": "^10.5.0",
5659
"tsc-multi": "https://github.com/stainless-api/tsc-multi/releases/download/v1.1.9/tsc-multi.tgz",
@@ -61,7 +64,8 @@
6164
"zod": "^4.0.17"
6265
},
6366
"peerDependencies": {
64-
"zod": "^4.0.17"
67+
"react": "^18 || ^19",
68+
"zod": "^4"
6569
},
6670
"exports": {
6771
".": {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { useEffect, useState } from 'react';
2+
import type { ZodType } from 'zod';
3+
4+
import type { BrowserUseEvent } from '../server/utils';
5+
import { TaskViewWithSchema } from '../../parse';
6+
7+
/**
8+
* A hook to stream Browser Use updates to the client.
9+
*/
10+
export function useBrowserUse<T extends ZodType = ZodType>(route: string): TaskViewWithSchema<T> | null {
11+
const [status, setStatus] = useState<TaskViewWithSchema<T> | null>(null);
12+
13+
useEffect(() => {
14+
const es = new EventSource(route);
15+
16+
es.addEventListener('status', (e) => {
17+
if (e instanceof MessageEvent) {
18+
const msg = JSON.parse(e.data) as BrowserUseEvent<T>;
19+
20+
setStatus(msg.data);
21+
22+
if (msg.data.status === 'finished') {
23+
es.close();
24+
}
25+
} else {
26+
console.error('Event is not a MessageEvent', e);
27+
}
28+
});
29+
30+
es.addEventListener('end', () => es.close());
31+
es.addEventListener('error', () => es.close());
32+
33+
return () => es.close();
34+
}, [route]);
35+
36+
return status;
37+
}

src/lib/nextjs/server/utils.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { TaskViewWithSchema } from '../../parse';
2+
import { ZodType } from 'zod';
3+
4+
export type BrowserUseEvent<T extends ZodType = ZodType> = {
5+
event: 'status';
6+
data: TaskViewWithSchema<T>;
7+
};
8+
9+
/**
10+
* Convert an async generator to a stream.
11+
*
12+
* @param gen - The async generator to convert to a stream.
13+
* @returns A stream of the async generator.
14+
*/
15+
export function gtos(
16+
gen: AsyncGenerator<{
17+
event: 'status';
18+
data: TaskViewWithSchema<ZodType>;
19+
}>,
20+
opts?: {
21+
/**
22+
* Called when an event is emitted.
23+
*/
24+
onEvent?: (event: TaskViewWithSchema<ZodType>) => void;
25+
26+
/**
27+
* Called when the task is finished.
28+
*/
29+
onFinished?: (event: TaskViewWithSchema<ZodType>) => void;
30+
},
31+
): ReadableStream<Uint8Array<ArrayBufferLike>> {
32+
const enc = new TextEncoder();
33+
34+
const stream = new ReadableStream<Uint8Array>({
35+
async start(controller) {
36+
// open the SSE stream quickly
37+
controller.enqueue(enc.encode(': connected\n\n'));
38+
39+
try {
40+
for await (const msg of gen) {
41+
opts?.onEvent?.(msg.data);
42+
43+
const data: BrowserUseEvent = {
44+
event: msg.event,
45+
data: msg.data,
46+
};
47+
48+
const encoded = JSON.stringify(data);
49+
50+
const payload = `event: ${msg.event}\ndata: ${encoded}\n\n`;
51+
52+
controller.enqueue(enc.encode(payload));
53+
54+
if (msg.data.status === 'finished') {
55+
opts?.onFinished?.(msg.data);
56+
}
57+
}
58+
59+
controller.enqueue(enc.encode('event: end\ndata: {}\n\n'));
60+
} catch (e) {
61+
controller.enqueue(enc.encode(`event: error\ndata: ${JSON.stringify({ message: String(e) })}\n\n`));
62+
} finally {
63+
controller.close();
64+
}
65+
},
66+
});
67+
68+
return stream;
69+
}

yarn.lock

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,13 @@
991991
dependencies:
992992
undici-types "~7.10.0"
993993

994+
"@types/react@^19.1.10":
995+
version "19.1.10"
996+
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.10.tgz#a05015952ef328e1b85579c839a71304b07d21d9"
997+
integrity sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==
998+
dependencies:
999+
csstype "^3.0.2"
1000+
9941001
"@types/stack-utils@^2.0.0":
9951002
version "2.0.3"
9961003
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8"
@@ -1475,6 +1482,11 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6:
14751482
shebang-command "^2.0.0"
14761483
which "^2.0.1"
14771484

1485+
csstype@^3.0.2:
1486+
version "3.1.3"
1487+
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
1488+
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
1489+
14781490
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.7:
14791491
version "4.4.1"
14801492
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
@@ -2984,11 +2996,23 @@ queue-microtask@^1.2.2:
29842996
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
29852997
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
29862998

2999+
react-dom@^19.1.1:
3000+
version "19.1.1"
3001+
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
3002+
integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==
3003+
dependencies:
3004+
scheduler "^0.26.0"
3005+
29873006
react-is@^18.0.0:
29883007
version "18.3.1"
29893008
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
29903009
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
29913010

3011+
react@^19.1.1:
3012+
version "19.1.1"
3013+
resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
3014+
integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==
3015+
29923016
readable-stream@^3.4.0:
29933017
version "3.6.2"
29943018
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
@@ -3063,6 +3087,11 @@ safe-buffer@~5.2.0:
30633087
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
30643088
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
30653089

3090+
scheduler@^0.26.0:
3091+
version "0.26.0"
3092+
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.26.0.tgz#4ce8a8c2a2095f13ea11bf9a445be50c555d6337"
3093+
integrity sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==
3094+
30663095
semver@^6.3.0, semver@^6.3.1:
30673096
version "6.3.1"
30683097
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"

0 commit comments

Comments
 (0)