Add parsing/encoding support for call links

This commit is contained in:
Max Radermacher 2024-03-21 13:10:38 -05:00 committed by GitHub
parent 7c85428f7b
commit 83ca18747b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 136 additions and 2 deletions

View File

@ -633,6 +633,8 @@
5050A8792B76E2E100E9BFA4 /* PreKeyId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5050A8782B76E2E100E9BFA4 /* PreKeyId.swift */; };
5050A87B2B76EEC500E9BFA4 /* PreKeyIdTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5050A87A2B76EEC500E9BFA4 /* PreKeyIdTest.swift */; };
5052AF5E2ACB0E9700D7EE9F /* MergePair.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5052AF5D2ACB0E9700D7EE9F /* MergePair.swift */; };
50552C2E2BAC066A00815474 /* CallLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50552C2D2BAC066A00815474 /* CallLink.swift */; };
50552C312BAC079A00815474 /* CallLinkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50552C302BAC079A00815474 /* CallLinkTest.swift */; };
50597BBA2B97C38C004681E1 /* SignalAccountStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50597BB92B97C38C004681E1 /* SignalAccountStore.swift */; };
50597BBC2B97C449004681E1 /* UsernameLookupRecordStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50597BBB2B97C449004681E1 /* UsernameLookupRecordStore.swift */; };
50597BBF2B97D629004681E1 /* SearchableNameFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50597BBE2B97D629004681E1 /* SearchableNameFinder.swift */; };
@ -3433,6 +3435,8 @@
5050A8782B76E2E100E9BFA4 /* PreKeyId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreKeyId.swift; sourceTree = "<group>"; };
5050A87A2B76EEC500E9BFA4 /* PreKeyIdTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreKeyIdTest.swift; sourceTree = "<group>"; };
5052AF5D2ACB0E9700D7EE9F /* MergePair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MergePair.swift; sourceTree = "<group>"; };
50552C2D2BAC066A00815474 /* CallLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallLink.swift; sourceTree = "<group>"; };
50552C302BAC079A00815474 /* CallLinkTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallLinkTest.swift; sourceTree = "<group>"; };
50597BB92B97C38C004681E1 /* SignalAccountStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalAccountStore.swift; sourceTree = "<group>"; };
50597BBB2B97C449004681E1 /* UsernameLookupRecordStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UsernameLookupRecordStore.swift; sourceTree = "<group>"; };
50597BBE2B97D629004681E1 /* SearchableNameFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchableNameFinder.swift; sourceTree = "<group>"; };
@ -6859,6 +6863,14 @@
path = Launch;
sourceTree = "<group>";
};
50552C2F2BAC079000815474 /* Calls */ = {
isa = PBXGroup;
children = (
50552C302BAC079A00815474 /* CallLinkTest.swift */,
);
path = Calls;
sourceTree = "<group>";
};
50597BBD2B97D624004681E1 /* Search */ = {
isa = PBXGroup;
children = (
@ -8486,6 +8498,7 @@
17ACF11D267D71E0009BE867 /* AudioSession+WebRTC.swift */,
88D23D2D23CEC1BE00B0E74B /* AudioSource.swift */,
88D23D2B23CEC17400B0E74B /* CallAudioService.swift */,
50552C2D2BAC066A00815474 /* CallLink.swift */,
88588D25252E59CE00405414 /* CallService.swift */,
B98D24692B8D2A2A00B1A8CC /* CallStarter.swift */,
8841584B252F9F1C0078903D /* SignalCall.swift */,
@ -8563,6 +8576,7 @@
34C6B0A41FA0E46F00D35993 /* Assets */,
1704690725D4C2DA000793D8 /* attachments */,
D979DA122B8D1B3E000EEAB8 /* Badge */,
50552C2F2BAC079000815474 /* Calls */,
D931080C2B338D00006A034E /* CallsTab */,
B660F6751C29867F00687D6E /* contact */,
50B6BCB22AEC58190010FB3B /* Contacts */,
@ -12396,6 +12410,7 @@
88ABB8B52534070400229EAA /* CallHeader.swift in Sources */,
88D23D2423CEC0C700B0E74B /* CallKitCallManager.swift in Sources */,
88D23D2523CEC0C700B0E74B /* CallKitCallUIAdaptee.swift in Sources */,
50552C2E2BAC066A00815474 /* CallLink.swift in Sources */,
E1E78CAF2B573BD100B6FC2D /* CallMemberCameraOffView.swift in Sources */,
E1E78CAD2B573B5800B6FC2D /* CallMemberChromeOverlayView.swift in Sources */,
E1E78CB42B575C2700B6FC2D /* CallMemberVideoView.swift in Sources */,
@ -12998,6 +13013,7 @@
D99ABC742A3D0BE10034CD3B /* BitmapsImageParsingTest.swift in Sources */,
D9317FD32A4BAC8300075A92 /* BitmapsImagePixelMergingTest.swift in Sources */,
D9317FD82A4BC4FC00075A92 /* BitmapsRectTest.swift in Sources */,
50552C312BAC079A00815474 /* CallLinkTest.swift in Sources */,
D93108152B34B6BE006A034E /* CallRecordLoaderTest.swift in Sources */,
D9F02BE72B96556C00E872C2 /* CallsListViewController+ViewModelLoaderTest.swift in Sources */,
954AEE6A1DF33E01002E5410 /* ContactsPickerTest.swift in Sources */,

View File

@ -11,6 +11,7 @@
<string>applinks:signal.group</string>
<string>applinks:signal.me</string>
<string>applinks:signaldonations.org</string>
<string>applinks:signal.link</string>
</array>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>

View File

@ -11,6 +11,7 @@
<string>applinks:signal.group</string>
<string>applinks:signal.me</string>
<string>applinks:signaldonations.org</string>
<string>applinks:signal.link</string>
</array>
<key>com.apple.developer.default-data-protection</key>
<string>NSFileProtectionComplete</string>

View File

@ -15,6 +15,7 @@ private enum OpenableUrl {
case signalProxy(URL)
case linkDevice(DeviceProvisioningURL)
case completeIDEALDonation(Stripe.IDEALCallbackType)
case callLink(CallLink)
}
class UrlOpener {
@ -73,6 +74,9 @@ class UrlOpener {
if let donationType = Stripe.parseStripeIDEALCallback(url) {
return .completeIDEALDonation(donationType)
}
if let callLink = CallLink(url: url), FeatureFlags.callLinkJoin {
return .callLink(callLink)
}
owsFailDebug("Couldn't parse URL")
return nil
}
@ -131,7 +135,7 @@ class UrlOpener {
private func shouldDismiss(for url: OpenableUrl) -> Bool {
switch url {
case .completeIDEALDonation: return false
case .groupInvite, .linkDevice, .phoneNumberLink, .signalProxy, .stickerPack, .usernameLink: return true
case .groupInvite, .linkDevice, .phoneNumberLink, .signalProxy, .stickerPack, .usernameLink, .callLink: return true
}
}
@ -209,6 +213,10 @@ class UrlOpener {
OWSLogger.warn("[Donations] Unexpected error encountered with iDEAL donation")
}
}
case .callLink(let callLink):
// CallLink TODO: Join the call.
Logger.debug("Trying to open \(callLink)")
}
}
}

