Skip to content

Commit f015e12

Browse files
committed
Merge branch 'main' into release
* main: chore: update face fit instruction text and error message (#49) chore: refactor session fit timeout from backend service (#46) chore: provide more fine-grained websocket close code on errors (#44)
2 parents b05510a + 680bb19 commit f015e12

File tree

13 files changed

+89
-51
lines changed

13 files changed

+89
-51
lines changed

HostApp/HostApp.xcodeproj/project.pbxproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@
701701
repositoryURL = "https://github.com/aws-amplify/amplify-ui-swift-liveness";
702702
requirement = {
703703
kind = upToNextMajorVersion;
704-
minimumVersion = 1.0.0;
704+
minimumVersion = 1.1.1;
705705
};
706706
};
707707
9077AB3529E5D28900433155 /* XCRemoteSwiftPackageReference "amplify-swift" */ = {

HostApp/HostApp/Views/LivenessCheckErrorContentView.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ struct LivenessCheckErrorContentView: View {
2828
extension LivenessCheckErrorContentView {
2929
static let mock = LivenessCheckErrorContentView(
3030
name: "Time out",
31-
description: "Face didn't fit inside oval in time limit. Try again and completely fill the oval with face within 7 seconds."
31+
description: "Face didn't fit inside oval in time limit. Try again and completely fill the oval with face in it."
3232
)
3333

3434
static let unexpected = LivenessCheckErrorContentView(
@@ -38,7 +38,7 @@ extension LivenessCheckErrorContentView {
3838

3939
static let faceMatchTimeOut = LivenessCheckErrorContentView(
4040
name: "Time out",
41-
description: "Face did not fill oval in time limit. Try again and completely fill the oval with face within 7 seconds."
41+
description: "Face did not fill oval in time limit. Try again and completely fill the oval with face in it."
4242
)
4343

4444
static let sessionTimeOut = LivenessCheckErrorContentView(

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ let package = Package(
1313
targets: ["FaceLiveness"]),
1414
],
1515
dependencies: [
16-
.package(url: "https://github.com/aws-amplify/amplify-swift", from: "2.14.1")
16+
.package(url: "https://github.com/aws-amplify/amplify-swift", from: "2.15.4")
1717
],
1818
targets: [
1919
.target(

Sources/FaceLiveness/Resources/Base.lproj/Localizable.strings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"amplify_ui_liveness_get_ready_steps_title" = "Follow the instructions to complete the check:";
1616
"amplify_ui_liveness_get_ready_face_not_covered" = "Make sure your face is not covered with sunglasses or a mask.";
1717
"amplify_ui_liveness_get_ready_lighting" = "Move to a well-lit place that is not in direct sunlight.";
18-
"amplify_ui_liveness_get_ready_fit_face" = "When an oval appears, fill the oval with your face within 7 seconds.";
18+
"amplify_ui_liveness_get_ready_fit_face" = "When an oval appears, fill the oval with your face in it.";
1919
"amplify_ui_liveness_get_ready_begin_check" = "Begin Check";
2020
"amplify_ui_liveness_get_ready_good_fit_example" = "Good fit";
2121
"amplify_ui_liveness_get_ready_too_far_example" = "Too far";

Sources/FaceLiveness/Utilities/LocalizedStringKey+Liveness.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ extension LocalizedStringKey {
5858
"amplify_ui_liveness_get_ready_lighting"
5959
)
6060

61-
/// en = "When an oval appears, fill the oval with your face within 7 seconds."
61+
/// en = "When an oval appears, fill the oval with your face in it."
6262
static let get_ready_fit_face = LocalizedStringKey(
6363
"amplify_ui_liveness_get_ready_fit_face"
6464
)

Sources/FaceLiveness/Utilities/UserAgent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ struct UserAgentValues {
5757
swiftVersion: Swift().version(),
5858
unameMachine: Device.current.machine.replacingOccurrences(of: ",", with: "_"),
5959
locale: Locale.current.identifier,
60-
lib: "lib/amplify-ui-swift-face-liveness/1.1.0",
60+
lib: "lib/amplify-ui-swift-face-liveness/1.1.1",
6161
additionalMetadata: additionalMetadata
6262
)
6363
}

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionView.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ public struct FaceLivenessDetectorView: View {
170170
isPresented = false
171171
onCompletion(.success(()))
172172
case .encounteredUnrecoverableError(let error):
173-
viewModel.livenessService.closeSocket(with: .normalClosure)
173+
let closeCode = error.webSocketCloseCode ?? .normalClosure
174+
viewModel.livenessService.closeSocket(with: closeCode)
174175
isPresented = false
175176
onCompletion(.failure(mapError(error)))
176177
default:
@@ -188,8 +189,6 @@ public struct FaceLivenessDetectorView: View {
188189
return .sessionTimedOut
189190
case .socketClosed:
190191
return .socketClosed
191-
case .invalidFaceMovementDuringCountdown:
192-
return .countdownFaceTooClose
193192
default:
194193
return .cameraPermissionDenied
195194
}
@@ -228,12 +227,6 @@ public struct FaceLivenessDetectorView: View {
228227
}
229228
}
230229

231-
enum CountdownDisplayState {
232-
case waitingToDisplay
233-
case displaying
234-
case finishedDisplaying
235-
}
236-
237230
enum DisplayState {
238231
case awaitingLivenessSession
239232
case displayingGetReadyView

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel+FaceDetectionResultHandler.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,26 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
8383
}
8484
}
8585

86-
func handleNoMatch(instruction: Instructor.Instruction, percentage: Double) {
87-
let noMatchTimeoutInterval: TimeInterval = 7
86+
func handleNoFaceFit(instruction: Instructor.Instruction, percentage: Double) {
8887
self.livenessState.awaitingFaceMatch(with: instruction, nearnessPercentage: percentage)
89-
if noMatchStartTime == nil {
90-
noMatchStartTime = Date()
88+
if noFitStartTime == nil {
89+
noFitStartTime = Date()
9190
}
92-
if let elapsedTime = noMatchStartTime?.timeIntervalSinceNow, abs(elapsedTime) >= noMatchTimeoutInterval {
91+
if let elapsedTime = noFitStartTime?.timeIntervalSinceNow, abs(elapsedTime) >= noFitTimeoutInterval {
92+
self.livenessState
93+
.unrecoverableStateEncountered(.timedOut)
94+
self.captureSession.stopRunning()
95+
}
96+
}
97+
98+
func handleNoFaceDetected() {
99+
if noFitStartTime == nil {
100+
noFitStartTime = Date()
101+
}
102+
if let elapsedTime = noFitStartTime?.timeIntervalSinceNow, abs(elapsedTime) >= noFitTimeoutInterval {
93103
self.livenessState
94104
.unrecoverableStateEncountered(.timedOut)
95105
self.captureSession.stopRunning()
96-
return
97106
}
98107
}
99108

@@ -109,14 +118,15 @@ extension FaceLivenessDetectionViewModel: FaceDetectionResultHandler {
109118
self.livenessViewControllerDelegate?.displayFreshness(colorSequences: colorSequences)
110119
let generator = UINotificationFeedbackGenerator()
111120
generator.notificationOccurred(.success)
112-
self.noMatchStartTime = nil
121+
self.noFitStartTime = nil
113122

114123
case .tooClose(_, let percentage),
115124
.tooFar(_, let percentage),
116125
.tooFarLeft(_, let percentage),
117126
.tooFarRight(_, let percentage):
118-
self.handleNoMatch(instruction: instruction, percentage: percentage)
119-
default: break
127+
self.handleNoFaceFit(instruction: instruction, percentage: percentage)
128+
case .none:
129+
self.handleNoFaceDetected()
120130
}
121131
}
122132
}

Sources/FaceLiveness/Views/Liveness/FaceLivenessDetectionViewModel.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import AVFoundation
1111
@_spi(PredictionsFaceLiveness) import AWSPredictionsPlugin
1212

1313
fileprivate let videoSize: CGSize = .init(width: 480, height: 640)
14+
fileprivate let defaultNoFitTimeoutInterval: TimeInterval = 7
1415

1516
@MainActor
1617
class FaceLivenessDetectionViewModel: ObservableObject {
@@ -39,7 +40,15 @@ class FaceLivenessDetectionViewModel: ObservableObject {
3940
var faceGuideRect: CGRect!
4041
var initialClientEvent: InitialClientEvent?
4142
var faceMatchedTimestamp: UInt64?
42-
var noMatchStartTime: Date?
43+
var noFitStartTime: Date?
44+
45+
var noFitTimeoutInterval: TimeInterval {
46+
if let sessionTimeoutMilliSec = sessionConfiguration?.ovalMatchChallenge.oval.ovalFitTimeout {
47+
return TimeInterval(sessionTimeoutMilliSec/1_000)
48+
} else {
49+
return defaultNoFitTimeoutInterval
50+
}
51+
}
4352

4453
init(
4554
faceDetector: FaceDetector,
@@ -108,7 +117,7 @@ class FaceLivenessDetectionViewModel: ObservableObject {
108117
@objc func willResignActive(_ notification: Notification) {
109118
DispatchQueue.main.async {
110119
self.stopRecording()
111-
self.livenessState.unrecoverableStateEncountered(.socketClosed)
120+
self.livenessState.unrecoverableStateEncountered(.viewResignation)
112121
}
113122
}
114123

Sources/FaceLiveness/Views/Liveness/LivenessStateMachine.swift

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,18 +124,28 @@ struct LivenessStateMachine {
124124

125125
struct LivenessError: Error, Equatable {
126126
let code: UInt8
127-
128-
static let unknown = LivenessError(code: 0)
129-
static let missingVideoPermission = LivenessError(code: 1)
130-
static let errorWithUnderlyingOSFramework = LivenessError(code: 2)
131-
static let userCancelled = LivenessError(code: 3)
132-
static let timedOut = LivenessError(code: 4)
133-
static let couldNotOpenStream = LivenessError(code: 5)
134-
static let socketClosed = LivenessError(code: 6)
135-
static let invalidFaceMovementDuringCountdown = LivenessError(code: 7)
127+
let webSocketCloseCode: URLSessionWebSocketTask.CloseCode?
128+
129+
static let unknown = LivenessError(code: 0, webSocketCloseCode: .unexpectedRuntimeError)
130+
static let missingVideoPermission = LivenessError(code: 1, webSocketCloseCode: .missingVideoPermission)
131+
static let errorWithUnderlyingOSFramework = LivenessError(code: 2, webSocketCloseCode: .unexpectedRuntimeError)
132+
static let userCancelled = LivenessError(code: 3, webSocketCloseCode: .ovalFitUserClosedSession)
133+
static let timedOut = LivenessError(code: 4, webSocketCloseCode: .ovalFitMatchTimeout)
134+
static let couldNotOpenStream = LivenessError(code: 5, webSocketCloseCode: .unexpectedRuntimeError)
135+
static let socketClosed = LivenessError(code: 6, webSocketCloseCode: .normalClosure)
136+
static let viewResignation = LivenessError(code: 8, webSocketCloseCode: .viewClosure)
136137

137138
static func == (lhs: LivenessError, rhs: LivenessError) -> Bool {
138139
lhs.code == rhs.code
139140
}
140141
}
141142
}
143+
144+
extension URLSessionWebSocketTask.CloseCode {
145+
static let ovalFitMatchTimeout = URLSessionWebSocketTask.CloseCode(rawValue: 4001)
146+
static let ovalFitTimeOutNoFaceDetected = URLSessionWebSocketTask.CloseCode(rawValue: 4002)
147+
static let ovalFitUserClosedSession = URLSessionWebSocketTask.CloseCode(rawValue: 4003)
148+
static let viewClosure = URLSessionWebSocketTask.CloseCode(rawValue: 4004)
149+
static let unexpectedRuntimeError = URLSessionWebSocketTask.CloseCode(rawValue: 4005)
150+
static let missingVideoPermission = URLSessionWebSocketTask.CloseCode(rawValue: 4006)
151+
}

0 commit comments

Comments
 (0)