tokwatchr 0.4.0 → 0.4.3

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.mjs CHANGED
@@ -391,21 +391,8 @@ async function downloadWithFfmpeg(options) {
391
391
  let stderrBuffer = "";
392
392
  let sizeBytes = 0;
393
393
  let duration = 0;
394
- const abortHandler = () => {
395
- proc.kill("SIGTERM");
396
- setTimeout(() => {
397
- if (proc.exitCode === null) proc.kill("SIGKILL");
398
- }, 15e3);
399
- };
400
- let aborted = false;
401
- const onAbort = () => {
402
- aborted = true;
403
- abortHandler();
404
- };
405
- signal?.addEventListener("abort", onAbort, { once: true });
406
394
  let firstDataTimer = setTimeout(() => {
407
395
  firstDataTimer = null;
408
- signal?.removeEventListener("abort", onAbort);
409
396
  proc.kill("SIGTERM");
410
397
  reject(new FfmpegError(`ffmpeg produced no output within ${timeout}ms — check the stream URL`));
411
398
  }, timeout);
@@ -439,7 +426,14 @@ async function downloadWithFfmpeg(options) {
439
426
  clearTimeout(firstDataTimer);
440
427
  firstDataTimer = null;
441
428
  }
442
- signal?.removeEventListener("abort", onAbort);
429
+ if (err.name === "AbortError") {
430
+ resolve({
431
+ sizeBytes,
432
+ duration,
433
+ format: outputPath.endsWith(".mkv") ? "mkv" : "mp4"
434
+ });
435
+ return;
436
+ }
443
437
  reject(new FfmpegError(err.message));
444
438
  });
445
439
  proc.on("close", (code) => {
@@ -447,8 +441,7 @@ async function downloadWithFfmpeg(options) {
447
441
  clearTimeout(firstDataTimer);
448
442
  firstDataTimer = null;
449
443
  }
450
- signal?.removeEventListener("abort", onAbort);
451
- if (aborted) {
444
+ if (signal?.aborted) {
452
445
  resolve({
453
446
  sizeBytes,
454
447
  duration,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokwatchr",
3
- "version": "0.4.0",
3
+ "version": "0.4.3",
4
4
  "description": "Download TikTok livestreams. Given a username, download the livestream.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.mjs",
@@ -40,6 +40,10 @@
40
40
  "stream"
41
41
  ],
42
42
  "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "git+https://github.com/zfadhli/tokwatchr.git"
46
+ },
43
47
  "devDependencies": {
44
48
  "@biomejs/biome": "^2.4.16",
45
49
  "@types/bun": "latest",
@@ -122,35 +122,12 @@ export async function downloadWithFfmpeg(
122
122
  let sizeBytes = 0;
123
123
  let duration = 0;
124
124
 
125
- const abortHandler = () => {
126
- // SIGTERM tells ffmpeg to flush its output (moov atom, etc.)
127
- // and exit cleanly. This produces a playable file even when
128
- // stopped mid-recording.
129
- proc.kill("SIGTERM");
130
- // Safety net: if ffmpeg doesn't respond, force-kill after 15s.
131
- // The close handler will still fire and resolve with partial data.
132
- setTimeout(() => {
133
- if (proc.exitCode === null) {
134
- proc.kill("SIGKILL");
135
- }
136
- }, 15_000);
137
- };
138
-
139
- let aborted = false;
140
- const onAbort = () => {
141
- aborted = true;
142
- abortHandler();
143
- };
144
-
145
- signal?.addEventListener("abort", onAbort, { once: true });
146
-
147
125
  // Startup timeout — if ffmpeg produces no stderr output within
148
126
  // `timeout` ms, it likely stalled on a bad URL. Kill it so the
149
127
  // caller doesn't hang indefinitely.
150
128
  let firstDataTimer: ReturnType<typeof setTimeout> | null = setTimeout(
151
129
  () => {
152
130
  firstDataTimer = null;
153
- signal?.removeEventListener("abort", onAbort);
154
131
  proc.kill("SIGTERM");
155
132
  reject(
156
133
  new FfmpegError(
@@ -200,7 +177,18 @@ export async function downloadWithFfmpeg(
200
177
  clearTimeout(firstDataTimer);
201
178
  firstDataTimer = null;
202
179
  }
203
- signal?.removeEventListener("abort", onAbort);
180
+ // spawn's signal option emits AbortError when the signal
181
+ // is already aborted at spawn time or fires mid-flight.
182
+ // Resolve with partial data instead of rejecting — the
183
+ // close handler will also fire and see signal?.aborted.
184
+ if (err.name === "AbortError") {
185
+ resolve({
186
+ sizeBytes,
187
+ duration,
188
+ format: outputPath.endsWith(".mkv") ? "mkv" : "mp4",
189
+ });
190
+ return;
191
+ }
204
192
  reject(new FfmpegError(err.message));
205
193
  });
206
194
 
@@ -209,12 +197,14 @@ export async function downloadWithFfmpeg(
209
197
  clearTimeout(firstDataTimer);
210
198
  firstDataTimer = null;
211
199
  }
212
- signal?.removeEventListener("abort", onAbort);
213
200
 
214
201
  // When the user aborted, resolve with whatever we got.
215
- // ffmpeg receives SIGTERM first, which tells it to flush
216
- // its output and exit cleanly, producing a playable file.
217
- if (aborted) {
202
+ // spawn's signal option sends SIGTERM, which tells ffmpeg
203
+ // to flush its output and exit cleanly.
204
+ // Use signal?.aborted directly instead of a listener flag
205
+ // to avoid a race between listener registration order and
206
+ // the async close event.
207
+ if (signal?.aborted) {
218
208
  resolve({
219
209
  sizeBytes,
220
210
  duration,