Use new web interop for node in tests
diff --git a/pubspec.yaml b/pubspec.yaml
index be54f62..63b46e2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -4,7 +4,7 @@
 repository: https://github.com/simolus3/tar/
 
 environment:
-  sdk: '>=3.0.0 <4.0.0'
+  sdk: '>=3.3.0 <4.0.0'
 
 dependencies:
   async: ^2.6.0
@@ -14,7 +14,6 @@
 dev_dependencies:
   charcode: ^1.2.0
   extra_pedantic: ^4.0.0
-  file: ^6.1.2
-  node_io: ^2.1.0
+  file: ^7.0.0
   path: ^1.8.0
   test: ^1.20.0
diff --git a/test/reader_test.dart b/test/reader_test.dart
index f0a2147..c22c84c 100644
--- a/test/reader_test.dart
+++ b/test/reader_test.dart
@@ -25,7 +25,7 @@
   test('v7', () => _testWith('reference/v7.tar', ignoreLongFileName: true));
 
   test('can skip tar files', () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
 
     expect(await reader.moveNext(), isTrue);
@@ -49,7 +49,7 @@
   });
 
   test("can't use moveNext() while a stream is active", () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
 
     expect(await reader.moveNext(), isTrue);
@@ -60,7 +60,7 @@
   });
 
   test("can't use moveNext() after canceling the reader", () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
     await reader.cancel();
 
@@ -68,7 +68,7 @@
   });
 
   test("can't read a stream multiple times", () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
     await reader.moveNext();
 
@@ -84,7 +84,7 @@
   });
 
   test("can't read a stream while a call to moveNext() is active", () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
     await reader.moveNext();
 
@@ -100,7 +100,7 @@
   });
 
   test("can't read the stream of an old tar entry", () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
     await reader.moveNext();
     final oldContents = reader.current.contents;
@@ -117,7 +117,7 @@
   });
 
   test('can cancel a stream and then read further entries', () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
     addTearDown(reader.cancel);
 
@@ -131,7 +131,7 @@
   });
 
   test('cancelling the reader closes the current subscription', () async {
-    final input = fs.file('reference/posix.tar').openRead();
+    final input = openRead('reference/posix.tar');
     final reader = TarReader(input);
 
     // Skip forward to the first actual file
@@ -181,8 +181,7 @@
     test('if the stream emits an error in content', () async {
       // Craft a stream that starts with a valid tar file, but then emits an
       // error in the middle of an entry. First 512 bytes are headers.
-      final iterator =
-          ChunkedStreamReader(fs.file('reference/v7.tar').openRead());
+      final iterator = ChunkedStreamReader(openRead('reference/v7.tar'));
       final controller = StreamController<List<int>>();
       controller.onListen = () async {
         // headers + 3 bytes of content
@@ -266,7 +265,7 @@
 
   group('tests from dart-neats PR', () {
     Stream<List<int>> open(String name) {
-      return fs.file('reference/neats_test/$name').openRead();
+      return openRead('reference/neats_test/$name');
     }
 
     final tests = [
@@ -943,8 +942,8 @@
   });
 
   test('does not read large headers', () {
-    final reader = TarReader(
-        fs.file('reference/headers/evil_large_header.tar').openRead());
+    final reader =
+        TarReader(openRead('reference/headers/evil_large_header.tar'));
 
     expect(
       reader.moveNext(),
@@ -960,27 +959,24 @@
         .having((e) => e.message, 'message', contains('Unexpected end'));
 
     test('at header', () {
-      final reader = TarReader(
-          fs.file('reference/bad/truncated_in_header.tar').openRead());
+      final reader =
+          TarReader(openRead('reference/bad/truncated_in_header.tar'));
       expect(reader.moveNext(), throwsA(expectedException));
     });
 
     test('in content', () {
-      final reader =
-          TarReader(fs.file('reference/bad/truncated_in_body.tar').openRead());
+      final reader = TarReader(openRead('reference/bad/truncated_in_body.tar'));
       expect(reader.moveNext(), throwsA(expectedException));
     });
   });
 
   test('throws for invalid utf8 in pax key', () async {
-    final reader =
-        TarReader(fs.file('reference/bad/invalid_pax_header.tar').openRead());
+    final reader = TarReader(openRead('reference/bad/invalid_pax_header.tar'));
     expect(reader.moveNext(), throwsA(isA<TarException>()));
   });
 
   test('throws for zero-length pax data', () async {
-    final reader =
-        TarReader(fs.file('reference/bad/invalid_pax_len.tar').openRead());
+    final reader = TarReader(openRead('reference/bad/invalid_pax_len.tar'));
     expect(reader.moveNext(), throwsA(isA<TarException>()));
   });
 
@@ -1046,7 +1042,7 @@
 Future<void> _testWith(String file, {bool ignoreLongFileName = false}) async {
   final entries = <String, Uint8List>{};
 
-  await TarReader.forEach(fs.file(file).openRead(), (entry) async {
+  await TarReader.forEach(openRead(file), (entry) async {
     entries[entry.name] = await entry.contents.readFully();
   });
 
@@ -1062,7 +1058,7 @@
 }
 
 Future<void> _testLargeFile(String file) async {
-  final reader = TarReader(fs.file(file).openRead());
+  final reader = TarReader(openRead(file));
   await reader.moveNext();
 
   expect(reader.current.size, 9663676416);
diff --git a/test/utils.dart b/test/utils.dart
index 0149de1..e8a385c 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -1,3 +1 @@
-export 'package:file/file.dart';
-
 export 'utils_vm.dart' if (dart.library.js) 'utils_node.dart';
diff --git a/test/utils_node.dart b/test/utils_node.dart
index 61a2af4..c8f45c4 100644
--- a/test/utils_node.dart
+++ b/test/utils_node.dart
@@ -1,7 +1,68 @@
-import 'package:file/file.dart';
+import 'dart:js_interop';
 
-import 'package:node_io/node_io.dart';
+Stream<List<int>> openRead(String path) {
+  return Stream.multi((listener) {
+    ReadStream stream;
+    try {
+      stream = fs.createReadStream(path.toJS);
+    } on Object catch (e, s) {
+      listener
+        ..addError(e, s)
+        // ignore: discarded_futures
+        ..close();
+      return;
+    }
 
-FileSystem get fs {
-  return nodeFileSystem;
+    stream.on(
+      'error'.toJS,
+      (JSObject error) {
+        listener.addErrorSync(error);
+      }.toJS,
+    );
+    stream.on(
+      'data'.toJS,
+      (JSAny event) {
+        final buffer = event as Buffer;
+        final toDart = buffer.buffer.toDart
+            .asUint8List(buffer.byteOffset.toDartInt, buffer.length.toDartInt);
+        listener.addSync(toDart);
+      }.toJS,
+    );
+    stream.on('end'.toJS, (() => listener.closeSync()).toJS);
+
+    listener
+      ..onPause = () {
+        stream.pause();
+      }
+      ..onResume = () {
+        stream.resume();
+      }
+      ..onCancel = () {
+        stream.destroy();
+      };
+  });
+}
+
+@JS()
+external JSObject require(JSString module);
+
+FileSystemModule get fs => require('fs'.toJS) as FileSystemModule;
+
+extension type FileSystemModule._(JSObject _) implements JSObject {
+  external ReadStream createReadStream(JSString path);
+}
+
+extension type ReadStream._(JSObject _) implements JSObject {
+  external void destroy();
+  external void pause();
+  external void resume();
+
+  external void on(JSString eventName, JSFunction listener);
+  external void removeAllListeners(JSString eventName);
+}
+
+extension type Buffer._(JSObject _) implements JSObject {
+  external JSArrayBuffer get buffer;
+  external JSNumber get byteOffset;
+  external JSNumber get length;
 }
diff --git a/test/utils_vm.dart b/test/utils_vm.dart
index b24f6f1..4f1320e 100644
--- a/test/utils_vm.dart
+++ b/test/utils_vm.dart
@@ -1,6 +1,6 @@
-import 'package:file/file.dart';
 import 'package:file/local.dart';
 
-FileSystem get fs {
-  return const LocalFileSystem();
+Stream<List<int>> openRead(String path) {
+  const fs = LocalFileSystem();
+  return fs.file(path).openRead();
 }