From 9e08dcd32039ada6e0cec9eee72366e964ba5410 Mon Sep 17 00:00:00 2001 From: Vitaly Lavrov Date: Tue, 13 Oct 2026 00:30:31 +0200 Subject: [PATCH 1/2] Cross-publish protocol for Scala.js --- build.mill | 26 +++++++++++++------ .../lavrov/bittorrent/app/protocol.scala | 15 +++++++++-- server/src/main/scala/SocketSession.scala | 16 +++++++----- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/build.mill b/build.mill index 3301848..9c395ff 100644 --- a/build.mill +++ b/build.mill @@ -1,17 +1,27 @@ //| mill-version: 1.1.8 //| mill-jvm-version: temurin:21.0.6 -import mill.*, scalalib.*, publish.* +import mill.*, scalalib.*, scalajslib.*, publish.* -object protocol extends CommonSettings { - def mvnDeps = Seq( - Deps.bittorrent.common, - Deps.upickle, - Deps.`scodec-bits`, - ) +object protocol { + object jvm extends CommonSettings, PlatformScalaModule { + override def scalaVersion = "3.3.6" + def mvnDeps = Seq( + Deps.upickle, + Deps.`scodec-bits`, + ) + } + object js extends CommonSettings, ScalaJSModule, PlatformScalaModule { + override def scalaVersion = "3.3.6" + def scalaJSVersion = "1.20.1" + def mvnDeps = Seq( + Deps.upickle, + Deps.`scodec-bits`, + ) + } } object server extends CommonSettings { - def moduleDeps = Seq(protocol) + def moduleDeps = Seq(protocol.jvm) def mvnDeps = Seq( Deps.bittorrent.bittorrent, Deps.bittorrent.dht, diff --git a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala index a29cd95..18f7e70 100644 --- a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala +++ b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala @@ -1,9 +1,20 @@ package com.github.lavrov.bittorrent.app.protocol -import com.github.torrentdam.bittorrent.InfoHash import scodec.bits.ByteVector import upickle.default.{macroRW, ReadWriter} +final case class InfoHash(bytes: ByteVector) { + override def toString: String = bytes.toHex +} + +object InfoHash { + + def fromString(source: String): Option[InfoHash] = + ByteVector.fromHex(source).toOption + .filter(_.length == 20) + .map(InfoHash.apply) +} + sealed trait Message sealed trait Command extends Message sealed trait Event extends Message @@ -40,4 +51,4 @@ object Message { macroRW[TorrentError], macroRW[TorrentStats], ) -} \ No newline at end of file +} diff --git a/server/src/main/scala/SocketSession.scala b/server/src/main/scala/SocketSession.scala index 8a2e56b..3414d12 100644 --- a/server/src/main/scala/SocketSession.scala +++ b/server/src/main/scala/SocketSession.scala @@ -4,6 +4,7 @@ import cats.effect.std.Queue import cats.effect.std.Supervisor import cats.implicits.* import com.github.torrentdam.bittorrent.InfoHash +import com.github.lavrov.bittorrent.app.protocol import com.github.lavrov.bittorrent.app.protocol.{Message, Event, Command} import fs2.Stream import org.legogroup.woof.{Logger, given} @@ -23,7 +24,7 @@ object SocketSession { makeTorrent: MakeTorrent, metadataRegistry: MetadataRegistry[IO], torrentIndex: TorrentIndex, - webSocketBuilder: WebSocketBuilder[IO] + webSocketBuilder: WebSocketBuilder[IO] )(using F: Concurrent[IO], logger: Logger[IO] @@ -86,6 +87,7 @@ object SocketSession { yield () private def handleRequestTorrent(infoHash: InfoHash, trackers: List[String]): IO[Unit] = + val messageInfoHash = protocol.InfoHash(infoHash.bytes) supervisor.supervise( makeTorrent(infoHash, trackers) .use { getTorrent => @@ -96,7 +98,7 @@ object SocketSession { .flatMap { (phase: ServerTorrent.Phase.FetchingMetadata) => phase.fromPeers.discrete .evalTap { count => - send(Message.TorrentPeersDiscovered(infoHash, count)) + send(Message.TorrentPeersDiscovered(messageInfoHash, count)) } .interruptWhen(phase.done.void.attempt) .compile @@ -106,20 +108,20 @@ object SocketSession { .flatMap { (phase: ServerTorrent.Phase.Ready) => val metadata = phase.serverTorrent.metadata.parsed val files = metadata.files.map(f => Message.File(f.path, f.length)) - send(Message.TorrentMetadataReceived(infoHash, metadata.name, files)) >> + send(Message.TorrentMetadataReceived(messageInfoHash, metadata.name, files)) >> phase.serverTorrent.pure[IO] } .timeout(5.minutes) .flatMap { torrent => - sendTorrentStats(infoHash, torrent) + sendTorrentStats(messageInfoHash, torrent) } } .orElse { - send(Message.TorrentError(infoHash, "Could not fetch metadata")) + send(Message.TorrentError(messageInfoHash, "Could not fetch metadata")) } ).void - private def sendTorrentStats(infoHash: InfoHash, torrent: ServerTorrent): IO[Nothing] = + private def sendTorrentStats(infoHash: protocol.InfoHash, torrent: ServerTorrent): IO[Nothing] = val sendStats = for stats <- torrent.stats @@ -150,7 +152,7 @@ object SocketSession { supervisor ) } - + trait MakeTorrent { def apply(infoHash: InfoHash, trackers: List[String]): Resource[IO, IO[ServerTorrent.Phase.PeerDiscovery]] } From 7e4ca8ae4fdadfe4b798701af704fa9ace1c5db5 Mon Sep 17 00:00:00 2001 From: Vibe Nuage Agent Date: Mon, 12 Oct 2026 22:55:23 +0000 Subject: [PATCH 2/2] Fix Mill module discovery and scodec-bits 1.2.4 compile error - Make 'protocol' a mill.Module so nested .jvm/.js submodules are discovered - ByteVector.fromHex already returns Option in scodec-bits 1.2.4; drop .toOption - Move -Xkind-projector:underscores to server module only (protocol uses Scala 3.3.6) Co-authored-by: lavrov --- build.mill | 6 ++++-- .../scala/com/github/lavrov/bittorrent/app/protocol.scala | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/build.mill b/build.mill index 9c395ff..de4b8fd 100644 --- a/build.mill +++ b/build.mill @@ -2,7 +2,7 @@ //| mill-jvm-version: temurin:21.0.6 import mill.*, scalalib.*, scalajslib.*, publish.* -object protocol { +object protocol extends Module { object jvm extends CommonSettings, PlatformScalaModule { override def scalaVersion = "3.3.6" def mvnDeps = Seq( @@ -22,6 +22,9 @@ object protocol { object server extends CommonSettings { def moduleDeps = Seq(protocol.jvm) + def scalacOptions = Seq( + "-Xkind-projector:underscores", + ) def mvnDeps = Seq( Deps.bittorrent.bittorrent, Deps.bittorrent.dht, @@ -41,7 +44,6 @@ trait CommonSettings extends ScalaModule, PublishModule { def scalaVersion = "3.8.3" def scalacOptions = Seq( "-source:future", - "-Xkind-projector:underscores", ) def sources = Task.Sources( moduleDir / "src" / "main" / "scala" diff --git a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala index 18f7e70..5fd7026 100644 --- a/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala +++ b/protocol/src/main/scala/com/github/lavrov/bittorrent/app/protocol.scala @@ -10,7 +10,7 @@ final case class InfoHash(bytes: ByteVector) { object InfoHash { def fromString(source: String): Option[InfoHash] = - ByteVector.fromHex(source).toOption + ByteVector.fromHex(source) .filter(_.length == 20) .map(InfoHash.apply) }