View File

@ -0,0 +1,73 @@
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import SignalCoreKit
import SignalRingRTC
struct CallLink {
// MARK: -
private enum Constants {
static let scheme = "https"
static let host = "signal.link"
static let path = "/call/"
static let key = "key"
}
// MARK: -
let rootKey: CallLinkRootKey
init(rootKey: CallLinkRootKey) {
self.rootKey = rootKey
}
/// Parses a URL of the form: https://signal.link/call/#key=value
init?(url: URL) {
guard
var components = URLComponents(url: url, resolvingAgainstBaseURL: false),
components.scheme == Constants.scheme,
components.user == nil,
components.password == nil,
components.host == Constants.host,
components.port == nil,
components.path == Constants.path,
components.query == nil
else {
return nil
}
components.percentEncodedQuery = components.percentEncodedFragment
guard
let queryItems = components.queryItems,
queryItems.count == 1,
let keyItem = queryItems.first,
keyItem.name == Constants.key,
let keyValue = keyItem.value,
let rootKey = try? CallLinkRootKey(keyValue)
else {
return nil
}
self.init(rootKey: rootKey)
}
static func generate() -> CallLink {
let rootKey = CallLinkRootKey.generate()
return CallLink(rootKey: rootKey)
}
func url() -> URL {
var components = URLComponents()
components.scheme = Constants.scheme
components.host = Constants.host
components.path = Constants.path
components.queryItems = [
URLQueryItem(name: Constants.key, value: rootKey.description),
]
components.percentEncodedFragment = components.percentEncodedQuery
components.query = nil
return components.url!
}
}

View File

@ -0,0 +1,33 @@
//
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import XCTest
@testable import Signal
final class CallLinkTest: XCTestCase {
private func parse(_ urlString: String) -> CallLink? {
return CallLink(url: URL(string: urlString)!)
}
func testUrlString() {
XCTAssertNil(parse("https://signal.link/call/#key=bcdf-ghkm-npqr-stxz-bcdf-ghkm-npqr-stx"))
XCTAssertNil(parse("http://signal.link/call/#key=bcdf-ghkm-npqr-stxz-bcdf-ghkm-npqr-stxz"))
XCTAssertNil(parse("https://signal.art/call/#key=bcdf-ghkm-npqr-stxz-bcdf-ghkm-npqr-stxz"))
XCTAssertNil(parse("https://signal.link/c/#key=bcdf-ghkm-npqr-stxz-bcdf-ghkm-npqr-stxz"))
}
func testRoundtrip() throws {
let urlString = "https://signal.link/call/#key=bcdf-ghkm-npqr-stxz-bcdf-ghkm-npqr-stxz"
let callLink = try XCTUnwrap(parse(urlString))
XCTAssertEqual(callLink.url().absoluteString, urlString)
}
func testGenerate() {
let url1 = CallLink.generate().url()
let url2 = CallLink.generate().url()
XCTAssertNotEqual(url1, url2)
}
}

View File

@ -5,6 +5,7 @@
import Foundation
import PassKit
import SignalCoreKit
/// Stripe donations
///
@ -509,7 +510,6 @@ public extension Stripe {
let components = URLComponents(string: url.absoluteString),
let queryItems = components.queryItems
else {
owsFailDebug("Invalid URL.")
return nil
}

View File

@ -93,6 +93,8 @@ public class FeatureFlags: NSObject {
public static let readV2Attachments = false
public static let newAttachmentsUseV2 = false
public static let callLinkJoin = build.includes(.dev)
}
// MARK: -