11package dev.kdriver.core.connection
22
33import dev.kaccelero.serializers.Serialization
4- import dev.kdriver.cdp.CDP
5- import dev.kdriver.cdp.Domain
6- import dev.kdriver.cdp.Message
7- import dev.kdriver.cdp.Request
4+ import dev.kdriver.cdp.*
85import dev.kdriver.cdp.domain.Target
96import dev.kdriver.cdp.domain.target
107import dev.kdriver.core.browser.Browser
@@ -47,36 +44,18 @@ open class Connection(
4744
4845 private var socketSubscription: Job ? = null
4946
50- private fun startListening () {
51- socketSubscription?.cancel()
52- socketSubscription = messageListeningScope.launch {
53- try {
54- for (frame in wsSession?.incoming ? : return @launch) {
55- try {
56- frame as ? Frame .Text ? : continue
57- val text = frame.readText()
58- logger.debug(" WS < CDP: ${text.take(debugStringLimit)} " )
59- val received = Serialization .json.decodeFromString<Message >(text)
60- allMessages.emit(received)
61- } catch (e: Exception ) {
62- logger.debug(" WebSocket exception while receiving message: {}" , e)
63- }
64- }
65- } catch (e: Exception ) {
66- e.printStackTrace()
67- // Handle disconnect, maybe trigger reconnect logic here
68- }
69- }
70- }
71-
7247 private val currentIdMutex = Mutex ()
7348 private var currentId = 0L
7449
7550 private var allMessages = MutableSharedFlow <Message >(extraBufferCapacity = eventsBufferSize)
7651
52+ @InternalCdpApi
7753 override val events: Flow <Message .Event > = allMessages.filterIsInstance()
54+
55+ @InternalCdpApi
7856 override val responses: Flow <Message .Response > = allMessages.filterIsInstance()
7957
58+ @InternalCdpApi
8059 override val generatedDomains: MutableMap <KClass <out Domain >, Domain > = mutableMapOf ()
8160
8261 private suspend fun connect () {
@@ -93,6 +72,34 @@ open class Connection(
9372 startListening()
9473 }
9574
75+ private fun startListening () {
76+ socketSubscription?.cancel()
77+ socketSubscription = messageListeningScope.launch {
78+ try {
79+ for (frame in wsSession?.incoming ? : return @launch) {
80+ try {
81+ frame as ? Frame .Text ? : continue
82+ val text = frame.readText()
83+ logger.debug(" WS < CDP: ${text.take(debugStringLimit)} " )
84+ val received = Serialization .json.decodeFromString<Message >(text)
85+ allMessages.emit(received)
86+ } catch (e: Exception ) {
87+ logger.debug(" WebSocket exception while receiving message: {}" , e)
88+ }
89+ }
90+ } catch (e: Exception ) {
91+ e.printStackTrace()
92+ // Handle disconnect, maybe trigger reconnect logic here
93+ }
94+ }
95+ }
96+
97+ /* *
98+ * Internal method to call a CDP command.
99+ *
100+ * This should not be called directly, but rather through typed methods (like `cdp.network.enable()`).
101+ */
102+ @InternalCdpApi
96103 override suspend fun callCommand (method : String , parameter : JsonElement ? ): JsonElement ? {
97104 connect()
98105 val requestId = currentIdMutex.withLock { currentId++ }
@@ -104,13 +111,22 @@ open class Connection(
104111 return result.result
105112 }
106113
114+ /* *
115+ * Closes the websocket connection. Should not be called manually by users.
116+ */
117+ @InternalCdpApi
107118 suspend fun close () {
108119 wsSession?.close()
109120 wsSession = null
110121 socketSubscription?.cancel()
111122 socketSubscription = null
112123 }
113124
125+ /* *
126+ * Updates the target information by fetching it from the CDP.
127+ *
128+ * This is useful to refresh the target info after some operations that might change it.
129+ */
114130 suspend fun updateTarget () {
115131 val targetInfo = target.getTargetInfo(targetId)
116132 this .targetInfo = targetInfo.targetInfo
@@ -149,11 +165,88 @@ open class Connection(
149165 }
150166 }
151167
168+ /* *
169+ * Suspends the coroutine for a specified time in milliseconds.
170+ *
171+ * This is a convenience method to ensure that the target information is updated before sleeping.
172+ *
173+ * @param t Time in milliseconds to sleep.
174+ */
152175 suspend fun sleep (t : Long ) {
153176 updateTarget()
154177 delay(t)
155178 }
156179
180+ /* *
181+ * Sends a CDP command and waits for the response.
182+ *
183+ * This is an alias so that you can use cdp the same way as zendriver does:
184+ * ```kotlin
185+ * // send a network.enable command with kdriver
186+ * tab.send { cdp.network.enable() }
187+ * ```
188+ * That would be equivalent to this with zendriver:
189+ * ```python
190+ * # send a network.enable command with zendriver
191+ * tab.send(cdp.network.enable())
192+ * ```
193+ *
194+ * Although you can directly call the CDP methods on the tab (recommended way of doing it):
195+ * ```kotlin
196+ * // send a network.enable command with kdriver, directly
197+ * tab.network.enable()
198+ * ```
199+ *
200+ * @param command The command to send. This is a suspending function that can call any CDP method.
201+ *
202+ * @return The result of the command, deserialized to type T.
203+ */
204+ inline fun <T > send (command : CDP .() -> T ): T {
205+ return this .command()
206+ }
207+
208+ /* *
209+ * Adds a handler for a specific CDP event.
210+ *
211+ * This is an alias so that you can use cdp the same way as zendriver does:
212+ * ```kotlin
213+ * // add a handler for the consoleAPICalled event with kdriver
214+ * tab.addHandler(this, { cdp.runtime.consoleAPICalled }) { event ->
215+ * println(event)
216+ * }
217+ * ```
218+ * That would be equivalent to this with zendriver:
219+ * ```python
220+ * # add a handler for the consoleAPICalled event with zendriver
221+ * tab.add_handler(cdp.runtime.consoleAPICalled, lambda event: print(event))
222+ * ```
223+ *
224+ * Although you can directly collect the events from the tab (recommended way of doing it):
225+ * ```kotlin
226+ * // add a handler for the consoleAPICalled event with kdriver, directly
227+ * launch {
228+ * tab.runtime.consoleAPICalled.collect { event ->
229+ * println(event)
230+ * }
231+ * }
232+ * ```
233+ *
234+ * @param coroutineScope The coroutine scope in which the handler will run.
235+ * @param event A lambda that returns a Flow of the event type to listen to.
236+ * @param handler A suspend function that will be called with each event of the specified type.
237+ *
238+ * @return A Job that can be used to cancel the handler.
239+ */
240+ inline fun <T > addHandler (
241+ coroutineScope : CoroutineScope ,
242+ crossinline event : CDP .() -> Flow <T >,
243+ crossinline handler : suspend (T ) -> Unit ,
244+ ): Job {
245+ return coroutineScope.launch {
246+ event().collect { handler(it) }
247+ }
248+ }
249+
157250 override fun toString (): String {
158251 return " Connection: ${targetInfo?.toString() ? : " no target" } "
159252 }
0 commit comments