Skip to content

Commit

Permalink
Merge pull request #151 from http4s/merge-0.2-main-20220525
Browse files Browse the repository at this point in the history
Merge 0.2 -> main
  • Loading branch information
armanbilge authored May 26, 2022
2 parents 60d5b39 + 15a5373 commit fccdb4b
Show file tree
Hide file tree
Showing 18 changed files with 184 additions and 110 deletions.
40 changes: 24 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
scala: [3.1.1, 2.13.8]
scala: [3.1.2, 2.13.8]
java: [temurin@8]
browser: [Chrome, Firefox]
jsenv: [Chrome, Firefox, NodeJS]
exclude:
- scala: 3.1.1
browser: Firefox
- scala: 3.1.2
jsenv: Firefox
- scala: 3.1.2
jsenv: NodeJS
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (full)
Expand Down Expand Up @@ -70,41 +72,47 @@ jobs:
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}

- name: Setup NodeJS v18
if: matrix.jsenv == 'NodeJS'
uses: actions/setup-node@v2
with:
node-version: 18

- name: Check that workflows are up to date
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' ''project /'' githubWorkflowCheck'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' ''project /'' githubWorkflowCheck'

- name: Check headers and formatting
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' headerCheckAll scalafmtCheckAll ''project /'' scalafmtSbtCheck'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' headerCheckAll scalafmtCheckAll ''project /'' scalafmtSbtCheck'

- name: Test
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' test'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' test'

- name: Check binary compatibility
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' mimaReportBinaryIssues'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' mimaReportBinaryIssues'

- name: Generate API documentation
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' doc'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' doc'

- name: Check unused compile dependencies
if: matrix.java == 'temurin@8'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.browser }}'' unusedCompileDependenciesTest'
run: 'sbt ''++${{ matrix.scala }}'' ''set Global / useJSEnv := JSEnv.${{ matrix.jsenv }}'' unusedCompileDependenciesTest'

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p target site/target tests/target dom/target jsdocs/target project/target
run: mkdir -p target site/target tests/target dom/target tests-nodejs/target jsdocs/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar target site/target tests/target dom/target jsdocs/target project/target
run: tar cf targets.tar target site/target tests/target dom/target tests-nodejs/target jsdocs/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
uses: actions/upload-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.browser }}
name: target-${{ matrix.os }}-${{ matrix.java }}-${{ matrix.scala }}-${{ matrix.jsenv }}
path: targets.tar

publish:
Expand Down Expand Up @@ -151,12 +159,12 @@ jobs:
~/Library/Caches/Coursier/v1
key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }}

- name: Download target directories (3.1.1, Chrome)
- name: Download target directories (3.1.2, Chrome)
uses: actions/download-artifact@v2
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.1-Chrome
name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.2-Chrome

- name: Inflate target directories (3.1.1, Chrome)
- name: Inflate target directories (3.1.2, Chrome)
run: |
tar xf targets.tar
rm targets.tar
Expand Down
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.4.3
version = 3.5.2

runner.dialect = Scala213Source3

Expand Down
6 changes: 3 additions & 3 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Everyone is expected to follow the [Scala Code of Conduct] when discussing the p

## Moderation

