Skip to content
This repository was archived by the owner on Aug 10, 2024. It is now read-only.

Commit d3821e9

Browse files
authored
Message listener (#504)
* Fix ValueElement.valueJsExpression * Add JS code for client to send Client2ServerMessage with custom data parameter. * Add WebBrowser to RemoteClientState.kt and add functionality for the server to respond to the new message type from the client. * Add Comments * Add another comment * Fix incorrect WebBrowser oversight. * Rework custom message system. * Rename RemoteClientState.handlers * Add TODO * Add comment * Clean sample code form Todo demo * Give clearer names in Kotlin code * Give clearer names in JS code * Rename one more variable.
1 parent e869700 commit d3821e9

File tree

7 files changed

+68
-17
lines changed

7 files changed

+68
-17
lines changed

src/main/kotlin/kweb/Kweb.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package kweb
33
import com.google.common.cache.Cache
44
import com.google.common.cache.CacheBuilder
55
import io.ktor.server.application.*
6-
import io.ktor.server.plugins.*
76
import io.ktor.http.*
87
import io.ktor.websocket.*
98
import io.ktor.websocket.Frame.*
@@ -189,11 +188,11 @@ class Kweb private constructor(
189188
val wsClientData = clientState.getIfPresent(sessionId)
190189
?: error("Can not add callback because: Client id $sessionId not found")
191190
wsClientData.lastModified = Instant.now()
192-
wsClientData.handlers[callbackId] = callback
191+
wsClientData.eventHandlers[callbackId] = callback
193192
}
194193

195194
fun removeCallback(clientId: String, callbackId: Int) {
196-
clientState.getIfPresent(clientId)?.handlers?.remove(callbackId)
195+
clientState.getIfPresent(clientId)?.eventHandlers?.remove(callbackId)
197196
}
198197

199198
override fun close() {
@@ -317,7 +316,7 @@ class Kweb private constructor(
317316
when {
318317
message.callback != null -> {
319318
val (resultId, result) = message.callback
320-
val resultHandler = remoteClientState.handlers[resultId]
319+
val resultHandler = remoteClientState.eventHandlers[resultId]
321320
?: error("No resultHandler for $resultId, for client ${remoteClientState.id}")
322321
resultHandler(result)
323322
}
@@ -326,6 +325,12 @@ class Kweb private constructor(
326325
logger.debug { "keepalive received from client ${hello.id}" }
327326
}
328327

328+
message.onMessageData != null -> {
329+
println("PUSHING ------------------------------------------------")
330+
val data = message.onMessageData
331+
remoteClientState.onMessageFunction!!.invoke(data)
332+
}
333+
329334
}
330335
}
331336
}
@@ -359,11 +364,16 @@ class Kweb private constructor(
359364
val clientPrefix = determineClientPrefix(call)
360365
val kwebSessionId = clientPrefix + ":" + createNonce()
361366

367+
val httpRequestInfo = HttpRequestInfo(call.request)
368+
369+
370+
//this doesn't work. I get this error when running the todo Demo
371+
//Caused by: java.lang.IllegalStateException: Client id Nb9_U7:eJ5dw4 not found
372+
//The debugger says that the remoteClientState ID here matches the clientPrefix and the kwebSessionID from a few lines ago.
362373
val remoteClientState = clientState.get(kwebSessionId) {
363374
RemoteClientState(id = kwebSessionId, clientConnection = Caching())
364375
}
365376

366-
val httpRequestInfo = HttpRequestInfo(call.request)
367377

368378
try {
369379
val webBrowser = WebBrowser(kwebSessionId, httpRequestInfo, this)

src/main/kotlin/kweb/ValueElement.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ abstract class ValueElement(
2222
open val element: Element, val kvarUpdateEvent: String = "input",
2323
val initialValue: String? = null
2424
) : Element(element) {
25-
val valueJsExpression: String by lazy { "document.getElementById(\"$id\").value" }
25+
val valueJsExpression: String get() = "document.getElementById(\"$id\").value"
2626

2727
suspend fun getValue(): String {
2828
return when (val result =

src/main/kotlin/kweb/WebBrowser.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import java.util.*
2121
import java.util.concurrent.ConcurrentHashMap
2222
import java.util.concurrent.atomic.AtomicInteger
2323
import java.util.concurrent.atomic.AtomicReference
24-
import kotlin.contracts.ExperimentalContracts
2524
import kotlin.math.abs
2625
import kotlin.reflect.KClass
2726
import kotlin.reflect.jvm.jvmName
@@ -232,6 +231,29 @@ class WebBrowser(val sessionId: String, val httpRequestInfo: HttpRequestInfo, va
232231
kweb.addCallback(sessionId, functionCall.callbackId!!, callback)
233232
}
234233

234+
//attaches the user supplied function to the remoteClientState, to be invoked in Kweb.listenForWebSocketConnection()
235+
fun onMessage(customFunction: (data: JsonElement?) -> Unit) {
236+
val remoteClientState = kweb.clientState.getIfPresent(sessionId)
237+
try {
238+
if (remoteClientState!!.onMessageFunction == null) {
239+
remoteClientState!!.onMessageFunction = customFunction
240+
} else {
241+
error("You can only register one message handler per WebBrowser, it should handle any messages you're expecting")
242+
}
243+
244+
} catch (e: NullPointerException) {
245+
//TODO it should be impossible for remoteClientState to ever be null here, but maybe we need to add something to handle it anyway
246+
}
247+
}
248+
249+
//helper function to allow invoking the custom message without writing Javascript.
250+
fun sendMessage(data: JsonElement? = null) {
251+
val remoteClientState = kweb.clientState.getIfPresent(sessionId)
252+
remoteClientState?.let {
253+
it.onMessageFunction!!.invoke(data)
254+
}
255+
}
256+
235257
fun addCloseListener(listener: () -> Unit) : Int {
236258
val id = random.nextInt()
237259
this.closeListeners[id] = listener

src/main/kotlin/kweb/client/Client2ServerMessage.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ data class Client2ServerMessage(
1010
val hello: Boolean? = true,
1111
val error: ErrorMessage? = null,
1212
val callback: C2SCallback? = null,
13+
val onMessageData: JsonElement? = null,
1314
val keepalive : Boolean = false,
1415
) {
1516

src/main/kotlin/kweb/client/RemoteClientState.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ import java.time.Instant
99
import java.util.concurrent.ConcurrentHashMap
1010

1111
data class RemoteClientState(val id: String, @Volatile var clientConnection: ClientConnection,
12-
val handlers: MutableMap<Int, (JsonElement) -> Unit> = HashMap(),
12+
val eventHandlers: MutableMap<Int, (JsonElement) -> Unit> = HashMap(),
1313
val onCloseHandlers : ConcurrentHashMap<Int, () -> Unit> = ConcurrentHashMap(),
14-
val debugTokens: MutableMap<String, DebugInfo> = HashMap(), var lastModified: Instant = Instant.now()) {
14+
val debugTokens: MutableMap<String, DebugInfo> = HashMap(), var lastModified: Instant = Instant.now(),
15+
var onMessageFunction: ((data: JsonElement?) -> Unit)? = null) {
1516
fun send(message: Server2ClientMessage) {
1617
clientConnection.send(Json.encodeToString(message))
1718
}

src/main/resources/kweb/kweb_bootstrap.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function handleInboundMessage(msg) {
6363
console.debug("Evaluated [ " + func.toString() + "]", data);
6464
const callback = {callbackId: callbackId, data: data};
6565
const message = {id: kwebClientId, callback: callback};
66-
sendMessage(JSON.stringify(message));
66+
sendClientMessage(JSON.stringify(message));
6767
} catch (err) {
6868
debugErr(debugToken, err, "Error Evaluating `" + func.toString() + "`: " + err);
6969
}
@@ -86,7 +86,7 @@ function debugErr(debugToken, err, errMsg) {
8686
error: {name: err.name, message: err.message}
8787
};
8888
const message = {id: kwebClientId, error: err};
89-
sendMessage(JSON.stringify(message));
89+
sendClientMessage(JSON.stringify(message));
9090
} else {
9191
console.error(errMsg)
9292
}
@@ -106,9 +106,9 @@ function connectWs() {
106106
websocketEstablished = true;
107107
console.debug("Websocket established", wsURL);
108108
removeElementByIdIfExists(bannerId);
109-
sendMessage(JSON.stringify({id: kwebClientId, hello: true}));
109+
sendClientMessage(JSON.stringify({id: kwebClientId, hello: true}));
110110
while (preWSMsgQueue.length > 0) {
111-
sendMessage(preWSMsgQueue.shift());
111+
sendClientMessage(preWSMsgQueue.shift());
112112
}
113113
reconnectTimeout = 2000
114114
};
@@ -177,7 +177,10 @@ function showReconnectToast() {
177177
}).showToast();
178178
}
179179

180-
function sendMessage(msg) {
180+
/* Sends websocket message from the client to the server. This is a lower-level function used by Kweb.
181+
Developers using Kweb should use sendMessage() instead
182+
*/
183+
function sendClientMessage(msg) {
181184
if (websocketEstablished) {
182185
console.debug("Sending WebSocket message", msg);
183186
socket.send(msg);
@@ -186,20 +189,29 @@ function sendMessage(msg) {
186189
}
187190
}
188191

192+
//Allows Kweb users to send messages from the client to the Server
193+
function sendMessage(data) {
194+
const msg = JSON.stringify({
195+
id: kwebClientId,
196+
onMessageData: data
197+
});
198+
sendClientMessage(msg);
199+
}
200+
189201
function callbackWs(callbackId, data) {
190202
const msg = JSON.stringify({
191203
id: kwebClientId,
192204
callback: {callbackId: callbackId, data: data}
193205
});
194-
sendMessage(msg);
206+
sendClientMessage(msg);
195207
}
196208

197209
function sendKeepalive() {
198210
const msg = JSON.stringify({
199211
id: kwebClientId,
200212
keepalive: true
201213
});
202-
sendMessage(msg);
214+
sendClientMessage(msg);
203215
}
204216

205217
setInterval(sendKeepalive, 60*1000);

src/test/kotlin/kweb/demos/todo/TodoApp.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package kweb.demos.todo
2+
import kotlinx.serialization.json.JsonElement
3+
import kotlinx.serialization.json.JsonPrimitive
24
import kotlinx.serialization.json.jsonPrimitive
35
import kweb.*
46
import kweb.plugins.fomanticUI.fomantic
57
import kweb.plugins.fomanticUI.fomanticUIPlugin
6-
import kweb.state.*
8+
import kweb.state.ObservableList
9+
import kweb.state.render
10+
import kweb.state.renderEach
711
import kweb.util.NotFoundException
812
import kweb.util.random
913
import mu.two.KotlinLogging
@@ -151,6 +155,7 @@ You may find the source code for this app
151155
}
152156
}
153157
}
158+
154159
}
155160

156161
private fun handleAddItem(activeListKey: String, input: InputElement, newItemText: String) {

0 commit comments

Comments
 (0)