Skip to content

Commit

Permalink
Merge pull request #170 from http4s/merge/0.2-main
Browse files Browse the repository at this point in the history
0.2 -> main
  • Loading branch information
armanbilge authored Jul 6, 2022
2 parents fccdb4b + 1d2911a commit 55a2f02
Show file tree
Hide file tree
Showing 13 changed files with 77 additions and 49 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ jobs:
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' doc'

- name: Check scalafix lints
if: matrix.java == 'temurin@8' && !startsWith(matrix.scala, '3.')
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' ''scalafixAll --check'''

- name: Check unused compile dependencies
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' unusedCompileDependenciesTest'
Expand Down
15 changes: 15 additions & 0 deletions .scalafix.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
rules = [
Http4sFs2Linters
Http4sGeneralLinters
Http4sUseLiteralsSyntax
LeakingImplicitClassVal
ExplicitResultTypes
OrganizeImports
]

triggered.rules = [
Http4sFs2Linters
Http4sGeneralLinters
Http4sUseLiteralsSyntax
LeakingImplicitClassVal
]
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.5.2
version = 3.5.8

runner.dialect = Scala213Source3

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ Use http4s in your browser with Scala.js! Check out the [interactive examples](h

Features:

* A [`Client` implementation](fetch.md) backed by [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
* A [`WSClient` implementation](websocket.md) backed by [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket$.html)
* A [`Service Worker` integration](serviceworker.md) to install your `HttpRoutes` as a [`FetchEvent` handler](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/onfetch)
* A [`Client` implementation](https://http4s.github.io/http4s-dom/fetch.html) backed by [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
* A [`WSClient` implementation](https://http4s.github.io/http4s-dom/websocket.html) backed by [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
* A [`Service Worker` integration](https://http4s.github.io/http4s-dom/serviceworker.html) to install your `HttpRoutes` as a [`FetchEvent` handler](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/fetch_event)
* Encoders for [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File), [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) and [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)

Notably, http4s-dom can also be used to create _serverless_ apps with [Cloudflare Workers](https://workers.cloudflare.com) which have adopted the same APIs used in the browser!
Expand All @@ -18,5 +18,5 @@ It is also possible to use the `FetchClient` in Node.js v18+, which added [exper
[![http4s-dom Scala version support](https://index.scala-lang.org/http4s/http4s-dom/http4s-dom/latest.svg)](https://index.scala-lang.org/http4s/http4s-dom/http4s-dom)

```scala
libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.2"
libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.3"
```
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@ ThisBuild / Test / jsEnv := {
}
}

val catsEffectVersion = "3.3.12"
val fs2Version = "3.2.7"
val http4sVersion = "1.0.0-M33"
val catsEffectVersion = "3.3.13"
val fs2Version = "3.2.9"
val http4sVersion = "1.0.0-M34"
val scalaJSDomVersion = "2.2.0"
val circeVersion = "0.14.2"
val munitVersion = "0.7.29"
Expand Down
41 changes: 26 additions & 15 deletions docs/fetch.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@ The @:api(org.http4s.dom.FetchClientBuilder) creates a standard http4s @:api(org

## Example

```scala
libraryDependencies += "org.http4s" %%% "http4s-circe" % "@HTTP4S_VERSION@"
libraryDependencies += "io.circe" %%% "circe-generic" % "@CIRCE_VERSION@"
```

```scala mdoc:js
<div style="text-align:center">
<h3 style="padding:10px">
I'm bored.
</h3>
<button id="button">Fetch Activity</button>
<p style="padding:10px" id="activity"></p>
<div>
<h3>How many stars?</h3>
<input id="repo" size="36" type="text" value="http4s/http4s" placeholder="http4s/http4s">
<button id="button">Fetch</button>
<span id="stars" style="margin-left: 1em; color: var(--secondary-color)"><span>
</div>
---
import cats.effect._
Expand All @@ -22,17 +26,24 @@ import org.scalajs.dom._

val client = FetchClientBuilder[IO].create

val activityElement = document.getElementById("activity")

case class Activity(activity: String)

val fetchActivity: IO[Unit] = for {
_ <- IO(activityElement.innerHTML = "<i>fetching...</i>")
activity <- client.expect[Activity]("https://www.boredapi.com/api/activity")
_ <- IO(activityElement.innerHTML = activity.activity)
val repoName = document.getElementById("repo").asInstanceOf[HTMLInputElement]
val repoStars = document.getElementById("stars").asInstanceOf[HTMLElement]

case class Repo(stargazers_count: Int)

val fetchRepo: IO[Unit] = for {
_ <- IO(repoStars.innerHTML = "<i>fetching...</i>")
name <- IO(repoName.value)
repo <- client.expect[Repo](s"https://api.github.com/repos/$name").attempt
_ <- IO {
repo match {
case Right(Repo(stars)) => repoStars.innerHTML = s"$stars"
case Left(_) => repoStars.innerHTML = s"Not found :("
}
}
} yield ()

val button = document.getElementById("button").asInstanceOf[HTMLButtonElement]

button.onclick = _ => fetchActivity.unsafeRunAndForget()
button.onclick = _ => fetchRepo.unsafeRunAndForget()
```
4 changes: 0 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,4 @@ libraryDependencies += "org.http4s" %%% "http4s-dom" % "@VERSION@"

// recommended, brings in the latest client module
libraryDependencies += "org.http4s" %%% "http4s-client" % "@HTTP4S_VERSION@"

// optional, for JSON support
libraryDependencies += "org.http4s" %%% "http4s-circe" % "@HTTP4S_VERSION@"
libraryDependencies += "io.circe" %%% "circe-generic" % "@CIRCE_VERSION@"
```
17 changes: 9 additions & 8 deletions dom/src/main/scala/org/http4s/dom/FetchClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ private[dom] object FetchClient {
requestTimeout: Duration,
options: FetchOptions
)(implicit F: Async[F]): Client[F] = Client[F] { (req: Request[F]) =>
Resource.eval {
req.entity match {
case Entity.Empty => None.pure
case Entity.Strict(chunk) => Some(chunk).pure
Resource.eval(req.toStrict(None)).flatMap { req =>
val body = req.entity match {
case Entity.Empty => None
case Entity.Strict(chunk) => Some(chunk)
case default => default.body.chunkAll.filter(_.nonEmpty).compile.last
}
} flatMap { body =>

Resource
.makeCaseFull { (poll: Poll[F]) =>
F.delay(new AbortController()).flatMap { abortController =>
Expand Down Expand Up @@ -77,8 +77,9 @@ private[dom] object FetchClient {
.foreach(referrer => init.referrer = referrer.renderString)
mergedOptions.referrerPolicy.foreach(init.referrerPolicy = _)

val fetch = poll(F.fromPromise(F.delay(Fetch.fetch(req.uri.renderString, init))))
.onCancel(F.delay(abortController.abort()))
val fetch =
poll(F.fromPromise(F.delay(Fetch.fetch(req.uri.renderString, init))))
.onCancel(F.delay(abortController.abort()))

requestTimeout match {
case d: FiniteDuration =>
Expand All @@ -95,7 +96,7 @@ private[dom] object FetchClient {
case (r, exitCase) =>
OptionT.fromOption(Option(r.body)).foreachF(closeReadableStream(_, exitCase))
}
.evalMap(fromDomResponse[F])
.evalMap(fromDomResponse[F](_))

}
}
Expand Down
2 changes: 1 addition & 1 deletion dom/src/main/scala/org/http4s/dom/ServiceWorker.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import cats.effect.unsafe.IORuntime
import cats.syntax.all._
import fs2.Chunk
import org.scalajs.dom.Fetch
import org.scalajs.dom.ResponseInit
import org.scalajs.dom.FetchEvent
import org.scalajs.dom.ResponseInit
import org.scalajs.dom.ServiceWorkerGlobalScope
import org.scalajs.dom.{Response => DomResponse}
import org.typelevel.vault.Key
Expand Down
12 changes: 5 additions & 7 deletions dom/src/main/scala/org/http4s/dom/WebSocketClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import cats.effect.std.Queue
import cats.effect.std.Semaphore
import cats.effect.syntax.all._
import cats.syntax.all._
import fs2.INothing
import fs2.Stream
import org.http4s.Method
import org.http4s.client.websocket.WSClientHighLevel
Expand All @@ -51,7 +50,7 @@ object WebSocketClient {
dispatcher <- Dispatcher[F]
messages <- Queue.unbounded[F, Option[MessageEvent]].toResource
semaphore <- Semaphore[F](1).toResource
error <- F.deferred[Either[Throwable, INothing]].toResource
error <- F.deferred[Throwable].toResource
close <- F.deferred[CloseEvent].toResource
ws <- Resource.makeCase {
F.async_[WebSocket] { cb =>
Expand All @@ -69,8 +68,7 @@ object WebSocketClient {

ws.onopen = { _ =>
ws.onerror = // replace the error handler
e =>
dispatcher.unsafeRunAndForget(error.complete(Left(js.JavaScriptException(e))))
e => dispatcher.unsafeRunAndForget(error.complete(js.JavaScriptException(e)))
cb(Right(ws))
}

Expand Down Expand Up @@ -122,15 +120,15 @@ object WebSocketClient {
def receive: F[Option[WSDataFrame]] = semaphore
.permit
.surround(OptionT(messages.take).map(decodeMessage).value)
.race(error.get.rethrow)
.race(error.get.flatMap(F.raiseError[Option[WSDataFrame]]))
.map(_.merge)

override def receiveStream: Stream[F, WSDataFrame] =
Stream
.resource(semaphore.permit)
.flatMap(_ => Stream.fromQueueNoneTerminated(messages))
.map(decodeMessage)
.concurrently(Stream.exec(error.get.rethrow.widen))
.concurrently(Stream.exec(error.get.flatMap(F.raiseError)))

private def decodeMessage(e: MessageEvent): WSDataFrame =
e.data match {
Expand All @@ -156,7 +154,7 @@ object WebSocketClient {
}

private def errorOr(fu: F[Unit]): F[Unit] = error.tryGet.flatMap {
case Some(error) => F.fromEither[Unit](error)
case Some(error) => F.raiseError(error)
case None => fu
}

Expand Down
6 changes: 3 additions & 3 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val http4sVersion = "0.23.12"
val http4sVersion = "0.23.13"

enablePlugins(BuildInfoPlugin)
buildInfoKeys += "http4sVersion" -> http4sVersion
Expand All @@ -7,7 +7,7 @@ libraryDependencies += "org.scala-js" %% "scalajs-env-selenium" % "1.1.1"
libraryDependencies += "org.http4s" %% "http4s-dsl" % http4sVersion
libraryDependencies += "org.http4s" %% "http4s-blaze-server" % "0.23.12"

addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.13.2")
addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.14.3")
addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.3.2")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1")
addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.11.0")
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package org.http4s
package dom

import cats.effect.IO
import cats.effect.Resource
import org.http4s.client.Client
import org.http4s.client.testkit.ClientRouteTestBattery

class NodeJSFetchSuite extends ClientRouteTestBattery("FetchClient") {
def clientResource = FetchClientBuilder[IO].resource
def clientResource: Resource[IO, Client[IO]] = FetchClientBuilder[IO].resource
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import cats.syntax.all._
import fs2.Stream
import munit.CatsEffectSuite
import org.http4s.Method._
import org.http4s.client.Client
import org.http4s.client.dsl.io._
import org.http4s.client.testkit.testroutes.GetRoutes
import org.http4s.multipart.Multiparts
Expand All @@ -35,9 +36,9 @@ import scala.scalajs.js

class FetchServiceWorkerSuite extends CatsEffectSuite {

val client = FetchClientBuilder[IO].create
val client: Client[IO] = FetchClientBuilder[IO].create

val baseUrl = uri"/"
val baseUrl: Uri = uri"/"

test("Install service worker") {
IO.fromPromise {
Expand Down

0 comments on commit 55a2f02

Please sign in to comment.