For any questions, concerns, or moderation requests, please contact a member of the [community staff](https://http4s.org/code-of-conduct/#moderation)
For any questions, concerns, or moderation requests, please contact a member of the [community staff](https://http4s.org/code-of-conduct.html#moderation)

[Scala Code of Conduct]: https://http4s.org/code-of-conduct/
[Community staff]: https://http4s.org/code-of-conduct/#moderation
[Scala Code of Conduct]: https://http4s.org/code-of-conduct.html
[Community staff]: https://http4s.org/code-of-conduct.html#moderation
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ Features:

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!

It is also possible to use the `FetchClient` in Node.js v18+, which added [experimental support](https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental) for `fetch`, although some browser-specific features may not be available.

### Usage

[![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
// Supports http4s 0.23.x
libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.1"
libraryDependencies += "org.http4s" %%% "http4s-dom" % "0.2.2"
```
98 changes: 81 additions & 17 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.firefox.FirefoxOptions
import org.scalajs.jsenv.nodejs.NodeJSEnv
import org.scalajs.jsenv.selenium.SeleniumJSEnv

import JSEnv._
Expand All @@ -28,24 +29,33 @@ ThisBuild / developers := List(
tlGitHubDev("armanbilge", "Arman Bilge")
)
ThisBuild / startYear := Some(2021)
ThisBuild / tlSiteApiUrl := Some(url(
"https://www.javadoc.io/doc/org.http4s/http4s-dom_sjs1_2.13/latest/org/http4s/dom/index.html"))

ThisBuild / githubWorkflowTargetBranches := Seq("main")
ThisBuild / tlCiReleaseBranches := Seq("main")
ThisBuild / tlSitePublishBranch := Some("series/0.2")

ThisBuild / crossScalaVersions := Seq("3.1.1", "2.13.8")
ThisBuild / crossScalaVersions := Seq("3.1.2", "2.13.8")
ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("8"))
ThisBuild / githubWorkflowBuildMatrixAdditions += "browser" -> List("Chrome", "Firefox")
ThisBuild / githubWorkflowBuildSbtStepPreamble += s"set Global / useJSEnv := JSEnv.$${{ matrix.browser }}"

val jsEnvs = List("Chrome", "Firefox", "NodeJS")
ThisBuild / githubWorkflowBuildMatrixAdditions += "jsenv" -> jsEnvs
ThisBuild / githubWorkflowBuildSbtStepPreamble += s"set Global / useJSEnv := JSEnv.$${{ matrix.jsenv }}"
ThisBuild / githubWorkflowBuildMatrixExclusions ++= {
for {
scala <- (ThisBuild / crossScalaVersions).value.init
} yield MatrixExclude(Map("scala" -> scala, "browser" -> "Firefox"))
jsenv <- jsEnvs.tail
} yield MatrixExclude(Map("scala" -> scala, "jsenv" -> jsenv))
}

lazy val useJSEnv = settingKey[JSEnv]("Browser for running Scala.js tests")
ThisBuild / githubWorkflowBuildPreamble +=
WorkflowStep.Use(
UseRef.Public("actions", "setup-node", "v2"),
name = Some("Setup NodeJS v18"),
params = Map("node-version" -> "18"),
cond = Some("matrix.jsenv == 'NodeJS'")
)

lazy val useJSEnv = settingKey[JSEnv]("JSEnv for running Scala.js tests")
Global / useJSEnv := Chrome

lazy val fileServicePort = settingKey[Int]("Port for static file server")
Expand Down Expand Up @@ -98,6 +108,7 @@ ThisBuild / Test / jsEnv := {
s"http://localhost:${fileServicePort.value}/target/selenium/")

useJSEnv.value match {
case NodeJS => new NodeJSEnv()
case Chrome =>
val options = new ChromeOptions()
options.setHeadless(true)
Expand All @@ -109,16 +120,16 @@ ThisBuild / Test / jsEnv := {
}
}

val catsEffectVersion = "3.3.8"
val fs2Version = "3.2.5"
val http4sVersion = "1.0.0-M32"
val scalaJSDomVersion = "2.1.0"
val circeVersion = "0.15.0-M1"
val catsEffectVersion = "3.3.12"
val fs2Version = "3.2.7"
val http4sVersion = "1.0.0-M33"
val scalaJSDomVersion = "2.2.0"
val circeVersion = "0.14.2"
val munitVersion = "0.7.29"
val munitCEVersion = "1.0.7"

lazy val root =
project.in(file(".")).aggregate(dom, tests).enablePlugins(NoPublishPlugin)
project.in(file(".")).aggregate(dom, tests, nodeJSTests).enablePlugins(NoPublishPlugin)

lazy val dom = project
.in(file("dom"))
Expand All @@ -138,17 +149,51 @@ lazy val tests = project
.in(file("tests"))
.settings(
scalaJSUseMainModuleInitializer := true,
(Test / test) := (Test / test).dependsOn(Compile / fastOptJS).value,
buildInfoKeys := Seq[BuildInfoKey](scalaVersion, fileServicePort),
Test / test := {
if (useJSEnv.value != NodeJS)
(Test / test).dependsOn(Compile / fastOptJS).value
else
()
},
buildInfoKeys := Seq[BuildInfoKey](
fileServicePort,
BuildInfoKey(
"workerDir" -> (Compile / fastLinkJS / scalaJSLinkerOutputDirectory)
.value
.relativeTo((ThisBuild / baseDirectory).value)
.get
.toString
)
),
buildInfoPackage := "org.http4s.dom",
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-client-testkit" % http4sVersion,
"org.scalameta" %%% "munit" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test
)
)
.dependsOn(dom)
.enablePlugins(ScalaJSPlugin, BuildInfoPlugin, NoPublishPlugin)

lazy val nodeJSTests = project
.in(file("tests-nodejs"))
.settings(
Test / test := {
if (useJSEnv.value == NodeJS)
(Test / test).value
else
()
},
libraryDependencies ++= Seq(
"org.http4s" %%% "http4s-client-testkit" % http4sVersion,
"org.scalameta" %%% "munit" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect-3" % munitCEVersion % Test
),
scalaJSLinkerConfig ~= (_.withModuleKind(ModuleKind.CommonJSModule))
)
.dependsOn(dom)
.enablePlugins(ScalaJSPlugin, NoPublishPlugin)

lazy val jsdocs =
project
.dependsOn(dom)
Expand All @@ -164,14 +209,33 @@ lazy val jsdocs =
lazy val docs = project
.in(file("site"))
.settings(
tlSiteApiModule := Some((dom / projectID).value),
tlSiteApiPackage := Some("org.http4s.dom"),
tlFatalWarningsInCi := false,
mdocJS := Some(jsdocs),
tlSiteRelatedProjects ++= Seq(
"calico" -> url("https://armanbilge.github.io/calico/")
),
mdocVariables ++= Map(
"js-opt" -> "fast",
"HTTP4S_VERSION" -> http4sVersion,
"CIRCE_VERSION" -> circeVersion
),
laikaConfig ~= { _.withRawContent },
laikaConfig := {
import laika.rewrite.link._
laikaConfig
.value
.withRawContent
.withConfigValue(LinkConfig(apiLinks = List(
ApiLinks(
baseUri =
s"https://www.javadoc.io/doc/org.http4s/http4s-dom_sjs1_2.13/${mdocVariables.value("VERSION")}/",
packagePrefix = "org.http4s.dom"),
ApiLinks(
baseUri = s"https://www.javadoc.io/doc/org.http4s/http4s-docs_2.13/$http4sVersion/",
packagePrefix = "org.http4s"
)
)))
},
tlSiteHeliumConfig ~= {
// Actually, this *disables* auto-linking, to avoid duplicates with mdoc
_.site.autoLinkJS()
Expand Down
4 changes: 2 additions & 2 deletions docs/fetch.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Fetch Client

The [`FetchClientBuilder`](https://www.javadoc.io/doc/org.http4s/http4s-dom_sjs1_2.13/latest/org/http4s/dom/FetchClientBuilder.html) creates a standard http4s [`Client`](https://http4s.org/v0.23/api/org/http4s/client/client) that is described in the [http4s documentation](https://http4s.org/v0.23/client/).
The @:api(org.http4s.dom.FetchClientBuilder) creates a standard http4s @:api(org.http4s.client.Client) that is described in the [http4s documentation](https://http4s.org/v0.23/docs/client.html).

## Example

Expand Down Expand Up @@ -32,7 +32,7 @@ val fetchActivity: IO[Unit] = for {
_ <- IO(activityElement.innerHTML = activity.activity)
} yield ()

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

button.onclick = _ => fetchActivity.unsafeRunAndForget()
```
7 changes: 4 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ Use http4s in your browser with Scala.js!
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 [`WSClient` implementation](websocket.md) backed by [`WebSocket`](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
* A [`Service Worker` integration](serviceworker.md) 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!

It is also possible to use the `FetchClient` in Node.js v18+, which added [experimental support](https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental) for `fetch`, although some browser-specific features may not be available.

## Installation

[![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
// Supports http4s 0.23.x and scala-js-dom 2.x
libraryDependencies += "org.http4s" %%% "http4s-dom" % "@VERSION@"

// recommended, brings in the latest client module
Expand Down
2 changes: 1 addition & 1 deletion docs/serviceworker.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Service Worker "Server"

The [`ServiceWorker`](https://www.javadoc.io/doc/org.http4s/http4s-dom_sjs1_2.13/latest/org/http4s/dom/ServiceWorker$.html) `FetchEvent` listener integrates directly with [http4s services](https://http4s.org/v0.23/service/). You can use it to run a "proxy server" as a background process in the browser that can intercept and respond to requests made by the `FetchClient`.
The @:api(org.http4s.dom.ServiceWorker$) `FetchEvent` listener integrates directly with [http4s services](https://http4s.org/v0.23/docs/service.html). You can use it to run a "proxy server" as a background process in the browser that can intercept and respond to requests made by the `FetchClient`.

## Example

Expand Down
12 changes: 6 additions & 6 deletions docs/websocket.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# WebSocket Client

The [`WebSocketClient`](https://www.javadoc.io/doc/org.http4s/http4s-dom_sjs1_2.13/latest/org/http4s/dom/WSClient$.html) creates a standard http4s [`WSClientHighLevel`](https://http4s.org/v0.23/api/org/http4s/client/client).
The @:api(org.http4s.dom.WebSocketClient$) implements the http4s @:api(org.http4s.client.websocket.WSClientHighLevel) interface.

## Example

Expand Down Expand Up @@ -28,15 +28,15 @@ import org.http4s.dom._
import org.http4s.syntax.all._
import org.scalajs.dom._

val message = document.getElementById("message").asInstanceOf[html.Input]
val button = document.getElementById("button").asInstanceOf[html.Button]
val sent = document.getElementById("sent").asInstanceOf[html.Element]
val received = document.getElementById("received").asInstanceOf[html.Element]
val message = document.getElementById("message").asInstanceOf[HTMLInputElement]
val button = document.getElementById("button").asInstanceOf[HTMLButtonElement]
val sent = document.getElementById("sent").asInstanceOf[HTMLElement]
val received = document.getElementById("received").asInstanceOf[HTMLElement]

val request = WSRequest(uri"wss://ws.postman-echo.com/raw")
val app = WebSocketClient[IO].connectHighLevel(request).use { conn =>

def log(e: html.Element, text: String): IO[Unit] =
def log(e: HTMLElement, text: String): IO[Unit] =
IO {
val p = document.createElement("p")
p.innerHTML = text
Expand Down
3 changes: 2 additions & 1 deletion dom/src/main/scala/org/http4s/dom/FetchClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.http4s
package dom

import cats.data.OptionT
import cats.effect.Async
import cats.effect.Poll
import cats.effect.Resource
Expand Down Expand Up @@ -92,7 +93,7 @@ private[dom] object FetchClient {
}
} {
case (r, exitCase) =>
closeReadableStream(r.body, exitCase)
OptionT.fromOption(Option(r.body)).foreachF(closeReadableStream(_, exitCase))
}
.evalMap(fromDomResponse[F])

Expand Down
Loading

0 comments on commit fccdb4b

Please sign in to comment.