603 lines
23 KiB
Swift
603 lines
23 KiB
Swift
//
|
|
// Copyright 2023 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
@testable import SignalServiceKit
|
|
public import XCTest
|
|
|
|
public class RegistrationSessionManagerTest: XCTestCase {
|
|
|
|
private var registrationSessionManager: RegistrationSessionManagerImpl!
|
|
|
|
private var date = Date()
|
|
|
|
private var db: InMemoryDB!
|
|
private var kvStore: KeyValueStore!
|
|
private var mockURLSession: TSRequestOWSURLSessionMock!
|
|
|
|
public override func setUp() {
|
|
db = InMemoryDB()
|
|
|
|
let mockURLSession = TSRequestOWSURLSessionMock()
|
|
self.mockURLSession = mockURLSession
|
|
let mockSignalService = OWSSignalServiceMock()
|
|
mockSignalService.mockUrlSessionBuilder = { _, _, _ in
|
|
return mockURLSession
|
|
}
|
|
|
|
kvStore = KeyValueStore(
|
|
collection: RegistrationSessionManagerImpl.KvStore.collectionName
|
|
)
|
|
|
|
registrationSessionManager = RegistrationSessionManagerImpl(
|
|
dateProvider: { self.date },
|
|
db: db,
|
|
schedulers: DispatchQueueSchedulers(),
|
|
signalService: mockSignalService
|
|
)
|
|
}
|
|
|
|
public func testParseRegistrationSession() async throws {
|
|
let code = "1234"
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.submitVerificationCodeRequest(
|
|
sessionId: oldSession.id,
|
|
code: code
|
|
)
|
|
|
|
// A standard response
|
|
var responseJSON = """
|
|
{
|
|
"id": "abcd",
|
|
"nextSms": 1,
|
|
"nextCall": 2,
|
|
"nextVerificationAttempt": 3,
|
|
"allowedToRequestCode": true,
|
|
"requestedInformation": ["captcha", "pushChallenge"],
|
|
"verified": false,
|
|
}
|
|
"""
|
|
|
|
mockURLSession.addResponse(TSRequestOWSURLSessionMock.Response(
|
|
matcher: { $0.url == expectedRequest.url },
|
|
statusCode: RegistrationServiceResponses.SubmitVerificationCodeResponseCodes.success.rawValue,
|
|
bodyData: responseJSON.data(using: .utf8)
|
|
))
|
|
|
|
do {
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
|
|
XCTAssertEqual(result, .success(RegistrationSession(
|
|
id: "abcd",
|
|
e164: oldSession.e164,
|
|
receivedDate: self.date,
|
|
nextSMS: 1,
|
|
nextCall: 2,
|
|
nextVerificationAttempt: 3,
|
|
allowedToRequestCode: true,
|
|
requestedInformation: [.captcha, .pushChallenge],
|
|
hasUnknownChallengeRequiringAppUpdate: false,
|
|
verified: false
|
|
)))
|
|
}
|
|
|
|
// Try empty time durations
|
|
responseJSON = """
|
|
{
|
|
"id": "abcd",
|
|
"allowedToRequestCode": true,
|
|
"requestedInformation": ["captcha", "pushChallenge"],
|
|
"verified": false,
|
|
}
|
|
"""
|
|
|
|
mockURLSession.addResponse(TSRequestOWSURLSessionMock.Response(
|
|
matcher: { $0.url == expectedRequest.url },
|
|
statusCode: RegistrationServiceResponses.SubmitVerificationCodeResponseCodes.success.rawValue,
|
|
bodyData: responseJSON.data(using: .utf8)
|
|
))
|
|
|
|
do {
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
|
|
XCTAssertEqual(result, .success(RegistrationSession(
|
|
id: "abcd",
|
|
e164: oldSession.e164,
|
|
receivedDate: self.date,
|
|
nextSMS: nil,
|
|
nextCall: nil,
|
|
nextVerificationAttempt: nil,
|
|
allowedToRequestCode: true,
|
|
requestedInformation: [.captcha, .pushChallenge],
|
|
hasUnknownChallengeRequiringAppUpdate: false,
|
|
verified: false
|
|
)))
|
|
}
|
|
|
|
// Try requested info we don't know how to parse.
|
|
responseJSON = """
|
|
{
|
|
"id": "abcd",
|
|
"nextSms": 1,
|
|
"nextCall": 2,
|
|
"nextVerificationAttempt": 3,
|
|
"allowedToRequestCode": true,
|
|
"requestedInformation": ["someRandomGarbage", "captcha", "pushChallenge"],
|
|
"verified": false,
|
|
}
|
|
"""
|
|
|
|
mockURLSession.addResponse(TSRequestOWSURLSessionMock.Response(
|
|
matcher: { $0.url == expectedRequest.url },
|
|
statusCode: RegistrationServiceResponses.SubmitVerificationCodeResponseCodes.success.rawValue,
|
|
bodyData: responseJSON.data(using: .utf8)
|
|
))
|
|
|
|
do {
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
|
|
XCTAssertEqual(result, .success(RegistrationSession(
|
|
id: "abcd",
|
|
e164: oldSession.e164,
|
|
receivedDate: self.date,
|
|
nextSMS: 1,
|
|
nextCall: 2,
|
|
nextVerificationAttempt: 3,
|
|
allowedToRequestCode: true,
|
|
requestedInformation: [.captcha, .pushChallenge],
|
|
// We saw unknown challenges
|
|
hasUnknownChallengeRequiringAppUpdate: true,
|
|
verified: false
|
|
)))
|
|
}
|
|
}
|
|
|
|
public func testBeginOrRestoreSession() async throws {
|
|
let e164 = E164("+17875550100")!
|
|
let apnsToken = "1234"
|
|
let beginSessionRequest = RegistrationRequestFactory.beginSessionRequest(
|
|
e164: e164,
|
|
pushToken: apnsToken,
|
|
mcc: nil,
|
|
mnc: nil
|
|
)
|
|
|
|
// Without any setup, we should try and begin a new session.
|
|
|
|
var responseBody = stubWireSession()
|
|
var responseSession = sessionConverter(responseBody)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: beginSessionRequest.url!.relativeString,
|
|
bodyJson: responseBody
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.beginOrRestoreSession(
|
|
e164: e164,
|
|
apnsToken: apnsToken
|
|
).awaitable()
|
|
XCTAssertEqual(result, .success(responseSession))
|
|
}
|
|
|
|
// The session should be in storage.
|
|
var savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertEqual(savedSession, responseSession)
|
|
|
|
// Now we should get back the same session if we try again, with a request
|
|
// only to check its validity.
|
|
var fetchSessionRequest = RegistrationRequestFactory.fetchSessionRequest(sessionId: responseSession.id)
|
|
|
|
// Make a new instance, which shuffles the id
|
|
responseBody = stubWireSession()
|
|
responseSession = sessionConverter(responseBody)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: fetchSessionRequest.url!.relativeString,
|
|
bodyJson: responseBody
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.beginOrRestoreSession(
|
|
e164: e164,
|
|
apnsToken: apnsToken
|
|
).awaitable()
|
|
XCTAssertEqual(result, .success(responseSession))
|
|
}
|
|
|
|
// The new (shuffled id) session should be in storage.
|
|
savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertEqual(savedSession, responseSession)
|
|
|
|
// If we have the service respond that the session is invalid, we should get a fresh
|
|
// session.
|
|
fetchSessionRequest = RegistrationRequestFactory.fetchSessionRequest(sessionId: responseSession.id)
|
|
|
|
// Make a new instance, which shuffles the id
|
|
responseBody = stubWireSession()
|
|
responseSession = sessionConverter(responseBody)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: fetchSessionRequest.url!.relativeString,
|
|
statusCode: RegistrationServiceResponses.FetchSessionResponseCodes.missingSession.rawValue
|
|
)
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: beginSessionRequest.url!.relativeString,
|
|
bodyJson: responseBody
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.beginOrRestoreSession(
|
|
e164: e164,
|
|
apnsToken: apnsToken
|
|
).awaitable()
|
|
XCTAssertEqual(result, .success(responseSession))
|
|
}
|
|
|
|
// The new (shuffled id) session should be in storage.
|
|
savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertEqual(savedSession, responseSession)
|
|
|
|
// If we complete the session, that should reset everything and behave like the first time.
|
|
|
|
db.write { registrationSessionManager.clearPersistedSession($0) }
|
|
|
|
// Should have no saved session
|
|
savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertNil(savedSession)
|
|
|
|
responseBody = stubWireSession()
|
|
responseSession = sessionConverter(responseBody)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: beginSessionRequest.url!.relativeString,
|
|
bodyJson: responseBody
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.beginOrRestoreSession(
|
|
e164: e164,
|
|
apnsToken: apnsToken
|
|
).awaitable()
|
|
XCTAssertEqual(result, .success(responseSession))
|
|
}
|
|
|
|
// The session should be in storage.
|
|
savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertEqual(savedSession, responseSession)
|
|
|
|
// If we try and request with a different e164 it should also get a new session and wipe the old one.
|
|
let newE164 = E164("+17875550101")!
|
|
responseBody = stubWireSession()
|
|
responseSession = sessionConverter(responseBody, e164: newE164)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: beginSessionRequest.url!.relativeString,
|
|
bodyJson: responseBody
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.beginOrRestoreSession(
|
|
e164: newE164,
|
|
apnsToken: apnsToken
|
|
).awaitable()
|
|
XCTAssertEqual(result, .success(responseSession))
|
|
}
|
|
|
|
// The session should be in storage.
|
|
savedSession = try db.read { transaction in
|
|
let session: RegistrationSession? = try kvStore.getCodableValue(
|
|
forKey: RegistrationSessionManagerImpl.KvStore.sessionKey,
|
|
transaction: transaction
|
|
)
|
|
return session
|
|
}
|
|
XCTAssertEqual(savedSession, responseSession)
|
|
}
|
|
|
|
public func testFulfillChallenge() async {
|
|
let captchaToken = "1234"
|
|
let pushChallengeToken = "ABCD"
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.fulfillChallengeRequest(
|
|
sessionId: oldSession.id,
|
|
captchaToken: captchaToken, // Put both, doesn't matter cuz we just match the url.
|
|
pushChallengeToken: pushChallengeToken
|
|
)
|
|
|
|
// will have a new id, which is fine cuz it lets us differentiate.
|
|
let responseBody = stubWireSession()
|
|
let responseSession = sessionConverter(responseBody)
|
|
|
|
let statusCodeResponsePairs: [(
|
|
RegistrationServiceResponses.FulfillChallengeResponseCodes,
|
|
Registration.UpdateSessionResponse,
|
|
Bool // expects session in response
|
|
)] = [
|
|
(.success, .success(responseSession), true),
|
|
(.notAccepted, .rejectedArgument(responseSession), true),
|
|
(.missingSession, .invalidSession, false),
|
|
(.notAccepted, .rejectedArgument(responseSession), true),
|
|
(.unexpectedError, .genericError, false)
|
|
]
|
|
for (statusCode, expectedResponse, sessionInBody) in statusCodeResponsePairs {
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: expectedRequest.url!.relativeString,
|
|
statusCode: statusCode.rawValue,
|
|
bodyJson: sessionInBody ? responseBody : nil
|
|
)
|
|
let result = await registrationSessionManager.fulfillChallenge(
|
|
for: oldSession,
|
|
fulfillment: [
|
|
Registration.ChallengeFulfillment.captcha(captchaToken),
|
|
.pushChallenge(pushChallengeToken)
|
|
].randomElement()!
|
|
).awaitable()
|
|
XCTAssertEqual(result, expectedResponse)
|
|
}
|
|
}
|
|
|
|
public func testFulfillChallenge_sucessResponseWithoutBody() async {
|
|
let captchaToken = "1234"
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.fulfillChallengeRequest(
|
|
sessionId: oldSession.id,
|
|
captchaToken: captchaToken,
|
|
pushChallengeToken: nil
|
|
)
|
|
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: expectedRequest.url!.relativeString,
|
|
statusCode: RegistrationServiceResponses.FulfillChallengeResponseCodes.success.rawValue,
|
|
bodyJson: nil /* empty json */
|
|
)
|
|
do {
|
|
let result = await registrationSessionManager.fulfillChallenge(
|
|
for: oldSession,
|
|
fulfillment: .captcha(captchaToken)
|
|
).awaitable()
|
|
XCTAssertEqual(result, Registration.UpdateSessionResponse.genericError)
|
|
}
|
|
}
|
|
|
|
public func testRequestVerificationCode() async {
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.requestVerificationCodeRequest(
|
|
sessionId: oldSession.id,
|
|
languageCode: nil,
|
|
countryCode: nil,
|
|
transport: .sms
|
|
)
|
|
|
|
// will have a new id, which is fine cuz it lets us differentiate.
|
|
let responseBody = stubWireSession()
|
|
|
|
let statusCodeResponsePairs: [(
|
|
RegistrationServiceResponses.RequestVerificationCodeResponseCodes,
|
|
Registration.UpdateSessionResponse,
|
|
Bool // expects session in response
|
|
)] = [
|
|
(.success, .success(sessionConverter(responseBody)), true),
|
|
(.malformedRequest, .genericError, false),
|
|
(.disallowed, .disallowed(sessionConverter(responseBody)), true),
|
|
(.missingSession, .invalidSession, false),
|
|
(.retry, .retryAfterTimeout(sessionConverter(responseBody)), true),
|
|
(.unexpectedError, .genericError, false)
|
|
]
|
|
for (statusCode, expectedResponse, sessionInBody) in statusCodeResponsePairs {
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: expectedRequest.url!.relativeString,
|
|
statusCode: statusCode.rawValue,
|
|
bodyJson: sessionInBody ? responseBody : nil
|
|
)
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
XCTAssertEqual(result, expectedResponse)
|
|
}
|
|
}
|
|
|
|
public func testRequestVerificationCodeServiceError() async {
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.requestVerificationCodeRequest(
|
|
sessionId: oldSession.id,
|
|
languageCode: nil,
|
|
countryCode: nil,
|
|
transport: .sms
|
|
)
|
|
|
|
var errorResponseJSON = """
|
|
{
|
|
"permanentFailure": false,
|
|
"reason": "providerRejected"
|
|
}
|
|
"""
|
|
|
|
mockURLSession.addResponse(TSRequestOWSURLSessionMock.Response(
|
|
matcher: { $0.url == expectedRequest.url },
|
|
statusCode: RegistrationServiceResponses.RequestVerificationCodeResponseCodes.providerFailure.rawValue,
|
|
bodyData: errorResponseJSON.data(using: .utf8)
|
|
))
|
|
|
|
do {
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
XCTAssertEqual(result, .serverFailure(Registration.ServerFailureResponse(
|
|
session: oldSession,
|
|
isPermanent: false,
|
|
reason: .providerRejected
|
|
)))
|
|
}
|
|
|
|
errorResponseJSON = """
|
|
{
|
|
"permanentFailure": false,
|
|
"reason": "someRandomValueTheClientCantParse"
|
|
}
|
|
"""
|
|
|
|
mockURLSession.addResponse(TSRequestOWSURLSessionMock.Response(
|
|
matcher: { $0.url == expectedRequest.url },
|
|
statusCode: RegistrationServiceResponses.RequestVerificationCodeResponseCodes.providerFailure.rawValue,
|
|
bodyData: errorResponseJSON.data(using: .utf8)
|
|
))
|
|
|
|
do {
|
|
let result = await registrationSessionManager.requestVerificationCode(
|
|
for: oldSession,
|
|
transport: [Registration.CodeTransport.sms, .voice].randomElement()!
|
|
).awaitable()
|
|
XCTAssertEqual(result, .serverFailure(Registration.ServerFailureResponse(
|
|
session: oldSession,
|
|
isPermanent: false,
|
|
reason: nil
|
|
)))
|
|
}
|
|
}
|
|
|
|
public func testSubmitVerificationCode() async {
|
|
let code = "1234"
|
|
let oldSession = stubSession()
|
|
let expectedRequest = RegistrationRequestFactory.submitVerificationCodeRequest(
|
|
sessionId: oldSession.id,
|
|
code: code
|
|
)
|
|
|
|
// will have a new ids, which is fine cuz it lets us differentiate.
|
|
let verifiedResponseBody = stubWireSession(verified: true)
|
|
let verifiedResponseSession = sessionConverter(verifiedResponseBody)
|
|
let unVerifiedResponseBody = stubWireSession(verified: false)
|
|
let unVerifiedResponseSession = sessionConverter(unVerifiedResponseBody)
|
|
let unVerifiedWithNoAttemptResponseBody = stubWireSession(verified: false, hasNextVerificationAttempt: false)
|
|
let unVerifiedWithNoAttemptResponseSession = sessionConverter(unVerifiedWithNoAttemptResponseBody)
|
|
|
|
let statusCodeResponsePairs: [(
|
|
RegistrationServiceResponses.SubmitVerificationCodeResponseCodes,
|
|
Registration.UpdateSessionResponse,
|
|
RegistrationServiceResponses.RegistrationSession?
|
|
)] = [
|
|
(.success, .success(verifiedResponseSession), verifiedResponseBody),
|
|
(.success, .rejectedArgument(unVerifiedResponseSession), unVerifiedResponseBody),
|
|
(.malformedRequest, .genericError, nil),
|
|
(.missingSession, .invalidSession, nil),
|
|
(.newCodeRequired, .success(verifiedResponseSession), verifiedResponseBody),
|
|
(.newCodeRequired, .retryAfterTimeout(unVerifiedResponseSession), unVerifiedResponseBody),
|
|
(.newCodeRequired, .disallowed(unVerifiedWithNoAttemptResponseSession), unVerifiedWithNoAttemptResponseBody),
|
|
(.retry, .retryAfterTimeout(unVerifiedResponseSession), unVerifiedResponseBody),
|
|
(.unexpectedError, .genericError, nil)
|
|
]
|
|
for (statusCode, expectedResponse, sessionInBody) in statusCodeResponsePairs {
|
|
mockURLSession.addResponse(
|
|
forUrlSuffix: expectedRequest.url!.relativeString,
|
|
statusCode: statusCode.rawValue,
|
|
bodyJson: sessionInBody
|
|
)
|
|
let result = await registrationSessionManager.submitVerificationCode(
|
|
for: oldSession,
|
|
code: code
|
|
).awaitable()
|
|
XCTAssertEqual(result, expectedResponse)
|
|
}
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
// MARK: Stub objects
|
|
|
|
private func stubSession() -> RegistrationSession {
|
|
return RegistrationSession(
|
|
id: UUID().uuidString,
|
|
e164: E164("+17875550100")!, // For our purposes, can be fixed.
|
|
receivedDate: date,
|
|
nextSMS: 1,
|
|
nextCall: 1,
|
|
nextVerificationAttempt: nil,
|
|
allowedToRequestCode: true,
|
|
requestedInformation: [],
|
|
hasUnknownChallengeRequiringAppUpdate: false,
|
|
verified: false
|
|
)
|
|
}
|
|
|
|
private func stubWireSession(
|
|
verified: Bool = false,
|
|
hasNextVerificationAttempt: Bool = true
|
|
) -> RegistrationServiceResponses.RegistrationSession {
|
|
return RegistrationServiceResponses.RegistrationSession(
|
|
id: UUID().uuidString,
|
|
nextSms: (0...100).randomElement(),
|
|
nextCall: (0...100).randomElement(),
|
|
nextVerificationAttempt: hasNextVerificationAttempt ? (0...100).randomElement() : nil,
|
|
allowedToRequestCode: false,
|
|
requestedInformation: [.captcha, .pushChallenge],
|
|
verified: verified
|
|
)
|
|
}
|
|
|
|
// Keep this independent of the production code converter for an extra layer of durability.
|
|
private func sessionConverter(
|
|
_ wireSession: RegistrationServiceResponses.RegistrationSession,
|
|
e164: E164 = E164("+17875550100")!
|
|
) -> RegistrationSession {
|
|
let requestedInformation: [RegistrationSession.Challenge] = wireSession.requestedInformation.compactMap {
|
|
switch $0 {
|
|
case .captcha: return .captcha
|
|
case .pushChallenge: return .pushChallenge
|
|
case .unknown:
|
|
XCTFail("If you want to test wire conversion for real be explicit")
|
|
return .captcha
|
|
}
|
|
}
|
|
return RegistrationSession(
|
|
id: wireSession.id,
|
|
e164: e164,
|
|
receivedDate: date,
|
|
nextSMS: wireSession.nextSms.map { TimeInterval($0) },
|
|
nextCall: wireSession.nextCall.map { TimeInterval($0) },
|
|
nextVerificationAttempt: wireSession.nextVerificationAttempt.map { TimeInterval($0) },
|
|
allowedToRequestCode: wireSession.allowedToRequestCode,
|
|
requestedInformation: requestedInformation,
|
|
hasUnknownChallengeRequiringAppUpdate: false,
|
|
verified: wireSession.verified
|
|
)
|
|
}
|
|
}
|