Add parsing/encoding support for call links
This commit is contained in:
parent
7c85428f7b
commit
83ca18747b
@ -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 */,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
73
Signal/src/Calls/CallLink.swift
Normal file
73
Signal/src/Calls/CallLink.swift
Normal 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!
|
||||
}
|
||||
}
|
||||
33
Signal/test/Calls/CallLinkTest.swift
Normal file
33
Signal/test/Calls/CallLinkTest.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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: -
|
||||
|
||||
Loading…
Reference in New Issue
Block a user