Skip to content

Commit 2ada97c

Browse files
authored
Merge pull request firecrawl#3462 from firecrawl/devin/1777532668-fix-nodejs-sdk-watcher-issues
fix(js-sdk): fix watcher duplicate events, start() Promise resolution, and done event fields
2 parents a822436 + 3c4a990 commit 2ada97c

2 files changed

Lines changed: 45 additions & 25 deletions

File tree

apps/js-sdk/firecrawl/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mendable/firecrawl-js",
3-
"version": "4.20.0",
3+
"version": "4.20.1",
44
"description": "JavaScript SDK for Firecrawl API",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

apps/js-sdk/firecrawl/src/v2/watcher.ts

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -109,24 +109,37 @@ export class Watcher extends EventEmitter {
109109
}
110110

111111
async start(): Promise<void> {
112-
try {
113-
const url = this.buildWsUrl();
114-
const wsCtor = await getWebSocketCtor();
115-
if (!wsCtor) {
116-
this.pollLoop();
117-
return;
118-
}
119-
this.ws = new wsCtor(url, this.http.getApiKey()) as any;
120-
if (this.ws && "binaryType" in this.ws) {
121-
(this.ws as any).binaryType = "arraybuffer";
122-
}
123-
124-
if (this.ws) {
125-
this.attachWsHandlers(this.ws);
126-
}
127-
} catch (err) {
128-
this.pollLoop();
129-
}
112+
return new Promise<void>((resolve, reject) => {
113+
const onDone = () => { cleanup(); resolve(); };
114+
const onError = (err: any) => { cleanup(); resolve(); };
115+
const cleanup = () => {
116+
this.removeListener("done", onDone);
117+
this.removeListener("error", onError);
118+
};
119+
this.on("done", onDone);
120+
this.on("error", onError);
121+
122+
(async () => {
123+
try {
124+
const url = this.buildWsUrl();
125+
const wsCtor = await getWebSocketCtor();
126+
if (!wsCtor) {
127+
this.pollLoop();
128+
return;
129+
}
130+
this.ws = new wsCtor(url, this.http.getApiKey()) as any;
131+
if (this.ws && "binaryType" in this.ws) {
132+
(this.ws as any).binaryType = "arraybuffer";
133+
}
134+
135+
if (this.ws) {
136+
this.attachWsHandlers(this.ws);
137+
}
138+
} catch (err) {
139+
this.pollLoop();
140+
}
141+
})();
142+
});
130143
}
131144

132145
private attachWsHandlers(ws: WebSocket) {
@@ -150,14 +163,14 @@ export class Watcher extends EventEmitter {
150163
}
151164
if (type === "document") {
152165
const doc = body.data;
153-
if (doc) this.emit("document", doc as Document & { id: string });
166+
if (doc) this.emitDocuments([doc]);
154167
return;
155168
}
156169
if (type === "done") {
157170
const payload = body.data || body;
158171
const data = (payload.data || []) as Document[];
159172
if (data.length) this.emitDocuments(data);
160-
this.emit("done", { status: "completed", data, id: this.jobId });
173+
this.emit("done", { status: "completed", data, id: this.jobId, total: payload.total, completed: payload.completed, creditsUsed: payload.creditsUsed });
161174
this.close();
162175
return;
163176
}
@@ -166,7 +179,10 @@ export class Watcher extends EventEmitter {
166179
} catch {
167180
// ignore
168181
}
169-
if (timeoutMs && Date.now() - startTs > timeoutMs) this.close();
182+
if (timeoutMs && Date.now() - startTs > timeoutMs) {
183+
this.emit("error", { status: "failed", data: [], error: "Watcher timeout", id: this.jobId });
184+
this.close();
185+
}
170186
};
171187
ws.onerror = () => {
172188
this.emit("error", { status: "failed", data: [], error: "WebSocket error", id: this.jobId });
@@ -227,7 +243,7 @@ export class Watcher extends EventEmitter {
227243
};
228244
this.emit("snapshot", snap);
229245
if (["completed", "failed", "cancelled"].includes(status)) {
230-
this.emit("done", { status, data, id: this.jobId });
246+
this.emit("done", { status, data, id: this.jobId, total: payload.total ?? 0, completed: payload.completed ?? 0, creditsUsed: payload.creditsUsed });
231247
this.close();
232248
}
233249
}
@@ -243,14 +259,18 @@ export class Watcher extends EventEmitter {
243259
this.emitDocuments((snap.data || []) as Document[]);
244260
this.emit("snapshot", snap);
245261
if (["completed", "failed", "cancelled"].includes(snap.status)) {
246-
this.emit("done", { status: snap.status, data: snap.data, id: this.jobId });
262+
this.emit("done", { status: snap.status, data: snap.data, id: this.jobId, total: (snap as any).total ?? 0, completed: (snap as any).completed ?? 0, creditsUsed: (snap as any).creditsUsed });
247263
this.close();
248264
break;
249265
}
250266
} catch {
251267
// ignore polling errors
252268
}
253-
if (timeoutMs && Date.now() - startTs > timeoutMs) break;
269+
if (timeoutMs && Date.now() - startTs > timeoutMs) {
270+
this.emit("error", { status: "failed", data: [], error: "Watcher timeout", id: this.jobId });
271+
this.close();
272+
break;
273+
}
254274
await new Promise((r) => setTimeout(r, Math.max(1000, this.pollInterval * 1000)));
255275
}
256276
}

0 commit comments

Comments
 (0)