diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/WebRtcCallFactory.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/WebRtcCallFactory.kt new file mode 100644 index 0000000000..858c5b5913 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/WebRtcCallFactory.kt @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallAnswerEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallHangupEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallIceCandidateEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallOfferEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallRejectEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallRenegotiateEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallType +import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent + +class WebRtcCallFactory { + data class Result( + val msg: Event, + val wrap: GiftWrapEvent, + ) + + suspend fun createCallOffer( + sdpOffer: String, + calleePubKey: HexKey, + callId: String, + callType: CallType, + signer: NostrSigner, + ): Result { + val template = CallOfferEvent.build(sdpOffer, calleePubKey, callId, callType) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = calleePubKey) + return Result(signed, wrap) + } + + suspend fun createCallAnswer( + sdpAnswer: String, + callerPubKey: HexKey, + callId: String, + signer: NostrSigner, + ): Result { + val template = CallAnswerEvent.build(sdpAnswer, callerPubKey, callId) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = callerPubKey) + return Result(signed, wrap) + } + + suspend fun createIceCandidate( + candidateJson: String, + peerPubKey: HexKey, + callId: String, + signer: NostrSigner, + ): Result { + val template = CallIceCandidateEvent.build(candidateJson, peerPubKey, callId) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = peerPubKey) + return Result(signed, wrap) + } + + suspend fun createHangup( + peerPubKey: HexKey, + callId: String, + reason: String = "", + signer: NostrSigner, + ): Result { + val template = CallHangupEvent.build(peerPubKey, callId, reason) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = peerPubKey) + return Result(signed, wrap) + } + + suspend fun createReject( + callerPubKey: HexKey, + callId: String, + reason: String = "", + signer: NostrSigner, + ): Result { + val template = CallRejectEvent.build(callerPubKey, callId, reason) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = callerPubKey) + return Result(signed, wrap) + } + + suspend fun createRenegotiate( + sdpOffer: String, + peerPubKey: HexKey, + callId: String, + signer: NostrSigner, + ): Result { + val template = CallRenegotiateEvent.build(sdpOffer, peerPubKey, callId) + val signed = signer.sign(template) + val wrap = GiftWrapEvent.create(event = signed, recipientPubKey = peerPubKey) + return Result(signed, wrap) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallAnswerEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallAnswerEvent.kt new file mode 100644 index 0000000000..b09c7da97c --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallAnswerEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallAnswerEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun sdpAnswer() = content + + companion object { + const val KIND = 25051 + const val ALT_DESCRIPTION = "WebRTC call answer" + const val EXPIRATION_SECONDS = 300L + + fun build( + sdpAnswer: String, + callerPubKey: HexKey, + callId: String, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, sdpAnswer, createdAt) { + alt(ALT_DESCRIPTION) + pTag(callerPubKey) + callId(callId) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallHangupEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallHangupEvent.kt new file mode 100644 index 0000000000..648cb64896 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallHangupEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallHangupEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun reason() = content.ifEmpty { null } + + companion object { + const val KIND = 25053 + const val ALT_DESCRIPTION = "WebRTC call hangup" + const val EXPIRATION_SECONDS = 300L + + fun build( + peerPubKey: HexKey, + callId: String, + reason: String = "", + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, reason, createdAt) { + alt(ALT_DESCRIPTION) + pTag(peerPubKey) + callId(callId) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallIceCandidateEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallIceCandidateEvent.kt new file mode 100644 index 0000000000..8a3d130878 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallIceCandidateEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallIceCandidateEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun candidateJson() = content + + companion object { + const val KIND = 25052 + const val ALT_DESCRIPTION = "WebRTC ICE candidate" + const val EXPIRATION_SECONDS = 300L + + fun build( + candidateJson: String, + peerPubKey: HexKey, + callId: String, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, candidateJson, createdAt) { + alt(ALT_DESCRIPTION) + pTag(peerPubKey) + callId(callId) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallOfferEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallOfferEvent.kt new file mode 100644 index 0000000000..e4cd9be14a --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallOfferEvent.kt @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallType +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallTypeTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callType +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallOfferEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun callType() = tags.firstNotNullOfOrNull(CallTypeTag::parse) + + fun sdpOffer() = content + + companion object { + const val KIND = 25050 + const val ALT_DESCRIPTION = "WebRTC call offer" + const val EXPIRATION_SECONDS = 300L // 5 minutes + + fun build( + sdpOffer: String, + calleePubKey: HexKey, + callId: String, + type: CallType, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, sdpOffer, createdAt) { + alt(ALT_DESCRIPTION) + pTag(calleePubKey) + callId(callId) + callType(type) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRejectEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRejectEvent.kt new file mode 100644 index 0000000000..84be6ad0fd --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRejectEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallRejectEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun reason() = content.ifEmpty { null } + + companion object { + const val KIND = 25054 + const val ALT_DESCRIPTION = "WebRTC call rejection" + const val EXPIRATION_SECONDS = 300L + + fun build( + callerPubKey: HexKey, + callId: String, + reason: String = "", + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, reason, createdAt) { + alt(ALT_DESCRIPTION) + pTag(callerPubKey) + callId(callId) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRenegotiateEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRenegotiateEvent.kt new file mode 100644 index 0000000000..f38898f703 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/events/CallRenegotiateEvent.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.people.pTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.CallIdTag +import com.vitorpamplona.quartz.nip100WebRtcCalls.tags.callId +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip40Expiration.expiration +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class CallRenegotiateEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, KIND, tags, content, sig) { + fun callId() = tags.firstNotNullOfOrNull(CallIdTag::parse) + + fun sdpOffer() = content + + companion object { + const val KIND = 25055 + const val ALT_DESCRIPTION = "WebRTC call renegotiation" + const val EXPIRATION_SECONDS = 300L + + fun build( + sdpOffer: String, + peerPubKey: HexKey, + callId: String, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(KIND, sdpOffer, createdAt) { + alt(ALT_DESCRIPTION) + pTag(peerPubKey) + callId(callId) + expiration(createdAt + EXPIRATION_SECONDS) + initializer() + } + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallIdTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallIdTag.kt new file mode 100644 index 0000000000..d708655506 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallIdTag.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class CallIdTag { + companion object { + const val TAG_NAME = "call-id" + + fun parse(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1] + } + + fun assemble(callId: String) = arrayOf(TAG_NAME, callId) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallTypeTag.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallTypeTag.kt new file mode 100644 index 0000000000..f0b055af97 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/CallTypeTag.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +enum class CallType( + val value: String, +) { + VOICE("voice"), + VIDEO("video"), + ; + + companion object { + fun fromString(value: String): CallType? = + when (value) { + "voice" -> VOICE + "video" -> VIDEO + else -> null + } + } +} + +class CallTypeTag { + companion object { + const val TAG_NAME = "call-type" + + fun parse(tag: Array): CallType? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + return CallType.fromString(tag[1]) + } + + fun assemble(callType: CallType) = arrayOf(TAG_NAME, callType.value) + } +} diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/TagArrayBuilderExt.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/TagArrayBuilderExt.kt new file mode 100644 index 0000000000..17631ee782 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip100WebRtcCalls/tags/TagArrayBuilderExt.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip100WebRtcCalls.tags + +import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder + +fun TagArrayBuilder.callId(callId: String) = addUnique(CallIdTag.assemble(callId)) + +fun TagArrayBuilder.callType(callType: CallType) = addUnique(CallTypeTag.assemble(callType)) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt index c4eb7d8d5e..48f0d8f04b 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/utils/EventFactory.kt @@ -50,6 +50,12 @@ import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallAnswerEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallHangupEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallIceCandidateEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallOfferEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallRejectEvent +import com.vitorpamplona.quartz.nip100WebRtcCalls.events.CallRenegotiateEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip15Marketplace.auction.AuctionEvent import com.vitorpamplona.quartz.nip15Marketplace.bid.BidEvent