diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index b123ff6..e32d98d 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -108,6 +108,11 @@ resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.5.tgz#cdba698d3d65087643130f9af43a2b622ce0b372" integrity sha512-OhXt2lS//WpLdkqrzo/KwB7SRD8AiNTFFzuo9n14IBupzIMa67yGItcK7I2W9D8Ghpa4T04Sw9FWsKCJG50Bxw== +"@mapbox/tiny-sdf@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-2.0.6.tgz#9a1d33e5018093e88f6a4df2343e886056287282" + integrity sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA== + "@mapbox/unitbezier@^0.0.0": version "0.0.0" resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e" @@ -1900,6 +1905,11 @@ kdbush@^3.0.0: resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0" integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew== +kdbush@^4.0.1, kdbush@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39" + integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA== + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -1991,16 +2001,16 @@ mapbox-gl@2.10.0: tinyqueue "^2.0.3" vt-pbf "^3.1.3" -mapbox-gl@2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-2.12.0.tgz#e9df28e72adc2f5b5999007e836df72c2f189e10" - integrity sha512-T60fbDV1ULikrhwPdRbpeQOF02+Nhv7QgJ4uGfKWybkoH+DdCxNWTmT7OFiKR0uz9ed98TAodYiHZQAQtFdwwQ== +mapbox-gl@2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/mapbox-gl/-/mapbox-gl-2.15.0.tgz#9439828d0bae1e7b464ae08b30cb2e65a7e2256d" + integrity sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A== dependencies: "@mapbox/geojson-rewind" "^0.5.2" "@mapbox/jsonlint-lines-primitives" "^2.0.2" "@mapbox/mapbox-gl-supported" "^2.0.1" "@mapbox/point-geometry" "^0.1.0" - "@mapbox/tiny-sdf" "^2.0.5" + "@mapbox/tiny-sdf" "^2.0.6" "@mapbox/unitbezier" "^0.0.1" "@mapbox/vector-tile" "^1.3.1" "@mapbox/whoots-js" "^3.1.0" @@ -2009,12 +2019,13 @@ mapbox-gl@2.12.0: geojson-vt "^3.2.1" gl-matrix "^3.4.3" grid-index "^1.1.0" + kdbush "^4.0.1" murmurhash-js "^1.0.0" pbf "^3.2.1" potpack "^2.0.0" quickselect "^2.0.0" rw "^1.3.3" - supercluster "^7.1.5" + supercluster "^8.0.0" tinyqueue "^2.0.3" vt-pbf "^3.1.3" @@ -2982,12 +2993,12 @@ supercluster@^7.1.4: dependencies: kdbush "^3.0.0" -supercluster@^7.1.5: - version "7.1.5" - resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3" - integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg== +supercluster@^8.0.0: + version "8.0.1" + resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5" + integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ== dependencies: - kdbush "^3.0.0" + kdbush "^4.0.2" supports-color@8.1.1, supports-color@^8.0.0: version "8.1.1" diff --git a/server/src/main/kotlin/ca/derekellis/reroute/server/realtime/RealtimeRoute.kt b/server/src/main/kotlin/ca/derekellis/reroute/server/realtime/RealtimeRoute.kt index c4bad9e..35c865c 100644 --- a/server/src/main/kotlin/ca/derekellis/reroute/server/realtime/RealtimeRoute.kt +++ b/server/src/main/kotlin/ca/derekellis/reroute/server/realtime/RealtimeRoute.kt @@ -2,8 +2,11 @@ package ca.derekellis.reroute.server.realtime import ca.derekellis.reroute.server.RoutingModule import ca.derekellis.reroute.server.di.RerouteScope +import io.ktor.server.application.call +import io.ktor.server.response.respond import io.ktor.server.routing.Route import io.ktor.server.routing.Routing +import io.ktor.server.routing.get import io.ktor.server.routing.route import io.ktor.server.websocket.sendSerialized import io.ktor.server.websocket.webSocket @@ -15,11 +18,15 @@ import org.slf4j.LoggerFactory @Inject @RerouteScope -class RealtimeRoute(private val worker: OcTranspoWorker) : RoutingModule { +class RealtimeRoute( + private val worker: OcTranspoWorker, + private val client: OcTranspoClient, +) : RoutingModule { private val logger = LoggerFactory.getLogger(javaClass) + context(Routing) override fun route(): Route = route("/realtime") { - webSocket("/{code}") { + /*webSocket("/{code}") { val code = call.parameters["code"]!! logger.info("WebSocket opened for: {}", code) @@ -33,6 +40,13 @@ class RealtimeRoute(private val worker: OcTranspoWorker) : RoutingModule { logger.error("Internal error", e) close(CloseReason(CloseReason.Codes.INTERNAL_ERROR, "")) } + }*/ + + get("/{code}") { + val code = call.parameters["code"]!! + val result = client.get(code) + + call.respond(result) } } } diff --git a/web/src/jsMain/kotlin/ca/derekellis/reroute/data/RerouteClient.kt b/web/src/jsMain/kotlin/ca/derekellis/reroute/data/RerouteClient.kt index ded0f4e..a62494d 100644 --- a/web/src/jsMain/kotlin/ca/derekellis/reroute/data/RerouteClient.kt +++ b/web/src/jsMain/kotlin/ca/derekellis/reroute/data/RerouteClient.kt @@ -29,6 +29,7 @@ class RerouteClient(private val client: HttpClient) { suspend fun getDataBundle(): TransitDataBundle = client.get("/api/data/").body() + @Deprecated("Websocket support is being dropped") fun nextTrips(code: String): Flow = flow { client.webSocket("/api/realtime/$code") { while (true) { @@ -36,4 +37,6 @@ class RerouteClient(private val client: HttpClient) { } } } + + suspend fun nextTripsSingle(code: String): RealtimeMessage = client.get("/api/realtime/$code").body() } diff --git a/web/src/jsMain/kotlin/ca/derekellis/reroute/stops/StopPresenter.kt b/web/src/jsMain/kotlin/ca/derekellis/reroute/stops/StopPresenter.kt index 09f0aea..e9f3267 100644 --- a/web/src/jsMain/kotlin/ca/derekellis/reroute/stops/StopPresenter.kt +++ b/web/src/jsMain/kotlin/ca/derekellis/reroute/stops/StopPresenter.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import ca.derekellis.reroute.data.DataSource @@ -15,11 +16,14 @@ import ca.derekellis.reroute.realtime.RealtimeMessage import ca.derekellis.reroute.ui.CollectEffect import ca.derekellis.reroute.ui.Navigator import ca.derekellis.reroute.ui.Presenter +import ca.derekellis.reroute.utils.whenWindowFocused +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import me.tatarka.inject.annotations.Assisted import me.tatarka.inject.annotations.Inject +import kotlin.time.Duration.Companion.seconds @Inject class StopPresenter( @@ -48,7 +52,14 @@ class StopPresenter( if (routesAtStop == null || stopList == null) return StopViewModel.Loading - val realtimeData by remember { client.nextTrips(args.code) }.collectAsState(null) + val realtimeData by produceState(initialValue = null) { + whenWindowFocused { + while (true) { + value = client.nextTripsSingle(args.code) + delay(30.seconds) + } + } + } val routeSections = remember(routesAtStop) { (routesAtStop ?: emptyList()).map { diff --git a/web/src/jsMain/kotlin/ca/derekellis/reroute/utils/whenWindowFocused.kt b/web/src/jsMain/kotlin/ca/derekellis/reroute/utils/whenWindowFocused.kt new file mode 100644 index 0000000..6523321 --- /dev/null +++ b/web/src/jsMain/kotlin/ca/derekellis/reroute/utils/whenWindowFocused.kt @@ -0,0 +1,29 @@ +package ca.derekellis.reroute.utils + +import kotlinx.browser.window +import kotlinx.coroutines.Job +import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.launch +import org.w3c.dom.events.Event + +suspend fun whenWindowFocused(block: suspend () -> Unit): Nothing = coroutineScope { + var job: Job? = null + val focusListener: (Event) -> Unit = { + job?.cancel() + job = launch { block() } + } + val blurListener: (Event) -> Unit = { + job?.cancel() + } + + try { + window.addEventListener("focus", focusListener) + window.addEventListener("blur", blurListener) + + awaitCancellation() + } finally { + window.removeEventListener("focus", focusListener) + window.removeEventListener("blur", blurListener) + } +}