@@ -36,6 +36,11 @@ enum IdentityAnalyticsClientError: AnalyticLoggableErrorV2 {
3636 }
3737}
3838
39+ private enum CameraPermissionError : String , AnalyticLoggableStringErrorV2 {
40+ case denied = " cameraPermissionDenied "
41+ case unknown = " cameraPermissionUnknown "
42+ }
43+
3944/// Wrapper for AnalyticsClient that formats Identity-specific analytics
4045final class IdentityAnalyticsClient {
4146
@@ -85,6 +90,15 @@ final class IdentityAnalyticsClient {
8590 case selfie
8691 }
8792
93+ enum CameraSource : String {
94+ case cameraSession = " camera_session "
95+ case imagePicker = " image_picker "
96+ }
97+ enum CameraEventKind : String {
98+ case permission = " permission "
99+ case runtimeError = " runtime_error "
100+ }
101+
88102 static let sharedAnalyticsClient = AnalyticsClientV2 (
89103 clientId: " mobile-identity-sdk " ,
90104 origin: " stripe-identity-ios "
@@ -143,6 +157,36 @@ final class IdentityAnalyticsClient {
143157 }
144158 }
145159
160+ private func cameraMetadata(
161+ screenName: ScreenName ,
162+ cameraSource: CameraSource ,
163+ cameraEventKind: CameraEventKind
164+ ) -> [ String : Any ] {
165+ return [
166+ " screen_name " : screenName. rawValue,
167+ " camera_source " : cameraSource. rawValue,
168+ " camera_event_kind " : cameraEventKind. rawValue,
169+ ]
170+ }
171+
172+ private func cameraAccessState( isGranted: Bool ? ) -> String {
173+ guard let isGranted = isGranted else {
174+ return " unknown "
175+ }
176+ return isGranted ? " granted " : " denied "
177+ }
178+
179+ private func cameraPermissionError( isGranted: Bool ? ) -> CameraPermissionError {
180+ guard let isGranted = isGranted else {
181+ return . unknown
182+ }
183+ if isGranted {
184+ assertionFailure ( " cameraPermissionError should not be created for granted camera access " )
185+ return . unknown
186+ }
187+ return . denied
188+ }
189+
146190 private func logAnalytic(
147191 _ eventName: EventName ,
148192 metadata: [ String : Any ] ,
@@ -219,8 +263,8 @@ final class IdentityAnalyticsClient {
219263 }
220264 }
221265
222- /// Helper to create metadata common to both failed, canceled, and succeed analytic events
223- private func failedCanceledSucceededCommonMetadataPayload (
266+ /// Helper to create metadata common to flow outcome analytic events
267+ private func flowOutcomeCommonMetadataPayload (
224268 sheetController: VerificationSheetControllerProtocol
225269 ) -> [ String : Any ] {
226270 var metadata : [ String : Any ] = [ : ]
@@ -239,13 +283,28 @@ final class IdentityAnalyticsClient {
239283 return metadata
240284 }
241285
286+ private func addLastScreenNameIfAvailable(
287+ to metadata: inout [ String : Any ] ,
288+ sheetController: VerificationSheetControllerProtocol
289+ ) {
290+ if let lastScreenName = sheetController. flowController. analyticsLastScreen? . analyticsScreenName. rawValue {
291+ metadata [ " last_screen_name " ] = lastScreenName
292+ }
293+ }
294+
242295 /// Logs an event when the verification sheet is closed
243296 private func logSheetClosed( sessionResult: String , sheetController: VerificationSheetControllerProtocol ) {
297+ var metadata = flowOutcomeCommonMetadataPayload (
298+ sheetController: sheetController
299+ )
300+ metadata [ " session_result " ] = sessionResult
301+ addLastScreenNameIfAvailable (
302+ to: & metadata,
303+ sheetController: sheetController
304+ )
244305 logAnalytic (
245306 . sheetClosed,
246- metadata: [
247- " session_result " : sessionResult
248- ] ,
307+ metadata: metadata,
249308 verificationPage: try ? sheetController. verificationPageResponse? . get ( )
250309 )
251310 }
@@ -257,7 +316,11 @@ final class IdentityAnalyticsClient {
257316 filePath: StaticString ,
258317 line: UInt
259318 ) {
260- var metadata = failedCanceledSucceededCommonMetadataPayload (
319+ var metadata = flowOutcomeCommonMetadataPayload (
320+ sheetController: sheetController
321+ )
322+ addLastScreenNameIfAvailable (
323+ to: & metadata,
261324 sheetController: sheetController
262325 )
263326 metadata [ " error " ] = AnalyticsClientV2 . serialize (
@@ -273,12 +336,13 @@ final class IdentityAnalyticsClient {
273336 private func logVerificationCanceled(
274337 sheetController: VerificationSheetControllerProtocol
275338 ) {
276- var metadata = failedCanceledSucceededCommonMetadataPayload (
339+ var metadata = flowOutcomeCommonMetadataPayload (
340+ sheetController: sheetController
341+ )
342+ addLastScreenNameIfAvailable (
343+ to: & metadata,
277344 sheetController: sheetController
278345 )
279- if let lastScreen = sheetController. flowController. analyticsLastScreen {
280- metadata [ " last_screen_name " ] = lastScreen. analyticsScreenName. rawValue
281- }
282346
283347 logAnalytic ( . verificationCanceled, metadata: metadata, verificationPage: try ? sheetController. verificationPageResponse? . get ( ) )
284348 }
@@ -287,7 +351,7 @@ final class IdentityAnalyticsClient {
287351 func logVerificationSucceeded(
288352 sheetController: VerificationSheetControllerProtocol
289353 ) {
290- var metadata = failedCanceledSucceededCommonMetadataPayload (
354+ var metadata = flowOutcomeCommonMetadataPayload (
291355 sheetController: sheetController
292356 )
293357
@@ -317,11 +381,15 @@ final class IdentityAnalyticsClient {
317381 /// Logs an event when a screen is presented
318382 func logScreenAppeared(
319383 screenName: ScreenName ,
384+ previousScreenName: ScreenName ? = nil ,
320385 sheetController: VerificationSheetControllerProtocol
321386 ) {
322- let metadata : [ String : Any ] = [
387+ var metadata : [ String : Any ] = [
323388 " screen_name " : screenName. rawValue,
324389 ]
390+ if let previousScreenName = previousScreenName {
391+ metadata [ " previous_screen_name " ] = previousScreenName. rawValue
392+ }
325393
326394 logAnalytic ( . screenAppeared, metadata: metadata, verificationPage: try ? sheetController. verificationPageResponse? . get ( ) )
327395 }
@@ -330,10 +398,16 @@ final class IdentityAnalyticsClient {
330398 func logCameraError(
331399 sheetController: VerificationSheetControllerProtocol ,
332400 error: Error ,
401+ screenName: ScreenName ,
402+ cameraSource: CameraSource ,
333403 filePath: StaticString = #filePath,
334404 line: UInt = #line
335405 ) {
336- var metadata : [ String : Any ] = [ : ]
406+ var metadata = cameraMetadata (
407+ screenName: screenName,
408+ cameraSource: cameraSource,
409+ cameraEventKind: . runtimeError
410+ )
337411 metadata [ " error " ] = AnalyticsClientV2 . serialize (
338412 error: error,
339413 filePath: filePath,
@@ -342,15 +416,74 @@ final class IdentityAnalyticsClient {
342416 logAnalytic ( . cameraError, metadata: metadata, verificationPage: try ? sheetController. verificationPageResponse? . get ( ) )
343417 }
344418
345- /// Logs either a permission denied or granted event when the camera permissions are checked prior to starting a camera session
419+ /// Logs a permission analytic when camera access is checked prior to starting a camera session
346420 func logCameraPermissionsChecked(
347421 sheetController: VerificationSheetControllerProtocol ,
348- isGranted: Bool ?
422+ isGranted: Bool ? ,
423+ screenName: ScreenName ,
424+ cameraSource: CameraSource ,
425+ filePath: StaticString = #filePath,
426+ line: UInt = #line
349427 ) {
350- let eventName : EventName =
351- ( isGranted == true ) ? . cameraPermissionGranted : . cameraPermissionDenied
428+ guard isGranted != true else {
429+ var metadata = cameraMetadata (
430+ screenName: screenName,
431+ cameraSource: cameraSource,
432+ cameraEventKind: . permission
433+ )
434+ metadata [ " camera_access_state " ] = cameraAccessState ( isGranted: isGranted)
435+ logAnalytic (
436+ . cameraPermissionGranted,
437+ metadata: metadata,
438+ verificationPage: try ? sheetController. verificationPageResponse? . get ( )
439+ )
440+ return
441+ }
442+ logCameraPermissionDeniedOrUnknown (
443+ sheetController: sheetController,
444+ isGranted: isGranted,
445+ screenName: screenName,
446+ cameraSource: cameraSource,
447+ filePath: filePath,
448+ line: line
449+ )
450+ }
352451
353- logAnalytic ( eventName, metadata: [ : ] , verificationPage: try ? sheetController. verificationPageResponse? . get ( ) )
452+ /// Logs a permission analytic only when camera access is denied or unknown
453+ func logCameraPermissionDeniedOrUnknown(
454+ sheetController: VerificationSheetControllerProtocol ,
455+ isGranted: Bool ? ,
456+ screenName: ScreenName ,
457+ cameraSource: CameraSource ,
458+ filePath: StaticString = #filePath,
459+ line: UInt = #line
460+ ) {
461+ guard isGranted != true else {
462+ return
463+ }
464+ var metadata = cameraMetadata (
465+ screenName: screenName,
466+ cameraSource: cameraSource,
467+ cameraEventKind: . permission
468+ )
469+ metadata [ " camera_access_state " ] = cameraAccessState ( isGranted: isGranted)
470+
471+ logAnalytic (
472+ . cameraPermissionDenied,
473+ metadata: metadata,
474+ verificationPage: try ? sheetController. verificationPageResponse? . get ( )
475+ )
476+
477+ metadata [ " error " ] = AnalyticsClientV2 . serialize (
478+ error: cameraPermissionError ( isGranted: isGranted) ,
479+ filePath: filePath,
480+ line: line
481+ )
482+ logAnalytic (
483+ . cameraError,
484+ metadata: metadata,
485+ verificationPage: try ? sheetController. verificationPageResponse? . get ( )
486+ )
354487 }
355488
356489 /// Logs an event when document capture times out
@@ -515,20 +648,44 @@ final class IdentityAnalyticsClient {
515648 /// Logs when an error occurs.
516649 func logGenericError(
517650 error: Error ,
651+ additionalMetadata: [ String : Any ] = [ : ] ,
518652 filePath: StaticString = #filePath,
519653 line: UInt = #line,
520654 sheetController: VerificationSheetControllerProtocol
521655 ) {
656+ var metadata = additionalMetadata
657+ metadata [ " error_details " ] = AnalyticsClientV2 . serialize (
658+ error: error,
659+ filePath: filePath,
660+ line: line
661+ )
522662 logAnalytic (
523663 . genericError,
524- metadata: [
525- " error_details " : AnalyticsClientV2 . serialize (
526- error: error,
527- filePath: filePath,
528- line: line
529- ) ,
530- ] ,
664+ metadata: metadata,
531665 verificationPage: try ? sheetController. verificationPageResponse? . get ( )
532666 )
533667 }
668+
669+ static func logUnscopedGenericError(
670+ _ error: Error ,
671+ context: String ,
672+ additionalMetadata: [ String : Any ] = [ : ] ,
673+ filePath: StaticString = #filePath,
674+ line: UInt = #line
675+ ) {
676+ var eventMetadata = additionalMetadata
677+ eventMetadata [ " error_context " ] = context
678+ eventMetadata [ " error_details " ] = AnalyticsClientV2 . serialize (
679+ error: error,
680+ filePath: filePath,
681+ line: line
682+ )
683+
684+ sharedAnalyticsClient. log (
685+ eventName: EventName . genericError. rawValue,
686+ parameters: [
687+ " event_metadata " : eventMetadata,
688+ ]
689+ )
690+ }
534691}
0 commit comments