diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/Info.plist b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/Info.plist
index 2d1b744cd..f6741846b 100644
--- a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/Info.plist
+++ b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/Info.plist
@@ -8,32 +8,32 @@
DebugSymbolsPath
dSYMs
LibraryIdentifier
- ios-arm64_x86_64-simulator
+ ios-arm64
LibraryPath
BridgeClient.framework
SupportedArchitectures
arm64
- x86_64
SupportedPlatform
ios
- SupportedPlatformVariant
- simulator
DebugSymbolsPath
dSYMs
LibraryIdentifier
- ios-arm64
+ ios-arm64_x86_64-simulator
LibraryPath
BridgeClient.framework
SupportedArchitectures
arm64
+ x86_64
SupportedPlatform
ios
+ SupportedPlatformVariant
+ simulator
CFBundlePackageType
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient
index 2e31dba79..be4d423df 100755
Binary files a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient and b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient differ
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
index ec804d922..0077b7c94 100644
--- a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
+++ b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
@@ -6,7 +6,7 @@
#import
#import
-@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
+@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUploadedFileRecordCompanion, BridgeClientUploadedFileRecord, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientPendingUploadFile, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
@protocol BridgeClientPlatformConfig, BridgeClientBridgeConfig, BridgeClientIOSPlatformConfig, BridgeClientNativeLogWriter, BridgeClientKotlinComparable, BridgeClientKotlinx_serialization_coreKSerializer, BridgeClientUploadFileIdentifiable, BridgeClientKoin_coreKoinComponent, BridgeClientParticipantScheduleMutator, BridgeClientKotlinx_coroutines_coreJob, BridgeClientEtagStorageCache, BridgeClientKtor_client_coreHttpClientPlugin, BridgeClientHttpUtil, BridgeClientRuntimeColumnAdapter, BridgeClientRuntimeTransactionWithoutReturn, BridgeClientRuntimeTransactionWithReturn, BridgeClientRuntimeTransacterBase, BridgeClientRuntimeTransacter, BridgeClientBridgeResourceDatabase, BridgeClientRuntimeSqlDriver, BridgeClientRuntimeSqlSchema, BridgeClientDbDriverFactory, BridgeClientKotlinx_coroutines_coreFlow, BridgeClientKotlinx_coroutines_coreCoroutineScope, BridgeClientBridgeErrorStatusNotifier, BridgeClientAuthenticationProvider, BridgeClientKotlinx_coroutines_coreStateFlow, BridgeClientKotlinIterator, BridgeClientKotlinx_serialization_coreEncoder, BridgeClientKotlinx_serialization_coreSerialDescriptor, BridgeClientKotlinx_serialization_coreSerializationStrategy, BridgeClientKotlinx_serialization_coreDecoder, BridgeClientKotlinx_serialization_coreDeserializationStrategy, BridgeClientKoin_coreKoinScopeComponent, BridgeClientKoin_coreQualifier, BridgeClientKotlinKClass, BridgeClientKotlinLazy, BridgeClientKotlinx_coroutines_coreChildHandle, BridgeClientKotlinx_coroutines_coreChildJob, BridgeClientKotlinx_coroutines_coreDisposableHandle, BridgeClientKotlinSequence, BridgeClientKotlinx_coroutines_coreSelectClause0, BridgeClientKotlinCoroutineContextKey, BridgeClientKotlinCoroutineContextElement, BridgeClientKotlinCoroutineContext, BridgeClientKtor_ioCloseable, BridgeClientKtor_client_coreHttpClientEngine, BridgeClientKtor_client_coreHttpClientEngineCapability, BridgeClientKtor_utilsAttributes, BridgeClientRuntimeTransactionCallbacks, BridgeClientRuntimeQueryListener, BridgeClientRuntimeQueryResult, BridgeClientRuntimeSqlPreparedStatement, BridgeClientRuntimeSqlCursor, BridgeClientRuntimeCloseable, BridgeClientKotlinx_coroutines_coreFlowCollector, BridgeClientSqliter_driverDatabaseManager, BridgeClientKotlinx_coroutines_coreSharedFlow, BridgeClientKotlinx_serialization_coreCompositeEncoder, BridgeClientKotlinAnnotation, BridgeClientKotlinx_serialization_coreCompositeDecoder, BridgeClientKoin_coreScopeCallback, BridgeClientKotlinKDeclarationContainer, BridgeClientKotlinKAnnotatedElement, BridgeClientKotlinKClassifier, BridgeClientKotlinx_coroutines_coreParentJob, BridgeClientKotlinx_coroutines_coreSelectInstance, BridgeClientKotlinx_coroutines_coreSelectClause, BridgeClientKotlinSuspendFunction2, BridgeClientSqliter_driverDatabaseConnection, BridgeClientKotlinx_serialization_coreSerializersModuleCollector, BridgeClientKtor_httpHeaders, BridgeClientKotlinContinuation, BridgeClientKotlinContinuationInterceptor, BridgeClientKotlinx_coroutines_coreRunnable, BridgeClientKotlinFunction, BridgeClientKtor_httpHttpMessage, BridgeClientKtor_ioByteReadChannel, BridgeClientKtor_httpHttpMessageBuilder, BridgeClientKtor_client_coreHttpRequest, BridgeClientSqliter_driverStatement, BridgeClientSqliter_driverLogger, BridgeClientKtor_httpParameters, BridgeClientKotlinMapEntry, BridgeClientKtor_utilsStringValues, BridgeClientKtor_ioReadSession, BridgeClientKotlinSuspendFunction1, BridgeClientKotlinAppendable, BridgeClientKtor_utilsStringValuesBuilder, BridgeClientKtor_httpParametersBuilder, BridgeClientKotlinKType, BridgeClientSqliter_driverCursor, BridgeClientKtor_ioObjectPool;
@@ -2069,10 +2069,8 @@ __attribute__((swift_name("UploadMetadata")))
@property (class, readonly, getter=companion) BridgeClientUploadMetadataCompanion *companion __attribute__((swift_name("companion")));
- (BridgeClientUploadMetadata *)doCopyInstanceGuid:(NSString * _Nullable)instanceGuid eventTimestamp:(NSString * _Nullable)eventTimestamp startedOn:(NSString * _Nullable)startedOn __attribute__((swift_name("doCopy(instanceGuid:eventTimestamp:startedOn:)")));
- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
-- (NSString * _Nullable)getId __attribute__((swift_name("getId()")));
- (NSUInteger)hash __attribute__((swift_name("hash()")));
- (NSString *)description __attribute__((swift_name("description()")));
-- (NSDictionary *)toStringMap __attribute__((swift_name("toStringMap()")));
@property (readonly) NSString * _Nullable eventTimestamp __attribute__((swift_name("eventTimestamp")));
@property (readonly) NSString * _Nullable instanceGuid __attribute__((swift_name("instanceGuid")));
@property (readonly) NSString * _Nullable startedOn __attribute__((swift_name("startedOn")));
@@ -2119,6 +2117,36 @@ __attribute__((swift_name("UploadSession.Companion")))
@end
+/**
+ * @note annotations
+ * kotlinx.serialization.Serializable
+*/
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord")))
+@interface BridgeClientUploadedFileRecord : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("init(filePath:uploadTimestamp:uploadSessionId:metadata:)"))) __attribute__((objc_designated_initializer));
+@property (class, readonly, getter=companion) BridgeClientUploadedFileRecordCompanion *companion __attribute__((swift_name("companion")));
+- (BridgeClientUploadedFileRecord *)doCopyFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("doCopy(filePath:uploadTimestamp:uploadSessionId:metadata:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) BridgeClientUploadMetadata * _Nullable metadata __attribute__((swift_name("metadata")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@property (readonly) BridgeClientKotlinx_datetimeInstant *uploadTimestamp __attribute__((swift_name("uploadTimestamp")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord.Companion")))
+@interface BridgeClientUploadedFileRecordCompanion : BridgeClientBase
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)companion __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) BridgeClientUploadedFileRecordCompanion *shared __attribute__((swift_name("shared")));
+- (id)serializer __attribute__((swift_name("serializer()")));
+@end
+
+
/**
* @note annotations
* kotlinx.serialization.Serializable
@@ -2498,8 +2526,11 @@ __attribute__((swift_name("NativeUploadManager")))
@interface BridgeClientNativeUploadManager : BridgeClientBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
-- (NSArray *)getUploadFiles __attribute__((swift_name("getUploadFiles()")));
-- (void)markUploadFileFinishedFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:callBack:)")));
+- (void)getPendingUploadFilesCallBack:(void (^)(NSArray *))callBack __attribute__((swift_name("getPendingUploadFiles(callBack:)")));
+- (BOOL)hasMarkedFileAsUploadedFilePath:(NSString *)filePath __attribute__((swift_name("hasMarkedFileAsUploaded(filePath:)")));
+- (BOOL)hasPendingUploads __attribute__((swift_name("hasPendingUploads()")));
+- (void)markUploadFileFinishedFilePath:(NSString *)filePath uploadSessionId:(NSString *)uploadSessionId callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:uploadSessionId:callBack:)")));
+- (void)markUploadUnrecoverableFailureFilePath:(NSString *)filePath __attribute__((swift_name("markUploadUnrecoverableFailure(filePath:)")));
- (void)processFinishedUploadsCallBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("processFinishedUploads(callBack:)")));
- (void)queueAndRequestUploadSessionUploadFile:(BridgeClientUploadFile *)uploadFile callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("queueAndRequestUploadSession(uploadFile:callBack:)")));
- (void)requestUploadSessionFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("requestUploadSession(filePath:callBack:)")));
@@ -3095,6 +3126,7 @@ __attribute__((swift_name("ResourceType")))
@property (class, readonly) BridgeClientResourceType *study __attribute__((swift_name("study")));
@property (class, readonly) BridgeClientResourceType *studyInfo __attribute__((swift_name("studyInfo")));
@property (class, readonly) BridgeClientResourceType *participantReport __attribute__((swift_name("participantReport")));
+@property (class, readonly) BridgeClientResourceType *uploadedFileRecord __attribute__((swift_name("uploadedFileRecord")));
+ (BridgeClientKotlinArray *)values __attribute__((swift_name("values()")));
@end
@@ -3477,6 +3509,18 @@ __attribute__((swift_name("ParticipantScheduleMutator")))
- (BridgeClientParticipantSchedule *)mutateParticipantScheduleParticipantSchedule:(BridgeClientParticipantSchedule *)participantSchedule __attribute__((swift_name("mutateParticipantSchedule(participantSchedule:)")));
@end
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("PendingUploadFile")))
+@interface BridgeClientPendingUploadFile : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("init(filePath:uploadSessionId:)"))) __attribute__((objc_designated_initializer));
+- (BridgeClientPendingUploadFile *)doCopyFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("doCopy(filePath:uploadSessionId:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@end
+
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Report")))
@interface BridgeClientReport : BridgeClientBase
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient
index de910a5f3..953c44435 100644
Binary files a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient and b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient differ
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient
index 44990561b..35891e56c 100755
Binary files a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient and b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient differ
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
index ec804d922..0077b7c94 100644
--- a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
+++ b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
@@ -6,7 +6,7 @@
#import
#import
-@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
+@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUploadedFileRecordCompanion, BridgeClientUploadedFileRecord, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientPendingUploadFile, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
@protocol BridgeClientPlatformConfig, BridgeClientBridgeConfig, BridgeClientIOSPlatformConfig, BridgeClientNativeLogWriter, BridgeClientKotlinComparable, BridgeClientKotlinx_serialization_coreKSerializer, BridgeClientUploadFileIdentifiable, BridgeClientKoin_coreKoinComponent, BridgeClientParticipantScheduleMutator, BridgeClientKotlinx_coroutines_coreJob, BridgeClientEtagStorageCache, BridgeClientKtor_client_coreHttpClientPlugin, BridgeClientHttpUtil, BridgeClientRuntimeColumnAdapter, BridgeClientRuntimeTransactionWithoutReturn, BridgeClientRuntimeTransactionWithReturn, BridgeClientRuntimeTransacterBase, BridgeClientRuntimeTransacter, BridgeClientBridgeResourceDatabase, BridgeClientRuntimeSqlDriver, BridgeClientRuntimeSqlSchema, BridgeClientDbDriverFactory, BridgeClientKotlinx_coroutines_coreFlow, BridgeClientKotlinx_coroutines_coreCoroutineScope, BridgeClientBridgeErrorStatusNotifier, BridgeClientAuthenticationProvider, BridgeClientKotlinx_coroutines_coreStateFlow, BridgeClientKotlinIterator, BridgeClientKotlinx_serialization_coreEncoder, BridgeClientKotlinx_serialization_coreSerialDescriptor, BridgeClientKotlinx_serialization_coreSerializationStrategy, BridgeClientKotlinx_serialization_coreDecoder, BridgeClientKotlinx_serialization_coreDeserializationStrategy, BridgeClientKoin_coreKoinScopeComponent, BridgeClientKoin_coreQualifier, BridgeClientKotlinKClass, BridgeClientKotlinLazy, BridgeClientKotlinx_coroutines_coreChildHandle, BridgeClientKotlinx_coroutines_coreChildJob, BridgeClientKotlinx_coroutines_coreDisposableHandle, BridgeClientKotlinSequence, BridgeClientKotlinx_coroutines_coreSelectClause0, BridgeClientKotlinCoroutineContextKey, BridgeClientKotlinCoroutineContextElement, BridgeClientKotlinCoroutineContext, BridgeClientKtor_ioCloseable, BridgeClientKtor_client_coreHttpClientEngine, BridgeClientKtor_client_coreHttpClientEngineCapability, BridgeClientKtor_utilsAttributes, BridgeClientRuntimeTransactionCallbacks, BridgeClientRuntimeQueryListener, BridgeClientRuntimeQueryResult, BridgeClientRuntimeSqlPreparedStatement, BridgeClientRuntimeSqlCursor, BridgeClientRuntimeCloseable, BridgeClientKotlinx_coroutines_coreFlowCollector, BridgeClientSqliter_driverDatabaseManager, BridgeClientKotlinx_coroutines_coreSharedFlow, BridgeClientKotlinx_serialization_coreCompositeEncoder, BridgeClientKotlinAnnotation, BridgeClientKotlinx_serialization_coreCompositeDecoder, BridgeClientKoin_coreScopeCallback, BridgeClientKotlinKDeclarationContainer, BridgeClientKotlinKAnnotatedElement, BridgeClientKotlinKClassifier, BridgeClientKotlinx_coroutines_coreParentJob, BridgeClientKotlinx_coroutines_coreSelectInstance, BridgeClientKotlinx_coroutines_coreSelectClause, BridgeClientKotlinSuspendFunction2, BridgeClientSqliter_driverDatabaseConnection, BridgeClientKotlinx_serialization_coreSerializersModuleCollector, BridgeClientKtor_httpHeaders, BridgeClientKotlinContinuation, BridgeClientKotlinContinuationInterceptor, BridgeClientKotlinx_coroutines_coreRunnable, BridgeClientKotlinFunction, BridgeClientKtor_httpHttpMessage, BridgeClientKtor_ioByteReadChannel, BridgeClientKtor_httpHttpMessageBuilder, BridgeClientKtor_client_coreHttpRequest, BridgeClientSqliter_driverStatement, BridgeClientSqliter_driverLogger, BridgeClientKtor_httpParameters, BridgeClientKotlinMapEntry, BridgeClientKtor_utilsStringValues, BridgeClientKtor_ioReadSession, BridgeClientKotlinSuspendFunction1, BridgeClientKotlinAppendable, BridgeClientKtor_utilsStringValuesBuilder, BridgeClientKtor_httpParametersBuilder, BridgeClientKotlinKType, BridgeClientSqliter_driverCursor, BridgeClientKtor_ioObjectPool;
@@ -2069,10 +2069,8 @@ __attribute__((swift_name("UploadMetadata")))
@property (class, readonly, getter=companion) BridgeClientUploadMetadataCompanion *companion __attribute__((swift_name("companion")));
- (BridgeClientUploadMetadata *)doCopyInstanceGuid:(NSString * _Nullable)instanceGuid eventTimestamp:(NSString * _Nullable)eventTimestamp startedOn:(NSString * _Nullable)startedOn __attribute__((swift_name("doCopy(instanceGuid:eventTimestamp:startedOn:)")));
- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
-- (NSString * _Nullable)getId __attribute__((swift_name("getId()")));
- (NSUInteger)hash __attribute__((swift_name("hash()")));
- (NSString *)description __attribute__((swift_name("description()")));
-- (NSDictionary *)toStringMap __attribute__((swift_name("toStringMap()")));
@property (readonly) NSString * _Nullable eventTimestamp __attribute__((swift_name("eventTimestamp")));
@property (readonly) NSString * _Nullable instanceGuid __attribute__((swift_name("instanceGuid")));
@property (readonly) NSString * _Nullable startedOn __attribute__((swift_name("startedOn")));
@@ -2119,6 +2117,36 @@ __attribute__((swift_name("UploadSession.Companion")))
@end
+/**
+ * @note annotations
+ * kotlinx.serialization.Serializable
+*/
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord")))
+@interface BridgeClientUploadedFileRecord : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("init(filePath:uploadTimestamp:uploadSessionId:metadata:)"))) __attribute__((objc_designated_initializer));
+@property (class, readonly, getter=companion) BridgeClientUploadedFileRecordCompanion *companion __attribute__((swift_name("companion")));
+- (BridgeClientUploadedFileRecord *)doCopyFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("doCopy(filePath:uploadTimestamp:uploadSessionId:metadata:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) BridgeClientUploadMetadata * _Nullable metadata __attribute__((swift_name("metadata")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@property (readonly) BridgeClientKotlinx_datetimeInstant *uploadTimestamp __attribute__((swift_name("uploadTimestamp")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord.Companion")))
+@interface BridgeClientUploadedFileRecordCompanion : BridgeClientBase
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)companion __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) BridgeClientUploadedFileRecordCompanion *shared __attribute__((swift_name("shared")));
+- (id)serializer __attribute__((swift_name("serializer()")));
+@end
+
+
/**
* @note annotations
* kotlinx.serialization.Serializable
@@ -2498,8 +2526,11 @@ __attribute__((swift_name("NativeUploadManager")))
@interface BridgeClientNativeUploadManager : BridgeClientBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
-- (NSArray *)getUploadFiles __attribute__((swift_name("getUploadFiles()")));
-- (void)markUploadFileFinishedFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:callBack:)")));
+- (void)getPendingUploadFilesCallBack:(void (^)(NSArray *))callBack __attribute__((swift_name("getPendingUploadFiles(callBack:)")));
+- (BOOL)hasMarkedFileAsUploadedFilePath:(NSString *)filePath __attribute__((swift_name("hasMarkedFileAsUploaded(filePath:)")));
+- (BOOL)hasPendingUploads __attribute__((swift_name("hasPendingUploads()")));
+- (void)markUploadFileFinishedFilePath:(NSString *)filePath uploadSessionId:(NSString *)uploadSessionId callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:uploadSessionId:callBack:)")));
+- (void)markUploadUnrecoverableFailureFilePath:(NSString *)filePath __attribute__((swift_name("markUploadUnrecoverableFailure(filePath:)")));
- (void)processFinishedUploadsCallBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("processFinishedUploads(callBack:)")));
- (void)queueAndRequestUploadSessionUploadFile:(BridgeClientUploadFile *)uploadFile callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("queueAndRequestUploadSession(uploadFile:callBack:)")));
- (void)requestUploadSessionFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("requestUploadSession(filePath:callBack:)")));
@@ -3095,6 +3126,7 @@ __attribute__((swift_name("ResourceType")))
@property (class, readonly) BridgeClientResourceType *study __attribute__((swift_name("study")));
@property (class, readonly) BridgeClientResourceType *studyInfo __attribute__((swift_name("studyInfo")));
@property (class, readonly) BridgeClientResourceType *participantReport __attribute__((swift_name("participantReport")));
+@property (class, readonly) BridgeClientResourceType *uploadedFileRecord __attribute__((swift_name("uploadedFileRecord")));
+ (BridgeClientKotlinArray *)values __attribute__((swift_name("values()")));
@end
@@ -3477,6 +3509,18 @@ __attribute__((swift_name("ParticipantScheduleMutator")))
- (BridgeClientParticipantSchedule *)mutateParticipantScheduleParticipantSchedule:(BridgeClientParticipantSchedule *)participantSchedule __attribute__((swift_name("mutateParticipantSchedule(participantSchedule:)")));
@end
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("PendingUploadFile")))
+@interface BridgeClientPendingUploadFile : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("init(filePath:uploadSessionId:)"))) __attribute__((objc_designated_initializer));
+- (BridgeClientPendingUploadFile *)doCopyFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("doCopy(filePath:uploadSessionId:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@end
+
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Report")))
@interface BridgeClientReport : BridgeClientBase
diff --git a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient
index 7abf3f532..3f727d42e 100644
Binary files a/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient and b/SwiftPackage/Binaries/debug/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient differ
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient
index 4b303d4a1..00b4d7b7c 100755
Binary files a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient and b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/BridgeClient differ
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
index ec804d922..0077b7c94 100644
--- a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
+++ b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/BridgeClient.framework/Headers/BridgeClient.h
@@ -6,7 +6,7 @@
#import
#import
-@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
+@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUploadedFileRecordCompanion, BridgeClientUploadedFileRecord, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientPendingUploadFile, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
@protocol BridgeClientPlatformConfig, BridgeClientBridgeConfig, BridgeClientIOSPlatformConfig, BridgeClientNativeLogWriter, BridgeClientKotlinComparable, BridgeClientKotlinx_serialization_coreKSerializer, BridgeClientUploadFileIdentifiable, BridgeClientKoin_coreKoinComponent, BridgeClientParticipantScheduleMutator, BridgeClientKotlinx_coroutines_coreJob, BridgeClientEtagStorageCache, BridgeClientKtor_client_coreHttpClientPlugin, BridgeClientHttpUtil, BridgeClientRuntimeColumnAdapter, BridgeClientRuntimeTransactionWithoutReturn, BridgeClientRuntimeTransactionWithReturn, BridgeClientRuntimeTransacterBase, BridgeClientRuntimeTransacter, BridgeClientBridgeResourceDatabase, BridgeClientRuntimeSqlDriver, BridgeClientRuntimeSqlSchema, BridgeClientDbDriverFactory, BridgeClientKotlinx_coroutines_coreFlow, BridgeClientKotlinx_coroutines_coreCoroutineScope, BridgeClientBridgeErrorStatusNotifier, BridgeClientAuthenticationProvider, BridgeClientKotlinx_coroutines_coreStateFlow, BridgeClientKotlinIterator, BridgeClientKotlinx_serialization_coreEncoder, BridgeClientKotlinx_serialization_coreSerialDescriptor, BridgeClientKotlinx_serialization_coreSerializationStrategy, BridgeClientKotlinx_serialization_coreDecoder, BridgeClientKotlinx_serialization_coreDeserializationStrategy, BridgeClientKoin_coreKoinScopeComponent, BridgeClientKoin_coreQualifier, BridgeClientKotlinKClass, BridgeClientKotlinLazy, BridgeClientKotlinx_coroutines_coreChildHandle, BridgeClientKotlinx_coroutines_coreChildJob, BridgeClientKotlinx_coroutines_coreDisposableHandle, BridgeClientKotlinSequence, BridgeClientKotlinx_coroutines_coreSelectClause0, BridgeClientKotlinCoroutineContextKey, BridgeClientKotlinCoroutineContextElement, BridgeClientKotlinCoroutineContext, BridgeClientKtor_ioCloseable, BridgeClientKtor_client_coreHttpClientEngine, BridgeClientKtor_client_coreHttpClientEngineCapability, BridgeClientKtor_utilsAttributes, BridgeClientRuntimeTransactionCallbacks, BridgeClientRuntimeQueryListener, BridgeClientRuntimeQueryResult, BridgeClientRuntimeSqlPreparedStatement, BridgeClientRuntimeSqlCursor, BridgeClientRuntimeCloseable, BridgeClientKotlinx_coroutines_coreFlowCollector, BridgeClientSqliter_driverDatabaseManager, BridgeClientKotlinx_coroutines_coreSharedFlow, BridgeClientKotlinx_serialization_coreCompositeEncoder, BridgeClientKotlinAnnotation, BridgeClientKotlinx_serialization_coreCompositeDecoder, BridgeClientKoin_coreScopeCallback, BridgeClientKotlinKDeclarationContainer, BridgeClientKotlinKAnnotatedElement, BridgeClientKotlinKClassifier, BridgeClientKotlinx_coroutines_coreParentJob, BridgeClientKotlinx_coroutines_coreSelectInstance, BridgeClientKotlinx_coroutines_coreSelectClause, BridgeClientKotlinSuspendFunction2, BridgeClientSqliter_driverDatabaseConnection, BridgeClientKotlinx_serialization_coreSerializersModuleCollector, BridgeClientKtor_httpHeaders, BridgeClientKotlinContinuation, BridgeClientKotlinContinuationInterceptor, BridgeClientKotlinx_coroutines_coreRunnable, BridgeClientKotlinFunction, BridgeClientKtor_httpHttpMessage, BridgeClientKtor_ioByteReadChannel, BridgeClientKtor_httpHttpMessageBuilder, BridgeClientKtor_client_coreHttpRequest, BridgeClientSqliter_driverStatement, BridgeClientSqliter_driverLogger, BridgeClientKtor_httpParameters, BridgeClientKotlinMapEntry, BridgeClientKtor_utilsStringValues, BridgeClientKtor_ioReadSession, BridgeClientKotlinSuspendFunction1, BridgeClientKotlinAppendable, BridgeClientKtor_utilsStringValuesBuilder, BridgeClientKtor_httpParametersBuilder, BridgeClientKotlinKType, BridgeClientSqliter_driverCursor, BridgeClientKtor_ioObjectPool;
@@ -2069,10 +2069,8 @@ __attribute__((swift_name("UploadMetadata")))
@property (class, readonly, getter=companion) BridgeClientUploadMetadataCompanion *companion __attribute__((swift_name("companion")));
- (BridgeClientUploadMetadata *)doCopyInstanceGuid:(NSString * _Nullable)instanceGuid eventTimestamp:(NSString * _Nullable)eventTimestamp startedOn:(NSString * _Nullable)startedOn __attribute__((swift_name("doCopy(instanceGuid:eventTimestamp:startedOn:)")));
- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
-- (NSString * _Nullable)getId __attribute__((swift_name("getId()")));
- (NSUInteger)hash __attribute__((swift_name("hash()")));
- (NSString *)description __attribute__((swift_name("description()")));
-- (NSDictionary *)toStringMap __attribute__((swift_name("toStringMap()")));
@property (readonly) NSString * _Nullable eventTimestamp __attribute__((swift_name("eventTimestamp")));
@property (readonly) NSString * _Nullable instanceGuid __attribute__((swift_name("instanceGuid")));
@property (readonly) NSString * _Nullable startedOn __attribute__((swift_name("startedOn")));
@@ -2119,6 +2117,36 @@ __attribute__((swift_name("UploadSession.Companion")))
@end
+/**
+ * @note annotations
+ * kotlinx.serialization.Serializable
+*/
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord")))
+@interface BridgeClientUploadedFileRecord : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("init(filePath:uploadTimestamp:uploadSessionId:metadata:)"))) __attribute__((objc_designated_initializer));
+@property (class, readonly, getter=companion) BridgeClientUploadedFileRecordCompanion *companion __attribute__((swift_name("companion")));
+- (BridgeClientUploadedFileRecord *)doCopyFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("doCopy(filePath:uploadTimestamp:uploadSessionId:metadata:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) BridgeClientUploadMetadata * _Nullable metadata __attribute__((swift_name("metadata")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@property (readonly) BridgeClientKotlinx_datetimeInstant *uploadTimestamp __attribute__((swift_name("uploadTimestamp")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord.Companion")))
+@interface BridgeClientUploadedFileRecordCompanion : BridgeClientBase
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)companion __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) BridgeClientUploadedFileRecordCompanion *shared __attribute__((swift_name("shared")));
+- (id)serializer __attribute__((swift_name("serializer()")));
+@end
+
+
/**
* @note annotations
* kotlinx.serialization.Serializable
@@ -2498,8 +2526,11 @@ __attribute__((swift_name("NativeUploadManager")))
@interface BridgeClientNativeUploadManager : BridgeClientBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
-- (NSArray *)getUploadFiles __attribute__((swift_name("getUploadFiles()")));
-- (void)markUploadFileFinishedFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:callBack:)")));
+- (void)getPendingUploadFilesCallBack:(void (^)(NSArray *))callBack __attribute__((swift_name("getPendingUploadFiles(callBack:)")));
+- (BOOL)hasMarkedFileAsUploadedFilePath:(NSString *)filePath __attribute__((swift_name("hasMarkedFileAsUploaded(filePath:)")));
+- (BOOL)hasPendingUploads __attribute__((swift_name("hasPendingUploads()")));
+- (void)markUploadFileFinishedFilePath:(NSString *)filePath uploadSessionId:(NSString *)uploadSessionId callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:uploadSessionId:callBack:)")));
+- (void)markUploadUnrecoverableFailureFilePath:(NSString *)filePath __attribute__((swift_name("markUploadUnrecoverableFailure(filePath:)")));
- (void)processFinishedUploadsCallBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("processFinishedUploads(callBack:)")));
- (void)queueAndRequestUploadSessionUploadFile:(BridgeClientUploadFile *)uploadFile callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("queueAndRequestUploadSession(uploadFile:callBack:)")));
- (void)requestUploadSessionFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("requestUploadSession(filePath:callBack:)")));
@@ -3095,6 +3126,7 @@ __attribute__((swift_name("ResourceType")))
@property (class, readonly) BridgeClientResourceType *study __attribute__((swift_name("study")));
@property (class, readonly) BridgeClientResourceType *studyInfo __attribute__((swift_name("studyInfo")));
@property (class, readonly) BridgeClientResourceType *participantReport __attribute__((swift_name("participantReport")));
+@property (class, readonly) BridgeClientResourceType *uploadedFileRecord __attribute__((swift_name("uploadedFileRecord")));
+ (BridgeClientKotlinArray *)values __attribute__((swift_name("values()")));
@end
@@ -3477,6 +3509,18 @@ __attribute__((swift_name("ParticipantScheduleMutator")))
- (BridgeClientParticipantSchedule *)mutateParticipantScheduleParticipantSchedule:(BridgeClientParticipantSchedule *)participantSchedule __attribute__((swift_name("mutateParticipantSchedule(participantSchedule:)")));
@end
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("PendingUploadFile")))
+@interface BridgeClientPendingUploadFile : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("init(filePath:uploadSessionId:)"))) __attribute__((objc_designated_initializer));
+- (BridgeClientPendingUploadFile *)doCopyFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("doCopy(filePath:uploadSessionId:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@end
+
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Report")))
@interface BridgeClientReport : BridgeClientBase
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient
index 7db6335a4..7177054ef 100644
Binary files a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient and b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient differ
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient
index 18231a321..08d3f7ac1 100755
Binary files a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient and b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/BridgeClient differ
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
index ec804d922..0077b7c94 100644
--- a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
+++ b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/BridgeClient.framework/Headers/BridgeClient.h
@@ -6,7 +6,7 @@
#import
#import
-@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
+@class BridgeClientPlatformConfigBridgeEnvironment, BridgeClientIOSBridgeConfig, BridgeClientIOSLogger, NSData, BridgeClientAdherenceRecord, BridgeClientAppConfig, BridgeClientKotlinx_serialization_jsonJsonElement, BridgeClientStudy, BridgeClientUserSessionInfo, BridgeClientKotlinEnumCompanion, BridgeClientKotlinEnum, BridgeClientLogSeverity, BridgeClientKotlinArray, NSError, BridgeClientAccountStatusCompanion, BridgeClientAccountStatus, BridgeClientAddressCompanion, BridgeClientAddress, BridgeClientKotlinx_datetimeInstant, BridgeClientAdherenceRecordCompanion, BridgeClientAdherenceRecordTypeCompanion, BridgeClientAdherenceRecordType, BridgeClientAdherenceRecordUpdatesCompanion, BridgeClientAdherenceRecordUpdates, BridgeClientSortOrder, BridgeClientAdherenceRecordsSearchCompanion, BridgeClientAdherenceRecordsSearch, BridgeClientCriteria, BridgeClientSurveyReference, BridgeClientSchemaReference, BridgeClientConfigReference, BridgeClientFileReference, BridgeClientAppConfigCompanion, BridgeClientAssessmentConfigCompanion, BridgeClientAssessmentConfig, BridgeClientColorScheme, BridgeClientImageResource, BridgeClientAssessmentInfoCompanion, BridgeClientAssessmentInfo, BridgeClientClientInfoCompanion, BridgeClientClientInfo, BridgeClientColorSchemeCompanion, BridgeClientConfigReferenceCompanion, BridgeClientConsentStatusCompanion, BridgeClientConsentStatus, BridgeClientContactRole, BridgeClientPhone, BridgeClientContactCompanion, BridgeClientContact, BridgeClientContactRoleCompanion, BridgeClientCriteriaCompanion, BridgeClientDateRangeCompanion, BridgeClientDateRange, BridgeClientEnrollmentInfoCompanion, BridgeClientEnrollmentInfo, BridgeClientEnvironmentCompanion, BridgeClientEnvironment, BridgeClientFileReferenceCompanion, BridgeClientIdentifierCompanion, BridgeClientIdentifier, BridgeClientLabel, BridgeClientImageResourceCompanion, BridgeClientIrbDecisionTypeCompanion, BridgeClientIrbDecisionType, BridgeClientLabelCompanion, BridgeClientNotificationType, BridgeClientNotificationMessage, BridgeClientNotificationInfoCompanion, BridgeClientNotificationInfo, BridgeClientNotificationMessageCompanion, BridgeClientNotificationTypeCompanion, BridgeClientParticipantDataCompanion, BridgeClientParticipantData, BridgeClientScheduledSession, BridgeClientSessionInfo, BridgeClientStudyBurstInfo, BridgeClientParticipantScheduleCompanion, BridgeClientParticipantSchedule, BridgeClientPerformanceOrderCompanion, BridgeClientPerformanceOrder, BridgeClientPhoneCompanion, BridgeClientPhoneSignInRequestCompanion, BridgeClientPhoneSignInRequest, BridgeClientPhoneSigninCompanion, BridgeClientPhoneSignin, BridgeClientReminderTypeCompanion, BridgeClientReminderType, BridgeClientRoleCompanion, BridgeClientRole, BridgeClientS3UploadSession, BridgeClientS3UploadType, BridgeClientScheduledAssessmentCompanion, BridgeClientScheduledAssessment, BridgeClientKotlinx_datetimeLocalTime, BridgeClientKotlinx_datetimeDateTimePeriod, BridgeClientKotlinx_datetimeLocalDate, BridgeClientScheduledSessionCompanion, BridgeClientKotlinx_datetimeLocalDateTime, BridgeClientSchemaReferenceCompanion, BridgeClientSessionInfoCompanion, BridgeClientSharingScopeCompanion, BridgeClientSharingScope, BridgeClientSignInTypeCompanion, BridgeClientSignInType, BridgeClientSortOrderCompanion, BridgeClientStudyPhase, BridgeClientStudyCompanion, BridgeClientStudyActivityEventCompanion, BridgeClientStudyActivityEvent, BridgeClientStudyActivityEventListCompanion, BridgeClientStudyActivityEventList, BridgeClientStudyActivityEventRequestCompanion, BridgeClientStudyBurstInfoCompanion, BridgeClientStudyInfoCompanion, BridgeClientStudyInfo, BridgeClientUserConsentHistory, BridgeClientStudyParticipantCompanion, BridgeClientStudyParticipant, BridgeClientStudyPhaseCompanion, BridgeClientSurveyReferenceCompanion, BridgeClientTimelineCompanion, BridgeClientTimeline, BridgeClientUploadMetadata, BridgeClientUploadFileCompanion, BridgeClientUploadFile, BridgeClientUploadMetadataCompanion, BridgeClientUploadSessionCompanion, BridgeClientUploadSession, BridgeClientUploadedFileRecordCompanion, BridgeClientUploadedFileRecord, BridgeClientUserConsentHistoryCompanion, BridgeClientUserSessionInfoCompanion, BridgeClientKoin_coreKoin, BridgeClientNativeAssessmentConfig, NSDate, BridgeClientNativeAdherenceRecord, BridgeClientResourceStatus, BridgeClientAppStatus, BridgeClientParticipantRepoUpdateParticipantRecord, BridgeClientKtor_httpHttpStatusCode, BridgeClientKotlinError, BridgeClientUserSessionState, BridgeClientAssessmentHistoryRecord, BridgeClientNativeParticipantDataRecord, BridgeClientNativeScheduledAssessment, NSDateComponents, BridgeClientNativeScheduledNotification, NSTimeZone, BridgeClientNativeScheduledSessionWindow, BridgeClientNativeScheduledSessionTimelineSlice, BridgeClientNativeStudyBurst, BridgeClientNativeStudyBurstSchedule, BridgeClientAbstractNativeTimelineManager, BridgeClientPendingUploadFile, BridgeClientEtagFeatureFeature, BridgeClientEtagFeature, BridgeClientKtor_client_coreHttpClient, BridgeClientKtor_utilsAttributeKey, BridgeClientEtagFeatureConfig, BridgeClientAdherenceRecords, BridgeClientAllAssessmentAdherence, BridgeClientBridgeResourceDatabaseQueries, BridgeClientLocalDataCacheQueries, BridgeClientParticipantScheduleQueries, BridgeClientBridgeResourceDatabaseCompanion, BridgeClientAdherenceRecordsAdapter, BridgeClientJsonDataAdapter, BridgeClientResourceAdapter, BridgeClientKotlinUnit, BridgeClientRuntimeTransacterTransaction, BridgeClientKotlinThrowable, BridgeClientRuntimeBaseTransacterImpl, BridgeClientRuntimeTransacterImpl, BridgeClientRuntimeQuery<__covariant RowType>, BridgeClientResourceType, BridgeClientResource, BridgeClientEtagCache, BridgeClientCompletedAssessmentAdherence, BridgeClientFullScheduledSessions, BridgeClientGroupedFuturePendingNotifications, BridgeClientKotlinx_datetimeTimeZone, BridgeClientJsonData, BridgeClientResourceDatabaseHelper, BridgeClientScheduledNotification, BridgeClientParticipantScheduleDatabaseExpandedScheduledSession, BridgeClientParticipantScheduleDatabaseScheduledAssessment, BridgeClientParticipantScheduleDatabaseScheduledSessionHolder, BridgeClientScheduledNotifications, BridgeClientScheduledAssessmentAdherence, BridgeClientScheduleMetadata, BridgeClientStudyBurstSessions, BridgeClientResourceDatabaseHelperCompanion, BridgeClientResourceResult<__covariant T>, BridgeClientResourceResultFailed, BridgeClientKotlinNothing, BridgeClientResourceResultInProgress, BridgeClientResourceResultSuccess<__covariant T>, BridgeClientScheduledAssessments, BridgeClientScheduledSessions, BridgeClientSqliteDriverFactory, BridgeClientNative_driverNativeSqliteDriver, BridgeClientAbstractResourceRepo, BridgeClientScheduleTimelineRepo, BridgeClientAuthenticationRepository, BridgeClientParticipantRepoUpdateParticipantRecordCompanion, BridgeClientReport, BridgeClientAdherenceRecordRepo, BridgeClientAssessmentConfigRepo, BridgeClientScheduledAssessmentReference, BridgeClientScheduledNotificationCompanion, BridgeClientScheduledSessionWindow, BridgeClientScheduledSessionTimelineSlice, BridgeClientStudyBurst, BridgeClientStudyBurstSchedule, BridgeClientKoin_coreModule, BridgeClientKoin_coreKoinApplication, BridgeClientKotlinx_serialization_jsonJsonElementCompanion, BridgeClientKotlinx_datetimeInstantCompanion, BridgeClientKotlinx_datetimeLocalTimeCompanion, BridgeClientKotlinx_datetimeDateTimePeriodCompanion, BridgeClientKotlinx_datetimeMonth, BridgeClientKotlinx_datetimeLocalDateCompanion, BridgeClientKotlinx_datetimeDayOfWeek, BridgeClientKotlinx_datetimeLocalDateTimeCompanion, BridgeClientKoin_coreScope, BridgeClientKoin_coreParametersHolder, BridgeClientKotlinLazyThreadSafetyMode, BridgeClientKoin_coreLogger, BridgeClientKoin_coreInstanceRegistry, BridgeClientKoin_corePropertyRegistry, BridgeClientKoin_coreScopeRegistry, BridgeClientKtor_httpHttpStatusCodeCompanion, BridgeClientKotlinCancellationException, BridgeClientKtor_client_coreHttpClientEngineConfig, BridgeClientKtor_client_coreHttpClientConfig, BridgeClientKtor_eventsEvents, BridgeClientKtor_client_coreHttpReceivePipeline, BridgeClientKtor_client_coreHttpRequestPipeline, BridgeClientKtor_client_coreHttpResponsePipeline, BridgeClientKtor_client_coreHttpSendPipeline, BridgeClientRuntimeExecutableQuery<__covariant RowType>, BridgeClientKotlinx_datetimeTimeZoneCompanion, BridgeClientNative_driverConnectionWrapper, BridgeClientSqliter_driverDatabaseConfiguration, BridgeClientKotlinException, BridgeClientKotlinRuntimeException, BridgeClientKotlinIllegalStateException, BridgeClientKoin_coreInstanceFactory, BridgeClientKotlinPair<__covariant A, __covariant B>, BridgeClientKoin_coreScopeDSL, BridgeClientKoin_coreSingleInstanceFactory, BridgeClientKoin_coreKoinApplicationCompanion, BridgeClientKoin_coreLevel, BridgeClientKotlinx_serialization_coreSerializersModule, BridgeClientKotlinx_serialization_coreSerialKind, BridgeClientKoin_coreParametersHolderCompanion, BridgeClientKoin_coreScopeRegistryCompanion, BridgeClientKtor_client_coreHttpRequestData, BridgeClientKtor_client_coreHttpResponseData, BridgeClientKotlinx_coroutines_coreCoroutineDispatcher, BridgeClientKtor_client_coreProxyConfig, BridgeClientKtor_eventsEventDefinition, BridgeClientKtor_utilsPipelinePhase, BridgeClientKtor_utilsPipeline, BridgeClientKtor_client_coreHttpReceivePipelinePhases, BridgeClientKtor_client_coreHttpResponse, BridgeClientKtor_client_coreHttpRequestPipelinePhases, BridgeClientKtor_client_coreHttpRequestBuilder, BridgeClientKtor_client_coreHttpResponsePipelinePhases, BridgeClientKtor_client_coreHttpResponseContainer, BridgeClientKtor_client_coreHttpClientCall, BridgeClientKtor_client_coreHttpSendPipelinePhases, BridgeClientKotlinByteArray, BridgeClientKotlinx_datetimeFixedOffsetTimeZone, BridgeClientSqliter_driverJournalMode, BridgeClientSqliter_driverDatabaseConfigurationExtended, BridgeClientSqliter_driverDatabaseConfigurationLogging, BridgeClientSqliter_driverDatabaseConfigurationLifecycle, BridgeClientSqliter_driverDatabaseConfigurationEncryption, BridgeClientKoin_coreBeanDefinition, BridgeClientKoin_coreInstanceFactoryCompanion, BridgeClientKoin_coreInstanceContext, BridgeClientKtor_httpUrl, BridgeClientKtor_httpHttpMethod, BridgeClientKtor_httpOutgoingContent, BridgeClientKtor_utilsGMTDate, BridgeClientKtor_httpHttpProtocolVersion, BridgeClientKotlinAbstractCoroutineContextElement, BridgeClientKotlinx_coroutines_coreCoroutineDispatcherKey, BridgeClientKtor_httpHeadersBuilder, BridgeClientKtor_client_coreHttpRequestBuilderCompanion, BridgeClientKtor_httpURLBuilder, BridgeClientKtor_utilsTypeInfo, BridgeClientKtor_client_coreHttpClientCallCompanion, BridgeClientKotlinByteIterator, BridgeClientKotlinx_datetimeUtcOffset, BridgeClientKotlinx_datetimeFixedOffsetTimeZoneCompanion, BridgeClientSqliter_driverJournalModeCompanion, BridgeClientSqliter_driverSynchronousFlag, BridgeClientKoin_coreKind, BridgeClientKoin_coreCallbacks, BridgeClientKtor_httpUrlCompanion, BridgeClientKtor_httpURLProtocol, BridgeClientKtor_httpHttpMethodCompanion, BridgeClientKtor_httpContentType, BridgeClientKtor_utilsGMTDateCompanion, BridgeClientKtor_utilsWeekDay, BridgeClientKtor_utilsMonth, BridgeClientKtor_httpHttpProtocolVersionCompanion, BridgeClientKotlinAbstractCoroutineContextKey, BridgeClientKtor_ioMemory, BridgeClientKtor_ioChunkBuffer, BridgeClientKtor_ioBuffer, BridgeClientKtor_ioByteReadPacket, BridgeClientKtor_utilsStringValuesBuilderImpl, BridgeClientKtor_httpURLBuilderCompanion, BridgeClientKotlinx_datetimeUtcOffsetCompanion, BridgeClientKtor_httpURLProtocolCompanion, BridgeClientKtor_httpHeaderValueParam, BridgeClientKtor_httpHeaderValueWithParametersCompanion, BridgeClientKtor_httpHeaderValueWithParameters, BridgeClientKtor_httpContentTypeCompanion, BridgeClientKtor_utilsWeekDayCompanion, BridgeClientKtor_utilsMonthCompanion, BridgeClientKtor_ioMemoryCompanion, BridgeClientKtor_ioBufferCompanion, BridgeClientKtor_ioChunkBufferCompanion, BridgeClientKtor_ioInputCompanion, BridgeClientKtor_ioInput, BridgeClientKtor_ioByteReadPacketCompanion, BridgeClientKotlinKTypeProjection, BridgeClientSqliter_driverFieldType, BridgeClientKotlinKVariance, BridgeClientKotlinKTypeProjectionCompanion, BridgeClientSqliter_driverFieldTypeCompanion;
@protocol BridgeClientPlatformConfig, BridgeClientBridgeConfig, BridgeClientIOSPlatformConfig, BridgeClientNativeLogWriter, BridgeClientKotlinComparable, BridgeClientKotlinx_serialization_coreKSerializer, BridgeClientUploadFileIdentifiable, BridgeClientKoin_coreKoinComponent, BridgeClientParticipantScheduleMutator, BridgeClientKotlinx_coroutines_coreJob, BridgeClientEtagStorageCache, BridgeClientKtor_client_coreHttpClientPlugin, BridgeClientHttpUtil, BridgeClientRuntimeColumnAdapter, BridgeClientRuntimeTransactionWithoutReturn, BridgeClientRuntimeTransactionWithReturn, BridgeClientRuntimeTransacterBase, BridgeClientRuntimeTransacter, BridgeClientBridgeResourceDatabase, BridgeClientRuntimeSqlDriver, BridgeClientRuntimeSqlSchema, BridgeClientDbDriverFactory, BridgeClientKotlinx_coroutines_coreFlow, BridgeClientKotlinx_coroutines_coreCoroutineScope, BridgeClientBridgeErrorStatusNotifier, BridgeClientAuthenticationProvider, BridgeClientKotlinx_coroutines_coreStateFlow, BridgeClientKotlinIterator, BridgeClientKotlinx_serialization_coreEncoder, BridgeClientKotlinx_serialization_coreSerialDescriptor, BridgeClientKotlinx_serialization_coreSerializationStrategy, BridgeClientKotlinx_serialization_coreDecoder, BridgeClientKotlinx_serialization_coreDeserializationStrategy, BridgeClientKoin_coreKoinScopeComponent, BridgeClientKoin_coreQualifier, BridgeClientKotlinKClass, BridgeClientKotlinLazy, BridgeClientKotlinx_coroutines_coreChildHandle, BridgeClientKotlinx_coroutines_coreChildJob, BridgeClientKotlinx_coroutines_coreDisposableHandle, BridgeClientKotlinSequence, BridgeClientKotlinx_coroutines_coreSelectClause0, BridgeClientKotlinCoroutineContextKey, BridgeClientKotlinCoroutineContextElement, BridgeClientKotlinCoroutineContext, BridgeClientKtor_ioCloseable, BridgeClientKtor_client_coreHttpClientEngine, BridgeClientKtor_client_coreHttpClientEngineCapability, BridgeClientKtor_utilsAttributes, BridgeClientRuntimeTransactionCallbacks, BridgeClientRuntimeQueryListener, BridgeClientRuntimeQueryResult, BridgeClientRuntimeSqlPreparedStatement, BridgeClientRuntimeSqlCursor, BridgeClientRuntimeCloseable, BridgeClientKotlinx_coroutines_coreFlowCollector, BridgeClientSqliter_driverDatabaseManager, BridgeClientKotlinx_coroutines_coreSharedFlow, BridgeClientKotlinx_serialization_coreCompositeEncoder, BridgeClientKotlinAnnotation, BridgeClientKotlinx_serialization_coreCompositeDecoder, BridgeClientKoin_coreScopeCallback, BridgeClientKotlinKDeclarationContainer, BridgeClientKotlinKAnnotatedElement, BridgeClientKotlinKClassifier, BridgeClientKotlinx_coroutines_coreParentJob, BridgeClientKotlinx_coroutines_coreSelectInstance, BridgeClientKotlinx_coroutines_coreSelectClause, BridgeClientKotlinSuspendFunction2, BridgeClientSqliter_driverDatabaseConnection, BridgeClientKotlinx_serialization_coreSerializersModuleCollector, BridgeClientKtor_httpHeaders, BridgeClientKotlinContinuation, BridgeClientKotlinContinuationInterceptor, BridgeClientKotlinx_coroutines_coreRunnable, BridgeClientKotlinFunction, BridgeClientKtor_httpHttpMessage, BridgeClientKtor_ioByteReadChannel, BridgeClientKtor_httpHttpMessageBuilder, BridgeClientKtor_client_coreHttpRequest, BridgeClientSqliter_driverStatement, BridgeClientSqliter_driverLogger, BridgeClientKtor_httpParameters, BridgeClientKotlinMapEntry, BridgeClientKtor_utilsStringValues, BridgeClientKtor_ioReadSession, BridgeClientKotlinSuspendFunction1, BridgeClientKotlinAppendable, BridgeClientKtor_utilsStringValuesBuilder, BridgeClientKtor_httpParametersBuilder, BridgeClientKotlinKType, BridgeClientSqliter_driverCursor, BridgeClientKtor_ioObjectPool;
@@ -2069,10 +2069,8 @@ __attribute__((swift_name("UploadMetadata")))
@property (class, readonly, getter=companion) BridgeClientUploadMetadataCompanion *companion __attribute__((swift_name("companion")));
- (BridgeClientUploadMetadata *)doCopyInstanceGuid:(NSString * _Nullable)instanceGuid eventTimestamp:(NSString * _Nullable)eventTimestamp startedOn:(NSString * _Nullable)startedOn __attribute__((swift_name("doCopy(instanceGuid:eventTimestamp:startedOn:)")));
- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
-- (NSString * _Nullable)getId __attribute__((swift_name("getId()")));
- (NSUInteger)hash __attribute__((swift_name("hash()")));
- (NSString *)description __attribute__((swift_name("description()")));
-- (NSDictionary *)toStringMap __attribute__((swift_name("toStringMap()")));
@property (readonly) NSString * _Nullable eventTimestamp __attribute__((swift_name("eventTimestamp")));
@property (readonly) NSString * _Nullable instanceGuid __attribute__((swift_name("instanceGuid")));
@property (readonly) NSString * _Nullable startedOn __attribute__((swift_name("startedOn")));
@@ -2119,6 +2117,36 @@ __attribute__((swift_name("UploadSession.Companion")))
@end
+/**
+ * @note annotations
+ * kotlinx.serialization.Serializable
+*/
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord")))
+@interface BridgeClientUploadedFileRecord : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("init(filePath:uploadTimestamp:uploadSessionId:metadata:)"))) __attribute__((objc_designated_initializer));
+@property (class, readonly, getter=companion) BridgeClientUploadedFileRecordCompanion *companion __attribute__((swift_name("companion")));
+- (BridgeClientUploadedFileRecord *)doCopyFilePath:(NSString *)filePath uploadTimestamp:(BridgeClientKotlinx_datetimeInstant *)uploadTimestamp uploadSessionId:(NSString * _Nullable)uploadSessionId metadata:(BridgeClientUploadMetadata * _Nullable)metadata __attribute__((swift_name("doCopy(filePath:uploadTimestamp:uploadSessionId:metadata:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) BridgeClientUploadMetadata * _Nullable metadata __attribute__((swift_name("metadata")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@property (readonly) BridgeClientKotlinx_datetimeInstant *uploadTimestamp __attribute__((swift_name("uploadTimestamp")));
+@end
+
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("UploadedFileRecord.Companion")))
+@interface BridgeClientUploadedFileRecordCompanion : BridgeClientBase
++ (instancetype)alloc __attribute__((unavailable));
++ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
++ (instancetype)companion __attribute__((swift_name("init()")));
+@property (class, readonly, getter=shared) BridgeClientUploadedFileRecordCompanion *shared __attribute__((swift_name("shared")));
+- (id)serializer __attribute__((swift_name("serializer()")));
+@end
+
+
/**
* @note annotations
* kotlinx.serialization.Serializable
@@ -2498,8 +2526,11 @@ __attribute__((swift_name("NativeUploadManager")))
@interface BridgeClientNativeUploadManager : BridgeClientBase
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
-- (NSArray *)getUploadFiles __attribute__((swift_name("getUploadFiles()")));
-- (void)markUploadFileFinishedFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:callBack:)")));
+- (void)getPendingUploadFilesCallBack:(void (^)(NSArray *))callBack __attribute__((swift_name("getPendingUploadFiles(callBack:)")));
+- (BOOL)hasMarkedFileAsUploadedFilePath:(NSString *)filePath __attribute__((swift_name("hasMarkedFileAsUploaded(filePath:)")));
+- (BOOL)hasPendingUploads __attribute__((swift_name("hasPendingUploads()")));
+- (void)markUploadFileFinishedFilePath:(NSString *)filePath uploadSessionId:(NSString *)uploadSessionId callBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("markUploadFileFinished(filePath:uploadSessionId:callBack:)")));
+- (void)markUploadUnrecoverableFailureFilePath:(NSString *)filePath __attribute__((swift_name("markUploadUnrecoverableFailure(filePath:)")));
- (void)processFinishedUploadsCallBack:(void (^)(BridgeClientBoolean *))callBack __attribute__((swift_name("processFinishedUploads(callBack:)")));
- (void)queueAndRequestUploadSessionUploadFile:(BridgeClientUploadFile *)uploadFile callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("queueAndRequestUploadSession(uploadFile:callBack:)")));
- (void)requestUploadSessionFilePath:(NSString *)filePath callBack:(void (^)(BridgeClientS3UploadSession * _Nullable))callBack __attribute__((swift_name("requestUploadSession(filePath:callBack:)")));
@@ -3095,6 +3126,7 @@ __attribute__((swift_name("ResourceType")))
@property (class, readonly) BridgeClientResourceType *study __attribute__((swift_name("study")));
@property (class, readonly) BridgeClientResourceType *studyInfo __attribute__((swift_name("studyInfo")));
@property (class, readonly) BridgeClientResourceType *participantReport __attribute__((swift_name("participantReport")));
+@property (class, readonly) BridgeClientResourceType *uploadedFileRecord __attribute__((swift_name("uploadedFileRecord")));
+ (BridgeClientKotlinArray *)values __attribute__((swift_name("values()")));
@end
@@ -3477,6 +3509,18 @@ __attribute__((swift_name("ParticipantScheduleMutator")))
- (BridgeClientParticipantSchedule *)mutateParticipantScheduleParticipantSchedule:(BridgeClientParticipantSchedule *)participantSchedule __attribute__((swift_name("mutateParticipantSchedule(participantSchedule:)")));
@end
+__attribute__((objc_subclassing_restricted))
+__attribute__((swift_name("PendingUploadFile")))
+@interface BridgeClientPendingUploadFile : BridgeClientBase
+- (instancetype)initWithFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("init(filePath:uploadSessionId:)"))) __attribute__((objc_designated_initializer));
+- (BridgeClientPendingUploadFile *)doCopyFilePath:(NSString *)filePath uploadSessionId:(NSString * _Nullable)uploadSessionId __attribute__((swift_name("doCopy(filePath:uploadSessionId:)")));
+- (BOOL)isEqual:(id _Nullable)other __attribute__((swift_name("isEqual(_:)")));
+- (NSUInteger)hash __attribute__((swift_name("hash()")));
+- (NSString *)description __attribute__((swift_name("description()")));
+@property (readonly) NSString *filePath __attribute__((swift_name("filePath")));
+@property (readonly) NSString * _Nullable uploadSessionId __attribute__((swift_name("uploadSessionId")));
+@end
+
__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Report")))
@interface BridgeClientReport : BridgeClientBase
diff --git a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient
index fb4f5432d..b4692746c 100644
Binary files a/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient and b/SwiftPackage/Binaries/release/BridgeClient.xcframework/ios-arm64_x86_64-simulator/dSYMs/BridgeClient.framework.dSYM/Contents/Resources/DWARF/BridgeClient differ
diff --git a/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/DataArchive.swift b/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/DataArchive.swift
index ee4a26088..733324ae1 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/DataArchive.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/DataArchive.swift
@@ -216,8 +216,11 @@ open class DataArchive : NSObject, Identifiable {
fileprivate let kBridgeV2InfoFilename = "info.json"
+/// The info.json file is required for Exporter V1/V2 and by the Bridge Study
+/// Manager in order to see an archive marked as "complete".
public struct BridgeUploaderInfoV2 : Encodable {
+ let createdOn: String = ISO8601TimestampFormatter.string(from: Date())
let files: [FileEntry]
let dataFilename: String
let format: FormatVersion
diff --git a/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/ScheduledAssessmentHandler.swift b/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/ScheduledAssessmentHandler.swift
index 813497d2d..a8ddc2d24 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/ScheduledAssessmentHandler.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/Data Archiving/ScheduledAssessmentHandler.swift
@@ -83,6 +83,13 @@ public struct AssessmentScheduleInfo : Identifiable, Hashable, Codable {
label: assessment.assessmentInfo.label)
}
+ /// Internal init that can be used for testing.
+ init(instanceGuid: String, session: Session, assessmentInfo: Info) {
+ self.instanceGuid = instanceGuid
+ self.session = session
+ self.assessmentInfo = assessmentInfo
+ }
+
/// A thread-safe struct with information required to load and save the results from an assessment.
public struct Info : Hashable, Codable {
/// The ``BridgeClient.AssessmentInfo/identifier``.
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/ArchiveUploadProcessor.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/ArchiveUploadProcessor.swift
index 3dd47d4c7..91de60603 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/ArchiveUploadProcessor.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/ArchiveUploadProcessor.swift
@@ -5,15 +5,20 @@ import Foundation
import BridgeClient
import JsonModel
+#if canImport(UIKit)
+import UIKit
+#endif
+
fileprivate let UnprocessedUploadsKey = "unprocessedUploads"
+/// This is a processor to use to handle archiving data and uploading in the background.
final class ArchiveUploadProcessor {
let pemPath: String?
let sharedUserDefaults: UserDefaults
var isTestUser: Bool = false
- weak var uploadManager: BridgeFileUploadManager!
+ weak var uploadManager: BridgeUploader!
init(pemPath: String?, sharedUserDefaults: UserDefaults) {
self.pemPath = pemPath
@@ -21,25 +26,48 @@ final class ArchiveUploadProcessor {
}
func encryptAndUpload(using builder: ArchiveBuilder) {
- Task.detached(priority: .medium) {
- await self._encryptAndUpload(using: builder)
+ Task.detached(priority: .userInitiated) {
+ await self.asyncEncryptAndUpload(using: builder)
}
}
@MainActor
- private func _encryptAndUpload(using builder: ArchiveBuilder) async {
+ func asyncEncryptAndUpload(using builder: ArchiveBuilder) async {
+ uploadManager.isArchiving = true
+
+ #if canImport(UIKit)
+ // This allows the app to request about 30 seconds after the app has been
+ // backgrounded to prepare data for upload.
+ let taskId = UIApplication.shared.beginBackgroundTask {
+ Logger.log(tag: .upload,
+ error: BridgeUploadFailedError(category: .backgroundTaskTimeout, message: "Timed out when archiving and starting background upload.")
+ )
+ self.uploadManager.isArchiving = false
+ }
+ #endif
+
do {
let archive = try await builder.buildArchive()
// Copy the startedOn date (if available) from the builder to the archive.
+ // This is a work-around for older code so that the adherence record can be
+ // marked for a requested archive.
if let resultBuilder = builder as? ResultArchiveBuilder, archive.adherenceStartedOn == nil {
archive.adherenceStartedOn = resultBuilder.startedOn
}
await _copyTest(archive: archive)
- let encrypted = await _encrypt(archive: archive)
- await _upload(archive: archive, encrypted: encrypted)
+ try await _encrypt(archive: archive)
+ try await _upload(archive: archive)
} catch {
Logger.log(error: error, message: "Failed to archive and upload \(builder.identifier)")
}
+
+ #if canImport(UIKit)
+ // Let the OS know that the app is done with the processing that needs to happen
+ // before ending.
+ UIApplication.shared.endBackgroundTask(taskId)
+ #endif
+
+ uploadManager.isArchiving = false
}
private func _copyTest(archive: DataArchive) async {
@@ -50,71 +78,55 @@ final class ArchiveUploadProcessor {
#endif
}
- private func _encrypt(archive: DataArchive) async -> Bool {
- guard let path = self.pemPath else { return false }
- do {
- try archive.encryptArchive(using: path)
- return true
- } catch {
- Logger.log(error: error, message: "Failed to encrypt \(archive.identifier)")
- return false
+ private func _encrypt(archive: DataArchive) async throws {
+ guard let path = self.pemPath else {
+ // Throw an assert to alert the developer that the pem file is missing.
+ let message = "Cannot upload archive. Missing pemPath."
+ assertionFailure(message)
+ throw BridgeUnexpectedNullError(category: .missingFile, message: message)
}
+ try archive.encryptArchive(using: path)
}
@MainActor
- private func _upload(archive: DataArchive, encrypted: Bool) async {
+ private func _upload(archive: DataArchive) async throws {
guard let url = archive.encryptedURL else {
let message = "Cannot upload archive. Missing encryption."
assertionFailure(message) // Throw an assert to alert the developer that the pem file is missing.
- Logger.log(error: BridgeUnexpectedNullError(category: .missingFile, message: message))
- return
+ throw BridgeUnexpectedNullError(category: .missingFile, message: message)
}
- _uploadEncrypted(id: archive.identifier, url: url, schedule: archive.schedule, startedOn: archive.adherenceStartedOn)
+ await _uploadEncrypted(id: archive.identifier, url: url, schedule: archive.schedule, startedOn: archive.adherenceStartedOn)
}
@MainActor
- private func _uploadEncrypted(id: String, url: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) {
- let dictionary = schedule.map {
- var ret = [
- "instanceGuid": $0.instanceGuid,
- "eventTimestamp": $0.session.eventTimestamp,
- ]
- if let startedOn = startedOn?.jsonObject() as? String {
- ret["startedOn"] = startedOn
+ private func _uploadEncrypted(id: String, url: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) async {
+ let success = await uploadManager.uploadEncryptedArchive(fileUrl: url, schedule: schedule, startedOn: startedOn)
+ if (success) {
+ // Only if the file was successfully queued for upload should the original file be deleted.
+ do {
+ try FileManager.default.removeItem(at: url)
+ } catch let err {
+ Logger.log(error: err, message: "Failed to delete encrypted archive \(id) at \(url)")
}
- return ret
}
- let exporterV3Metadata: JsonElement? = dictionary.map { .object($0) }
- let extras = StudyDataUploadExtras(encrypted: true, metadata: exporterV3Metadata, zipped: true)
- Logger.log(severity: .info, message: "Uploading file: \(id)", metadata: dictionary)
- uploadManager.studyDataUploadAPI.upload(fileId: id, fileUrl: url, contentType: "application/zip", extras: extras)
- do {
- try FileManager.default.removeItem(at: url)
- } catch let err {
- Logger.log(error: err, message: "Failed to delete encrypted archive \(id) at \(url)")
+ else {
+ // Store the path to the failed upload to allow potentially recovering the file and log error.
+ var failedUploads = (sharedUserDefaults.array(forKey: UnprocessedUploadsKey) as? [String]) ?? []
+ failedUploads.append(url.path)
+ sharedUserDefaults.set(failedUploads, forKey: UnprocessedUploadsKey)
+ let err = BridgeUploadFailedError(category: .fileFailure, message: "Failed to queue encrypted file for upload. \(url)")
+ Logger.log(tag: .upload, error: err)
}
}
- // TODO: syoung 07/27/2023 Replace upload with V2 uploader
-// @MainActor
-// private func _uploadEncrypted(id: String, url: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) {
-// let success = uploader.uploadEncryptedArchive(fileUrl: url, schedule: schedule, startedOn: startedOn)
-// if (success) {
-// // Only if the file was successfully queued for upload should the original file be deleted.
-// do {
-// try FileManager.default.removeItem(at: url)
-// } catch let err {
-// Logger.log(error: err, message: "Failed to delete encrypted archive \(id) at \(url)")
-// }
-// }
-// else {
-// // Store the path to the failed upload to allow potentially recovering the file and log error.
-// var failedUploads = (sharedUserDefaults.array(forKey: UnprocessedUploadsKey) as? [String]) ?? []
-// failedUploads.append(url.path)
-// sharedUserDefaults.set(failedUploads, forKey: UnprocessedUploadsKey)
-// let err = BridgeUploadFailedError(errorCode: -99, message: "Failed to queue encrypted file for upload. \(url)")
-// Logger.log(tag: .upload, error: err)
-// }
-// }
-
+}
+
+/// Use a protocol to allow for unit tests to mock the Upload Manager.
+protocol BridgeUploader : NSObjectProtocol {
+ var isArchiving: Bool { get set }
+ @MainActor func uploadEncryptedArchive(fileUrl: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) async -> Bool
+ @MainActor func upload(fileUrl: URL, contentType: String, encrypted: Bool, metadata: UploadMetadata?, s3UploadType: S3UploadType) async -> Bool
+}
+
+extension UploadManager : BridgeUploader {
}
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundNetworkManager.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundNetworkManager.swift
index bef986ab3..9c14ed97e 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundNetworkManager.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundNetworkManager.swift
@@ -165,7 +165,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}.joined(separator: "&")
}
- func request(method: HTTPMethod, URLString: String, headers: [String : String]?) -> URLRequest {
+ func downloadRequest(method: HTTPMethod, URLString: String, headers: [String : String]?) -> URLRequest {
var request = URLRequest(url: bridgeURL(for: URLString))
request.httpMethod = method.rawValue
request.httpShouldHandleCookies = false
@@ -175,7 +175,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
return request
}
- func request(method: HTTPMethod, URLString: String, headers: [String : String]?, parameters: T) -> URLRequest where T: Encodable {
+ func downloadRequest(method: HTTPMethod, URLString: String, headers: [String : String]?, parameters: T) -> URLRequest where T: Encodable {
var URLString = URLString
let isGet = (method == .GET)
@@ -189,7 +189,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}
}
- var request = self.request(method: method, URLString: URLString, headers: headers)
+ var request = self.downloadRequest(method: method, URLString: URLString, headers: headers)
// for non-GET requests, the parameters (if any) go in the request body
let contentTypeHeader = "Content-Type"
@@ -212,30 +212,30 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}
@discardableResult
- func downloadFile(from URLString: String, method: HTTPMethod, httpHeaders: [String : String]?, taskDescription: String) -> BridgeURLSessionDownloadTask {
- let request = self.request(method: method, URLString: URLString, headers: httpHeaders)
+ func downloadFile(from URLString: String, method: HTTPMethod, httpHeaders: [String : String]?, taskDescription: String) -> Bool {
+ let request = self.downloadRequest(method: method, URLString: URLString, headers: httpHeaders)
return self.downloadFile(with: request, taskDescription: taskDescription)
}
@discardableResult
- func downloadFile(from URLString: String, method: HTTPMethod, httpHeaders: [String : String]?, parameters: T?, taskDescription: String) -> BridgeURLSessionDownloadTask where T: Encodable {
- let request = self.request(method: method, URLString: URLString, headers: httpHeaders, parameters: parameters)
+ func downloadFile(from URLString: String, method: HTTPMethod, httpHeaders: [String : String]?, parameters: T?, taskDescription: String) -> Bool where T: Encodable {
+ let request = self.downloadRequest(method: method, URLString: URLString, headers: httpHeaders, parameters: parameters)
return self.downloadFile(with: request, taskDescription: taskDescription)
}
- private func downloadFile(with request: URLRequest, taskDescription: String) -> BridgeURLSessionDownloadTask {
+ private func downloadFile(with request: URLRequest, taskDescription: String) -> Bool {
let task = self.backgroundSession().downloadTask(with: request)
task.taskDescription = taskDescription
task.resume()
- return task
+ return true
}
@discardableResult
- func uploadFile(_ fileURL: URL, httpHeaders: [String : String]?, to urlString: String, taskDescription: String) -> BridgeURLSessionUploadTask? {
+ func uploadFile(_ fileURL: URL, httpHeaders: [String : String]?, to urlString: String, taskDescription: String) -> Bool {
guard let url = URL(string: urlString) else {
let message = "Error: Could not create URL from string '\(urlString)"
Logger.log(tag: .upload, error: BridgeUnexpectedNullError(category: .invalidURL, message: message))
- return nil
+ return false
}
var request = URLRequest(url: url)
request.allHTTPHeaderFields = httpHeaders
@@ -243,7 +243,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
let task = backgroundSession().uploadTask(with: request, fromFile: fileURL)
task.taskDescription = taskDescription
task.resume()
- return task
+ return true
}
func restore(backgroundSession: String, completionHandler: @escaping () -> Void) {
@@ -339,6 +339,28 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
$0.bridgeUrlSession(session, didBecomeInvalidWithError: error)
}
}
+
+ // MARK: Async task handling
+
+ func getAllTasks() async -> [BridgeURLSessionTask] {
+ let mainSession = backgroundSession()
+ var tasks: [BridgeURLSessionTask] = await withCheckedContinuation { continuation in
+ mainSession.getAllTasks {
+ continuation.resume(returning: $0 as [BridgeURLSessionTask])
+ }
+ }
+ for pair in self.restoredSessions {
+ let session = pair.value as any BridgeURLSession
+ if session.identifier != mainSession.identifier {
+ tasks.append(contentsOf: await withCheckedContinuation { continuation in
+ session.getAllTasks {
+ continuation.resume(returning: $0 as [BridgeURLSessionTask])
+ }
+ })
+ }
+ }
+ return tasks
+ }
}
// MARK: URLSession Mocking
@@ -364,6 +386,7 @@ protocol BridgeURLSession : NSObjectProtocol {
protocol BridgeURLSessionTask : NSObjectProtocol {
var taskDescription: String? { get set }
+ var state: URLSessionTask.State { get }
var taskType: BridgeURLSessionTaskType { get }
var originalRequest: URLRequest? { get }
var response: URLResponse? { get }
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundProcessSyncManager.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundProcessSyncManager.swift
new file mode 100644
index 000000000..9b8e4258d
--- /dev/null
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundProcessSyncManager.swift
@@ -0,0 +1,269 @@
+// Created 8/28/23
+// swift-tools-version:5.0
+
+import Foundation
+import BackgroundTasks
+
+/// The delegate is responsible for the actual work that needs to be processed.
+protocol BackgroundProcessSyncDelegate : AnyObject {
+
+ /// Should a background process be scheduled?
+ var shouldScheduleBackgroundProcess: Bool { get }
+
+ /// The method to call to process work. The method should be able to handle having the task
+ /// cancelled (because of timing out) and exit gracefully.
+ ///
+ /// - Parameter isBackground: Is the process being called while the app is already in the background?
+ func retryBackgroundProcess(isBackground: Bool) async throws
+}
+
+#if canImport(UIKit)
+import UIKit
+
+/// The `BackgroundProcessSyncManager` is set up to allow syncing a background processing
+/// task scheduled with `BGTaskScheduler` while **also** allowing the process to run while
+/// the app is active with a timing that is more "chatty".
+///
+/// - Note: syoung 08/28/2023 The background process sync manager was set up this way
+/// to allow testing using this manager in a simplified app. The use of `BGTaskScheduler`
+/// is a bit brittle - as of this writing, a background process that runs when the app is
+/// backgrounded will stall on tasks that are running on the `MainActor` so only
+/// ``onLaunch(backgroundProcessId:)`` is ever called from that thread.
+///
+actor BackgroundProcessSyncManager {
+
+ weak private var delegate: BackgroundProcessSyncDelegate!
+
+ init(delegate: BackgroundProcessSyncDelegate) {
+ self.delegate = delegate
+ }
+
+ /// Toggle flag used to check whether or not the app is running in the background.
+ var isAppBackground: Bool = false
+
+ /// **Required:** This method should be called on app launch to register the background
+ /// process id and set up the listeners for app becoming active and entering the
+ /// background.
+ @MainActor
+ func onLaunch(backgroundProcessId: String?) {
+ // Register the background process
+ if let backgroundProcessId = backgroundProcessId {
+ registerBackgroundTasks(backgroundProcessId)
+ }
+ setupAppListeners()
+ }
+
+ /// Retry the background process immediately. Calls to this method are ignored if the app
+ /// is in the background or the retry is already running.
+ func retryBackgroundProcessAsync() {
+ guard !isAppBackground else {
+ Logger.log(severity: .info, tag: .upload, message: "Attempting to check and retry uploads from the background")
+ return
+ }
+ guard checkRetryTask?.isCancelled ?? true else {
+ Logger.log(severity: .info, tag: .upload, message: "Check and retry already running. Ignoring.")
+ return
+ }
+
+ checkRetryTask = Task(priority: .background) {
+
+ // retry all pending uploads
+ let task = taskToRetryAllPendingUploads(isBackground: false)
+ let _ = await task.result
+
+ // check if we still have pending uploads and schedule a retry
+ if !isAppBackground,
+ (delegate?.shouldScheduleBackgroundProcess ?? false) {
+ enqueueForRetry()
+ }
+
+ // nil out the check retry task so it can be restarted
+ checkRetryTask = nil
+ }
+ }
+
+ /// Queue up a delay timer for retrying the background process. This will only trigger
+ /// if the app is active and there isn't already a retry queued up.
+ func enqueueForRetry() {
+ guard !isAppBackground else {
+ Logger.log(severity: .info, tag: .upload, message: "Attempting to enqueue file for retry in the background. Ignoring.")
+ return
+ }
+ guard waitRetryTask?.isCancelled ?? true else { return }
+ waitRetryTask = Task(priority: .background) {
+ do {
+ try await Task.sleep(nanoseconds: UInt64(delayForRetry * 1_000_000_000))
+ retryBackgroundProcessAsync()
+ } catch is CancellationError {
+ Logger.log(severity: .info, tag: .upload, message: "Wait for check and retry uploads cancelled")
+ } catch {
+ Logger.log(tag: .upload, error: error, message: "Unexpected failure waiting to retry upload")
+ }
+ waitRetryTask = nil
+ }
+ }
+
+ // MARK: Private methods and ivars
+
+ /// The minimum delay before retrying a failed upload (in seconds). This is a
+ /// variable so that unit tests can run without waiting.
+ var delayForRetry: TimeInterval = 5 * 60
+
+ private var waitRetryTask: Task?
+ private var checkRetryTask: Task?
+ private var retryTask: Task?
+ private var backgroundProcessId: String?
+
+ private func taskToRetryAllPendingUploads(isBackground: Bool) -> Task {
+ if retryTask == nil {
+ retryTask = Task(priority: .background) {
+
+ // if launched from the foreground, then ask for time to finish in the background
+ if !isBackground {
+ await beginTimedBackgroundTask()
+ }
+
+ // run the background process - catching all errors
+ do {
+ Logger.log(severity: .info, tag: .upload, message: "Retrying background process. isBackground=\(isBackground)")
+ try await delegate?.retryBackgroundProcess(isBackground: isBackground)
+ } catch is CancellationError {
+ Logger.log(severity: .info, tag: .upload, message: "Check and retry uploads cancelled")
+ } catch {
+ // TODO: syoung 08/29/2023 Check if the error is b/c of spotty network connectivity
+ Logger.log(tag: .upload, error: error, message: "Failure when checking and retrying uploads")
+ }
+
+ // finally, let the app know that we are done with our background process
+ // (either because it finished or because it timed out)
+ if !isBackground {
+ await endTimedBackgroundTask()
+ }
+
+ // nil out the finished task so it can be restarted
+ retryTask = nil
+ }
+ }
+ return retryTask!
+ }
+
+ private func setBackgroundProcessId(_ backgroundProcessId: String) {
+ self.backgroundProcessId = backgroundProcessId
+ }
+
+ private func didBecomeActive() {
+ Logger.log(severity: .info, tag: .upload, message: "App did become active. Check and retry uploads.")
+ // when becoming active, we want to retry running the process to clear the queue
+ isAppBackground = false
+ retryBackgroundProcessAsync()
+ }
+
+ private func didEnterBackground() {
+ Logger.log(severity: .info, tag: .upload, message: "App did enter background. Scheduling background processing if needed.")
+ // when entering the background, we want to cancel waiting to retry uploads
+ // and then schedule a background process.
+ isAppBackground = true
+ waitRetryTask?.cancel()
+ scheduleBackgroundProcessingIfNeeded()
+ }
+
+ private func scheduleBackgroundProcessingIfNeeded() {
+ // Check that there is a registered background process id, that the
+ // app is still in the background, and that the delegate has work
+ // to do.
+ guard let taskId = self.backgroundProcessId,
+ isAppBackground,
+ delegate?.shouldScheduleBackgroundProcess ?? false
+ else {
+ return
+ }
+ Logger.log(severity: .info, tag: .upload, message: "Pending uploads detected. Scheduling background process: \(taskId)")
+
+ let request = BGProcessingTaskRequest(identifier: taskId)
+ request.requiresNetworkConnectivity = true // Do not attempt processing without network connectivity
+ request.earliestBeginDate = Date(timeIntervalSinceNow: 15*60) // Wait 15 minutes to allow S3 uploads in progress to complete
+
+ do {
+ try BGTaskScheduler.shared.submit(request)
+ } catch {
+ Logger.log(tag: .upload, error: error, message: "Error scheduling BG task: \(taskId)")
+ }
+ }
+
+ // MARK: @MainActor methods used to handle set up and continuing a foreground process in the background
+
+ @MainActor var timedBackgroundTaskId: UIBackgroundTaskIdentifier?
+
+ @MainActor
+ private func registerBackgroundTasks(_ backgroundProcessId: String) {
+ Task {
+ await setBackgroundProcessId(backgroundProcessId)
+ }
+ BGTaskScheduler.shared.register(forTaskWithIdentifier: backgroundProcessId, using: .main) { task in
+ Task {
+ let retry = await self.taskToRetryAllPendingUploads(isBackground: true)
+ let _ = await retry.result
+ task.setTaskCompleted(success: true)
+ await self.scheduleBackgroundProcessingIfNeeded()
+ }
+ }
+ }
+
+ @MainActor
+ private func setupAppListeners() {
+ // Set up a listener to retry temporarily-failed uploads whenever the app becomes active
+ NotificationCenter.default.addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in
+ guard let strongSelf = self else { return }
+ Task {
+ await strongSelf.didBecomeActive()
+ }
+ }
+ // Set up a listener to handle entering background
+ NotificationCenter.default.addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
+ guard let strongSelf = self else { return }
+ Task {
+ await strongSelf.didEnterBackground()
+ let _ = true // Use this as a breakpoint for simulating a background process
+ }
+ }
+ }
+
+ @MainActor
+ private func beginTimedBackgroundTask() {
+ guard timedBackgroundTaskId == nil else { return }
+ timedBackgroundTaskId = UIApplication.shared.beginBackgroundTask {
+ Logger.log(severity: .info, tag: .upload, message: "Timed out when checking and retrying uploads.")
+ self.endTimedBackgroundTask()
+ Task {
+ await self.retryTask?.cancel()
+ }
+ }
+ }
+
+ @MainActor
+ private func endTimedBackgroundTask() {
+ guard let taskId = timedBackgroundTaskId else { return }
+ timedBackgroundTaskId = nil
+ UIApplication.shared.endBackgroundTask(taskId)
+ }
+
+}
+
+#else
+
+/// This is included b/c a limitation of Swift PM is that you cannot explicitly specify
+/// support for only certain platforms. syoung 08/29/2023
+actor BackgroundProcessSyncManager {
+ init(delegate: BackgroundProcessSyncDelegate) { }
+ var isAppBackground: Bool = false
+ @MainActor func onLaunch(backgroundProcessId: String?) { }
+ func retryBackgroundProcessAsync() { }
+ func enqueueForRetry() { }
+}
+
+#endif
+
+/** Command for simulating launch of a background task:
+ e -l objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"org.sagebase.BackgroundProcessTest"]
+
+ */
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUnexpectedNullError.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUnexpectedNullError.swift
index 0b15d523d..89654cf2c 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUnexpectedNullError.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUnexpectedNullError.swift
@@ -28,5 +28,6 @@ struct BridgeUnexpectedNullError : Error, CustomNSError {
case missingMetadata = -107
case missingMapping = -108
case notFound = -109
+ case duplicateCall = -110
}
}
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUploadFailedError.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUploadFailedError.swift
index 0449bca64..c95ac0039 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUploadFailedError.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/Errors/BridgeUploadFailedError.swift
@@ -6,12 +6,19 @@ import Foundation
struct BridgeUploadFailedError : Error, CustomNSError {
static var errorDomain: String { "BridgeClient.UploadFailedError" }
- let errorCode: Int
+ let category: Category
let message: String
- init(errorCode: Int = -1, message: String = "Bridge upload failed") {
- self.errorCode = errorCode
- self.message = message
+ var errorCode: Int {
+ category.rawValue
+ }
+
+ enum Category : Int {
+ case unknown = -1
+ case backgroundTaskTimeout = -51
+ case wrongHandler = -52
+ case fileFailure = -53
+ case unexpectedBackgroundCall = -54
}
var errorUserInfo: [String : Any] {
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/SandboxFileManager.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/SandboxFileManager.swift
index 4deef2b45..d430ea70a 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/SandboxFileManager.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/SandboxFileManager.swift
@@ -42,7 +42,19 @@ class SandboxFileManager: NSObject {
// Use a UUID for the temp file's name
let tempFileURL = uploadDirURL.appendingPathComponent(UUID().uuidString)
-
+
+ // Copy the file to the temp file
+ guard copyFile(from: fileURL, to: tempFileURL) else {
+ return nil
+ }
+
+ // "touch" the temp file for retry accounting purposes
+ self.touch(fileUrl: tempFileURL)
+
+ return (fileURL, tempFileURL)
+ }
+
+ func copyFile(from fileURL: URL, to tempFileURL: URL) -> Bool {
// Use a NSFileCoordinator to make a temp local copy so the app can delete
// the original as soon as the upload call returns.
let coordinator = NSFileCoordinator(filePresenter: nil)
@@ -57,17 +69,13 @@ class SandboxFileManager: NSObject {
}
}
if copyError != nil {
- return nil
+ return false
}
if let err = coordError {
Logger.log(tag: .upload, error: err, message: "File coordinator error copying upload file \(fileURL) to temp file \(tempFileURL) for upload.")
- return nil
+ return false
}
-
- // "touch" the temp file for retry accounting purposes
- self.touch(fileUrl: tempFileURL)
-
- return (fileURL, tempFileURL)
+ return true
}
func mimeTypeFor(fileUrl: URL) -> String {
@@ -175,6 +183,10 @@ class SandboxFileManager: NSObject {
return normalizedPath
}
+ func fileURL(of path: String) -> URL {
+ URL(fileURLWithPath: fullyQualifiedPath(of: path))
+ }
+
/// Return the file size of the file at this URL.
func fileContentLength(_ fileUrl: URL) -> Int? {
do {
@@ -201,6 +213,32 @@ class SandboxFileManager: NSObject {
return nil
}
}
+
+ /// Check that the file exists
+ func fileExists(atPath filePath: String) -> Bool {
+ FileManager.default.fileExists(atPath: filePath)
+ }
+
+ /// Check that the file exists
+ func fileExists(at url: URL) -> Bool {
+ fileExists(atPath: url.absoluteString)
+ }
+
+ /// Append the base directory with a subdirectory and create if needed.
+ /// - Parameters:
+ /// - baseURL: The base directory
+ /// - subdir: The subdirectory to append
+ /// - Returns: The subdirectory or nil it fails.
+ func createSubdirectory(baseURL: URL, subdir: String) throws -> URL {
+ let url: URL
+ if #available(iOS 16.0, *) {
+ url = baseURL.appending(component: subdir, directoryHint: .isDirectory)
+ } else {
+ url = baseURL.appendingPathComponent(subdir)
+ }
+ try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
+ return url
+ }
}
diff --git a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/UploadManager.swift b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/UploadManager.swift
index 9fe0dc35b..666613173 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/File Uploading/UploadManager.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/File Uploading/UploadManager.swift
@@ -2,55 +2,71 @@
// swift-tools-version:5.0
import Foundation
+import BackgroundTasks
import BridgeClient
-protocol BridgeUploader {
-
- /// Upload an encrypted archive.
- /// - Parameters:
- /// - fileUrl: The file url of the original encrypted archive.
- /// - schedule: The schedule (if any) associated with this archive.
- /// - startedOn: The `startedOn` value for the associated adherence record that is used to uniquely identify it.
- /// - Returns: `true` if the file was successfully copied and queued for upload.
- @MainActor func uploadEncryptedArchive(fileUrl: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) -> Bool
-
-
- /// Upload a file to Bridge.
- /// - Parameters:
- /// - fileUrl: The original file url for the file to upload.
- /// - contentType: The content-type for the file.
- /// - encrypted: Whether or not the file is encrypted.
- /// - metadata: The metadata associated with this file.
- /// - s3UploadType: The upload type.
- /// - Returns: `true` if the file was successfully copied and queued for upload.
- @MainActor func upload(fileUrl: URL, contentType: String, encrypted: Bool, metadata: UploadMetadata?, s3UploadType: S3UploadType) -> Bool
-}
+#if canImport(UIKit)
+import UIKit
+#endif
+
+class UploadManager : NSObject, BridgeURLSessionHandler, BackgroundProcessSyncDelegate {
-class UploadManager : NSObject, BridgeUploader {
- static let shared: UploadManager = .init()
+ weak private(set) var backgroundNetworkManager: SharedBackgroundUploadManager!
+ let sandboxFileManager: SandboxFileManager
+ let nativeUploadManager: BridgeClientUploadManager
+ lazy private(set) var syncManager: BackgroundProcessSyncManager = .init(delegate: self)
- lazy var sandboxFileManager: SandboxFileManager = .init()
- lazy var nativeUploadManager: BridgeClientUploadManager = NativeUploadManager()
+ init(backgroundNetworkManager: SharedBackgroundUploadManager,
+ sandboxFileManager: SandboxFileManager = .init(),
+ nativeUploadManager: BridgeClientUploadManager = NativeUploadManager()
+ ) {
+ // Set up pointers
+ self.sandboxFileManager = sandboxFileManager
+ self.nativeUploadManager = nativeUploadManager
+ super.init()
+
+ // register self with the background network manager
+ self.backgroundNetworkManager = backgroundNetworkManager
+ backgroundNetworkManager.registerBackgroundTransferHandler(self)
+ }
+
+ let subdir = "BridgeUploadsV2"
lazy var uploadDirURL: URL? = {
do {
let baseURL = try FileManager.default.sharedAppSupportDirectory()
- let url: URL
- let subdir = "BridgeUploads"
- if #available(iOS 16.0, *) {
- url = baseURL.appending(component: subdir, directoryHint: .isDirectory)
- } else {
- url = baseURL.appendingPathComponent(subdir)
- }
- try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
- return url
+ return try sandboxFileManager.createSubdirectory(baseURL: baseURL, subdir: subdir)
} catch {
- Logger.log(tag: .upload, error: error, message: "Error trying to create the uploads directory.")
+ Logger.log(tag: .upload, error: error, message: "Error trying to create the shared app support directory.")
return nil
}
}()
- func uploadEncryptedArchive(fileUrl: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) -> Bool {
+ var isArchiving: Bool = false
+
+ /// **Required:** This method should be called on app launch to register the background
+ /// process id and set up the listeners for app becoming active and entering the
+ /// background.
+ @MainActor
+ func onLaunch(backgroundProcessId: String?) {
+ syncManager.onLaunch(backgroundProcessId: backgroundProcessId)
+ }
+
+ /// Force check and retry uploads.
+ func checkAndRetryUploads() {
+ Task {
+ await syncManager.retryBackgroundProcessAsync()
+ }
+ }
+
+ /// Upload an encrypted archive.
+ /// - Parameters:
+ /// - fileUrl: The file url of the original encrypted archive.
+ /// - schedule: The schedule (if any) associated with this archive.
+ /// - startedOn: The `startedOn` value for the associated adherence record that is used to uniquely identify it.
+ /// - Returns: `true` if the file was successfully copied and queued for upload.
+ @MainActor
+ func uploadEncryptedArchive(fileUrl: URL, schedule: AssessmentScheduleInfo?, startedOn: Date?) async -> Bool {
let metadata: UploadMetadata? = schedule.map {
UploadMetadata(
instanceGuid: $0.instanceGuid,
@@ -58,13 +74,22 @@ class UploadManager : NSObject, BridgeUploader {
startedOn: startedOn?.jsonObject() as? String
)
}
- return upload(fileUrl: fileUrl, contentType: "application/zip", encrypted: true, metadata: metadata, s3UploadType: .studyData)
+ return await upload(fileUrl: fileUrl, contentType: "application/zip", encrypted: true, metadata: metadata, s3UploadType: .studyData)
}
- func upload(fileUrl: URL, contentType: String, encrypted: Bool, metadata: UploadMetadata?, s3UploadType: S3UploadType) -> Bool {
+ /// Upload a file to Bridge.
+ /// - Parameters:
+ /// - fileUrl: The original file url for the file to upload.
+ /// - contentType: The content-type for the file.
+ /// - encrypted: Whether or not the file is encrypted.
+ /// - metadata: The metadata associated with this file.
+ /// - s3UploadType: The upload type.
+ /// - Returns: `true` if the file was successfully copied and queued for upload.
+ @MainActor
+ func upload(fileUrl: URL, contentType: String, encrypted: Bool, metadata: UploadMetadata?, s3UploadType: S3UploadType) async -> Bool {
// Both study data and participant data should first be copied to a temp file.
- guard let uploadDirURL = self.uploadDirURL,
+ guard let uploadDirURL = self.uploadDir(for: s3UploadType),
let (_, tempUrl) = sandboxFileManager.tempFileFor(inFileURL: fileUrl, uploadDirURL: uploadDirURL),
let contentLength = sandboxFileManager.fileContentLength(tempUrl)
else {
@@ -94,27 +119,323 @@ class UploadManager : NSObject, BridgeUploader {
md5Hash: contentMD5String,
encrypted: encrypted,
metadata: metadata,
- s3UploadType: .studyData)
+ s3UploadType: s3UploadType)
// Finally, queue the upload using the native upload manager.
- nativeUploadManager.queueAndRequestUploadSession(uploadFile: uploadFile, callBack: startS3Upload)
+ if let uploadSession = await nativeUploadManager.queueAndRequestUploadSession(uploadFile: uploadFile) {
+ // If there is an upload session then start the upload.
+ startS3Upload(uploadSession)
+ }
+ else {
+ // If the callback is nil then services are offline. Try again later.
+ await enqueueForRetry(filePath: invariantFilePath, uploaded: false)
+ }
+
return true
}
- private func startS3Upload(_ uploadSession: S3UploadSession?) {
- // TODO: Implement syoung 07/26/2023
+ fileprivate func uploadDir(for s3UploadType: S3UploadType) -> URL? {
+ guard let baseURL = uploadDirURL else { return nil }
+ do {
+ return try sandboxFileManager.createSubdirectory(baseURL: baseURL, subdir: s3UploadType.name)
+ } catch {
+ Logger.log(tag: .upload, error: error, message: "Error trying to create the uploads directory for \(s3UploadType.name).")
+ return nil
+ }
}
+ // MARK: BackgroundProcessSyncDelegate
+
+ var shouldScheduleBackgroundProcess: Bool {
+ isArchiving || nativeUploadManager.hasPendingUploads()
+ }
+
+ func retryBackgroundProcess(isBackground: Bool) async throws {
+ try await retryUploadToS3()
+ try await processFinishedUploads()
+ try await cleanupLostFiles()
+ }
+
+ // MARK: BridgeURLSessionHandler
+
+ func canHandle(task: BridgeURLSessionTask) -> Bool {
+ return (task.taskType == .upload) && (task.taskDescription?.contains(subdir) ?? false)
+ }
+
+ func bridgeUrlSession(_ session: any BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
+ Task {
+ do {
+ try await urlSession(task: task, didCompleteWithError: error)
+ } catch {
+ Logger.log(tag: .upload, error: error, message: "Failed to handle completed task \(task)")
+ }
+ }
+ }
+
+ func urlSession(task: BridgeURLSessionTask, didCompleteWithError error: Error?) async throws {
+ // Ignore tasks that are not uploads
+ guard task.taskType == .upload else {
+ throw BridgeUploadFailedError(category: .wrongHandler, message: "Failed to handle upload task \(task) using the \(self)")
+ }
+
+ // get the sandbox-relative path to the temp copy of the participant file
+ let (uploadSessionId, invariantFilePath) = try task.parseTaskDescription()
+
+ // any error that makes it all the way through to here would be the result of something like a
+ // malformed request, so clean up the temp file and throw the error
+ if let error = error {
+ handleUnrecoverableFailure(filePath: invariantFilePath)
+ throw error
+ }
+
+ // get the http response
+ guard let httpResponse = task.response as? HTTPURLResponse else {
+ throw BridgeUnexpectedNullError(category: .wrongType,
+ message: "Upload task response is not an HTTPURLResponse: \(type(of: task.response))")
+ }
+
+ // check the upload status
+ switch httpResponse.httpUploadStatus() {
+ case .unhandled:
+ // The status code is unrecoverable
+ handleUnrecoverableFailure(filePath: invariantFilePath)
+ throw BridgeHttpError(errorCode: httpResponse.statusCode, message: "Participant file upload to S3 of file \(invariantFilePath) failed with HTTP response status code \(httpResponse.statusCode)--unhandled, will not retry")
+
+ case .retry:
+ // this call failed but we may be able to recover with a retry
+ await enqueueForRetry(filePath: invariantFilePath, uploaded: false)
+
+ case .success:
+ // If we get here, then we are done uploading to S3. Mark the file and clean up the temp file.
+ async let success = nativeUploadManager.markUploadFileFinished(filePath: invariantFilePath, uploadSessionId: uploadSessionId)
+ sandboxFileManager.removeTempFile(filePath: invariantFilePath)
+ if await !success {
+ // If we aren't successful in sending the completion then enqueue for retry
+ await enqueueForRetry(filePath: invariantFilePath, uploaded: true)
+ }
+ }
+ }
+
+ func bridgeUrlSession(_ session: any BridgeURLSession, didBecomeInvalidWithError error: Error?) {
+ // If a url session becomes invalid, just queue up a delayed retry
+ Task {
+ await syncManager.enqueueForRetry()
+ }
+ }
+
+ // MARK: Private methods
+
+ func urlUploadTaskIdentifier(filePath: String, uploadSessionId: String) -> String {
+ "\(uploadSessionId)|\(filePath)"
+ }
+
+ private func startS3Upload(_ uploadSession: S3UploadSession) {
+ let fileURL = sandboxFileManager.fileURL(of: uploadSession.filePath)
+ let taskIdentifier = urlUploadTaskIdentifier(filePath: uploadSession.filePath, uploadSessionId: uploadSession.uploadSessionId)
+ backgroundNetworkManager.uploadFile(fileURL,
+ httpHeaders: uploadSession.requestHeaders,
+ to: uploadSession.url,
+ taskDescription: taskIdentifier)
+ }
+
+ private func handleUnrecoverableFailure(filePath: String) {
+ nativeUploadManager.markUploadUnrecoverableFailure(filePath: filePath)
+ // TODO: syoung 08/24/2023 If something "unrecoverable" happens, do we want to remove the file or just leave it?
+ sandboxFileManager.removeTempFile(filePath: filePath)
+ }
+
+ func enqueueForRetry(filePath: String, uploaded: Bool) async {
+ // TODO: syoung 08/29/2023 Consider if we need to *only* retry the one file?
+ await syncManager.enqueueForRetry()
+ }
+
+ private func processFinishedUploads() async throws {
+ // process the finished uploads - this is the last call and can take a while
+ // TODO: syoung 08/23/2023 We need to revisit this for the case where there are *a lot*
+ // of completion messages to send (since they are all set serially) so that this process
+ // doesn't timeout and get killed by the OS.
+ guard !Task.isCancelled else {
+ throw CancellationError()
+ }
+ let _ = try await nativeUploadManager.processFinishedUploads()
+ }
+
+ private func retryUploadToS3() async throws {
+
+ // get all the currently running upload tasks that can be handled by this manager
+ async let allUrlSessionTasks = backgroundNetworkManager.getAllTasks()
+ // get all the files that are currently pending
+ async let allPendingUploads = nativeUploadManager.getPendingUploadFiles()
+
+ // look through all the currect url session tasks and figure out which should be restarted
+ var uploadsToRetry = Set(await allPendingUploads)
+ for urlSessionTask in await allUrlSessionTasks {
+ // check that the task is not cancelled
+ guard !Task.isCancelled else {
+ throw CancellationError()
+ }
+ // check that this is an upload task handled by this manager
+ guard self.canHandle(task: urlSessionTask),
+ let uploadId = urlSessionTask.parsePendingUpload()
+ else {
+ Logger.log(severity: .info, tag: .upload, message: "Skipping URLSessionTask \(urlSessionTask) - does not match pattern of tasks handled by the UploadManager.")
+ continue
+ }
+ let requiresRestart = uploadsToRetry.contains(.init(filePath: uploadId.filePath, uploadSessionId: nil))
+ switch urlSessionTask.state {
+ case .running, .suspended:
+ if requiresRestart {
+ // upload session id has expired - need to restart
+ Logger.log(severity: .info, tag: .upload, message: "Found expired URLSessionTask for \(uploadId) - cancelling task")
+ urlSessionTask.cancel()
+ }
+ else {
+ // If the session is still valid, then check if it needs to be resumed.
+ if urlSessionTask.state == .suspended {
+ Logger.log(severity: .warn, tag: .upload, message: "Found valid suspended URLSessionTask for \(uploadId) - resuming")
+ urlSessionTask.resume()
+ }
+ if let _ = uploadsToRetry.remove(uploadId) {
+ Logger.log(severity: .info, tag: .upload, message: "Found valid running URLSessionTask for \(uploadId) - not restarting")
+ }
+ }
+
+ default:
+ // TODO: syoung 08/28/2023 Do we need to do more handling for cancelled or completed tasks?
+ Logger.log(severity: .info, tag: .upload, message: "Found URLSessionTask for \(uploadId) - state=\(urlSessionTask.state)")
+ break
+ }
+ }
+
+ // fetch the uploads to retry
+ for upload in uploadsToRetry {
+ // check that the task is not cancelled
+ guard !Task.isCancelled else {
+ throw CancellationError()
+ }
+ // request an upload session and if non-nil, then start it
+ if let uploadSession = await nativeUploadManager.requestUploadSession(filePath: upload.filePath) {
+ startS3Upload(uploadSession)
+ }
+ }
+ }
+
+ private func cleanupLostFiles() async throws {
+ // TODO: syoung 08/25/2023 Implement
+ }
+}
+
+enum HTTPS3UploadStatus {
+ case success, retry, unhandled
+}
+
+extension HTTPURLResponse {
+ func httpUploadStatus() -> HTTPS3UploadStatus {
+ if statusCode < 300 {
+ return .success
+ }
+ else {
+ switch statusCode {
+ case 403, 409, 500, 503:
+ // 403: for our purposes means the pre-signed url timed out before starting the actual upload to S3.
+ // 409: in our case it could only mean a temporary conflict (resource locked by another process, etc.) that should be retried.
+ // 500: means internal server error ("We encountered an internal error. Please try again.")
+ // 503: means service not available or the requests are coming too fast, so try again later.
+ // In any case, we'll retry after a minimum delay to avoid spamming retries.
+ return .retry
+
+ default:
+ // iOS handles redirects automatically so only e.g. 304 resource not changed etc. from the 300 range should end up here
+ // (along with all unhandled 4xx and 5xx of course).
+ return .unhandled
+ }
+ }
+ }
+}
+
+extension BridgeURLSessionTask {
+
+ fileprivate func parseTaskDescription() throws -> (sessionId: String, invariantFilePath: String) {
+ guard let identifier = taskDescription else {
+ throw BridgeUnexpectedNullError(category: .empty, message: "The `taskDescription` should not be NULL for an S3 upload.")
+ }
+ let split = identifier.components(separatedBy: "|")
+ guard split.count == 2 else {
+ throw BridgeUnexpectedNullError(category: .missingIdentifier, message: "The `taskDescription` should include the '|' character to separate the components of the identifier: \(identifier)")
+ }
+ return (split[0], split[1])
+ }
+
+ fileprivate func parsePendingUpload() -> PendingUploadFile? {
+ guard let (sessionId, filePath) = try? parseTaskDescription() else {
+ return nil
+ }
+ return .init(filePath: filePath, uploadSessionId: sessionId)
+ }
+}
+
+// Use a protocol to wrap the background network manager - this is to allow using a mock for testing.
+protocol SharedBackgroundUploadManager : AnyObject {
+ var sessionDelegateQueue: OperationQueue { get }
+ func registerBackgroundTransferHandler(_ handler: BridgeURLSessionHandler)
+ @discardableResult func uploadFile(_ fileURL: URL, httpHeaders: [String : String]?, to urlString: String, taskDescription: String) -> Bool
+ func getAllTasks() async -> [BridgeURLSessionTask]
+}
+
+extension BackgroundNetworkManager : SharedBackgroundUploadManager {
}
// Use a protocol to wrap the native upload manager - this is to allow using a mock for testing.
protocol BridgeClientUploadManager : AnyObject {
- func getUploadFiles() -> [String]
- func queueAndRequestUploadSession(uploadFile: UploadFile, callBack: @escaping (S3UploadSession?) -> Void)
- func requestUploadSession(filePath: String, callBack: @escaping (S3UploadSession?) -> Void)
- func markUploadFileFinished(filePath: String, callBack: @escaping (KotlinBoolean) -> Void)
- func processFinishedUploads(callBack: @escaping (KotlinBoolean) -> Void)
+ func hasPendingUploads() -> Bool
+ func getPendingUploadFiles() async -> [PendingUploadFile]
+ func queueAndRequestUploadSession(uploadFile: UploadFile) async -> S3UploadSession?
+ func requestUploadSession(filePath: String) async -> S3UploadSession?
+ func markUploadFileFinished(filePath: String, uploadSessionId: String) async -> Bool
+ func markUploadUnrecoverableFailure(filePath: String)
+ func processFinishedUploads() async throws -> Bool
}
extension NativeUploadManager : BridgeClientUploadManager {
+
+ func getPendingUploadFiles() async -> [PendingUploadFile] {
+ return await withCheckedContinuation { continuation in
+ self.getPendingUploadFiles() { ret in
+ continuation.resume(returning: ret)
+ }
+ }
+ }
+
+ func queueAndRequestUploadSession(uploadFile: UploadFile) async -> S3UploadSession? {
+ return await withCheckedContinuation { continuation in
+ self.queueAndRequestUploadSession(uploadFile: uploadFile) { session in
+ continuation.resume(returning: session)
+ }
+ }
+ }
+
+ func requestUploadSession(filePath: String) async -> S3UploadSession? {
+ return await withCheckedContinuation { continuation in
+ self.requestUploadSession(filePath: filePath) { session in
+ continuation.resume(returning: session)
+ }
+ }
+ }
+
+ func markUploadFileFinished(filePath: String, uploadSessionId: String) async -> Bool {
+ return await withCheckedContinuation { continuation in
+ self.markUploadFileFinished(filePath: filePath, uploadSessionId: uploadSessionId) { result in
+ continuation.resume(returning: result.boolValue)
+ }
+ }
+ }
+
+ func processFinishedUploads() async throws -> Bool {
+ return await withCheckedContinuation { continuation in
+ self.processFinishedUploads() { result in
+ continuation.resume(returning: result.boolValue)
+ }
+ }
+ }
}
+
diff --git a/SwiftPackage/Sources/BridgeClientExtension/Model/NetworkStatus.swift b/SwiftPackage/Sources/BridgeClientExtension/Model/NetworkStatus.swift
index 821fb7f80..d06e8c520 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/Model/NetworkStatus.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/Model/NetworkStatus.swift
@@ -2,6 +2,7 @@
// swift-tools-version:5.0
import Foundation
+import Network
/// Option set for the current network status.
public struct NetworkStatus : OptionSet, CaseIterable, Hashable {
@@ -38,3 +39,88 @@ public struct NetworkStatus : OptionSet, CaseIterable, Hashable {
}
}
}
+
+/// A mockable wrapper for network monitoring.
+public class NetworkMonitor : ObservableObject {
+
+ /// Current network status.
+ @Published public fileprivate(set) var networkStatus: NetworkStatus = .unknown {
+ didSet {
+ self.didChangeHandler?(networkStatus)
+ }
+ }
+
+ /// Set a block to be called when the network status changes.
+ public var didChangeHandler: ((NetworkStatus) -> Void)?
+
+ public init(didChangeHandler: ((NetworkStatus) -> Void)? = nil) {
+ self.didChangeHandler = didChangeHandler
+ }
+
+ fileprivate let networkMonitoringQueue = DispatchQueue(label: "org.sagebase.NetworkConnectivityMonitor.\(UUID())")
+ fileprivate var networkMonitor: NWPathMonitor?
+
+ deinit {
+ networkMonitor?.cancel()
+ }
+
+ /// Cancel network monitoring, after which point no more path updates will be delivered.
+ public func cancel() {
+ networkMonitor?.cancel()
+ networkMonitor = nil
+ networkStatus = .unknown
+ }
+
+ /// Start monitoring network status. If status is already being monitored then multiple
+ /// calls to start will be ignored.
+ public func startIfNeeded() {
+ guard networkMonitor == nil else { return }
+ let networkMonitor = NWPathMonitor()
+ self.networkMonitor = networkMonitor
+ networkMonitor.pathUpdateHandler = { [weak self] path in
+ guard let strongSelf = self else { return }
+ DispatchQueue.main.async {
+ switch path.status {
+ case .unsatisfied:
+ if #available(iOS 14.2, *), path.unsatisfiedReason == .cellularDenied {
+ strongSelf.networkStatus = .cellularDenied
+ } else {
+ strongSelf.networkStatus = .notConnected
+ }
+ case .satisfied:
+ strongSelf.networkStatus = .connected
+ default:
+ strongSelf.networkStatus = .unknown
+ }
+ }
+ }
+ networkMonitor.start(queue: networkMonitoringQueue)
+ }
+}
+
+/// A mock network monitor that can be used to set the network status. This can be used in SwiftUI Preview
+/// or in unit tests.
+public class MockNetworkMonitor : NetworkMonitor {
+
+ override public func startIfNeeded() {
+ // Do not start network monitoring - this is a mock.
+ }
+
+ public func setNetworkStatus(_ newStatus: NetworkStatus, withDelay delay: DispatchTimeInterval? = nil) {
+ guard let delay = delay else {
+ if Thread.isMainThread {
+ self.networkStatus = newStatus
+ } else {
+ DispatchQueue.main.sync {
+ self.networkStatus = newStatus
+ }
+ }
+ return
+ }
+ self.networkMonitoringQueue.asyncAfter(deadline: .now().advanced(by: delay)) {
+ DispatchQueue.main.async {
+ self.networkStatus = newStatus
+ }
+ }
+ }
+}
diff --git a/SwiftPackage/Sources/BridgeClientExtension/UploadAppManager.swift b/SwiftPackage/Sources/BridgeClientExtension/UploadAppManager.swift
index aa2eda5c1..9ed2bc500 100644
--- a/SwiftPackage/Sources/BridgeClientExtension/UploadAppManager.swift
+++ b/SwiftPackage/Sources/BridgeClientExtension/UploadAppManager.swift
@@ -6,7 +6,6 @@
import Foundation
import BridgeClient
import JsonModel
-import Network
public let kPreviewStudyId = "xcode_preview"
public let kStudyIdKey = "studyId"
@@ -45,8 +44,7 @@ open class UploadAppManager : ObservableObject {
///
/// - Note: Data should be encrypted so that it is not stored insecurely on the phone (while waiting
/// for an upload connection).
- public var pemPath: String? { uploadProcessor.pemPath }
- private let uploadProcessor: ArchiveUploadProcessor
+ public var pemPath: String? { self.uploadProcessor.pemPath }
/// The title of the app to display. By default, this is the localized display name of the app that is shown
/// to the participant in their phone home screen.
@@ -193,13 +191,16 @@ open class UploadAppManager : ObservableObject {
}
}
+ private let uploadProcessor: ArchiveUploadProcessor
+ private let backgroundProcessId: String?
private var appConfigManager: NativeAppConfigManager!
public private(set) var authManager: NativeAuthenticationManager!
private var pendingUploadObserver: PendingUploadObserver!
- lazy var backgroundNetworkManager: BackgroundNetworkManager = .init()
- lazy var uploadManagerV1: BridgeFileUploadManager = .init(netManager: backgroundNetworkManager, appManager: self)
+ let backgroundNetworkManager: BackgroundNetworkManager = .init()
+ lazy private(set) var uploadManagerV1: BridgeFileUploadManager = .init(netManager: backgroundNetworkManager, appManager: self)
+ lazy private(set) var uploadManagerV2: UploadManager = .init(backgroundNetworkManager: backgroundNetworkManager)
- /// Convenience initializer for intializing a bridge manager with just an app id and pem file.
+ /// Convenience initializer for intializing a bridge manager with just required properties.
///
/// Both the APP ID and the CMS Public Key (ie. pem file) are accessible through the [Bridge Study Manager](https://research.sagebridge.org).
/// Once logged into an "app" (which can host multiple "studies"), select "Server Config -> Settings" from the menu.
@@ -209,17 +210,43 @@ open class UploadAppManager : ObservableObject {
/// - pemPath: The path to the pem file (as an embedded resource) to use for encrypting uploads. The pem file can be downloaded
/// from the [Bridge Study Manager](https://research.sagebridge.org) by going "Server Settings -> Settings"
/// and tapping on the button labeled "Download CMS Public Key..." and saving the file to a secure location.
+ /// - backgroundProcessId: The background process ID registered in the "info.plist" used to allow processing pending uploads
+ /// in the background.
+ /// - appGroupId: The app group ID to use when setting up files that are shared with an app extension.
+ /// - defaultConsentGuid: The default consent guid for this app (where the app includes in-app consent).
+ public convenience init(appId: String, pemPath: String, backgroundProcessId: String, appGroupId: String? = nil, defaultConsentGuid: String? = nil) {
+ self.init(platformConfig: PlatformConfigImpl(appId: appId, appGroupIdentifier: appGroupId, defaultConsentGuid: defaultConsentGuid), pemPath: pemPath, backgroundProcessId: backgroundProcessId)
+ }
+
+ public enum MockType {
+ case unitTest, preview
+ }
+
+ /// A special manager that is used in unit testing and SwiftUI preview.
+ /// - Parameter mockType: The type of mock manager to initialize.
+ public convenience init(mockType: MockType) {
+ let appId = mockType == .preview ? kPreviewStudyId : "not-a-real-appid"
+ self.init(appId: appId, pemPath: "", backgroundProcessId: "")
+ }
+
+ @available(*, deprecated, message: "Use initializer with non-nil pem path and background process Id")
public convenience init(appId: String, appGroupId: String? = nil, pemPath: String? = nil, defaultConsentGuid: String? = nil) {
self.init(platformConfig: PlatformConfigImpl(appId: appId, appGroupIdentifier: appGroupId, defaultConsentGuid: defaultConsentGuid), pemPath: pemPath)
}
+ @available(*, deprecated, message: "Use initializer with non-nil pem path and background process Id")
+ public convenience init(platformConfig: IOSPlatformConfig, pemPath: String? = nil) {
+ let pemPath = pemPath ?? Bundle.main.path(forResource: platformConfig.appId, ofType: "pem") ?? ""
+ self.init(platformConfig: platformConfig, pemPath: pemPath, backgroundProcessId: "")
+ }
+
/// Initialize the bridge manager with a custom platform config.
///
/// - Parameters:
/// - platformConfig: The platform config to use to set up the connection to Bridge.
/// - pemPath: The path to the location of the pem file to use when encrypting uploads.
- public init(platformConfig: IOSPlatformConfig, pemPath: String? = nil) {
- let pemPath = pemPath ?? Bundle.main.path(forResource: platformConfig.appId, ofType: "pem")
+ /// - backgroundProcessId: The background process ID registered in the "info.plist" used to allow processing pending uploads in the background.
+ public init(platformConfig: IOSPlatformConfig, pemPath: String, backgroundProcessId: String) {
self.title = platformConfig.localizedAppName
self.platformConfig = platformConfig
@@ -234,6 +261,7 @@ open class UploadAppManager : ObservableObject {
// Create the archive upload processor
self.uploadProcessor = .init(pemPath: pemPath, sharedUserDefaults: self.sharedUserDefaults)
+ self.backgroundProcessId = backgroundProcessId.isEmpty ? nil : backgroundProcessId
// Is this a new login (in which case we need to get the adherence records)
self.isNewLogin = (self.userSessionId == nil)
@@ -257,7 +285,7 @@ open class UploadAppManager : ObservableObject {
// Setup upload processor
Logger.log(severity: .info, message: "Hook up upload processor")
- self.uploadProcessor.uploadManager = uploadManagerV1
+ self.uploadProcessor.uploadManager = uploadManagerV2
// Hook up app config
Logger.log(severity: .info, message: "Hook up app config")
@@ -289,10 +317,16 @@ open class UploadAppManager : ObservableObject {
self.authManager.observeUserSessionInfo()
// Hook up upload observer
+ Logger.log(severity: .info, message: "Hook up upload observer")
self.pendingUploadObserver = PendingUploadObserver() { count in
self.isUploading = (count.intValue > 0)
+ Logger.log(severity: .info, tag: .upload, message: "Pending upload count changed: \(count.intValue)")
}
self.pendingUploadObserver.observePendingUploadCount()
+
+ // Setup the upload manager
+ Logger.log(severity: .info, message: "Setup the upload manager")
+ self.uploadManagerV2.onLaunch(backgroundProcessId: backgroundProcessId)
}
/// **Required:** This method should be called by the app delegate in the implementation of
@@ -377,46 +411,30 @@ open class UploadAppManager : ObservableObject {
// Do nothing. Allows subclass override setup and app state changes.
}
- private let networkMonitoringQueue = DispatchQueue(label: "org.sagebase.NetworkConnectivityMonitor.\(UUID())")
- private var networkMonitor: NWPathMonitor?
+ lazy private var networkMonitor: NetworkMonitor = .init() { [weak self] newStatus in
+ guard let strongSelf = self else { return }
+ Task {
+ await MainActor.run {
+ let oldStatus = strongSelf.networkStatus
+ if newStatus == .connected, oldStatus != .unknown {
+ // only check for uploads if the new status is connected and the previous
+ // status was *not* connected.
+ strongSelf.uploadManagerV2.checkAndRetryUploads()
+ }
+ Logger.log(severity: .info, message: "Network status changed: \(newStatus.stringValue)")
+ strongSelf.networkStatus = newStatus
+ }
+ }
+ }
private func updateNetworkMonitoring() {
// This is not thread protected so it should always be called from the main thread
if isUploading || !userSessionInfo.isAuthenticated {
- startMonitoringNetwork()
+ networkMonitor.startIfNeeded()
}
else {
- stopMonitoringNetwork()
- }
- }
-
- private func stopMonitoringNetwork() {
- networkMonitor?.cancel()
- networkMonitor = nil
- }
-
- private func startMonitoringNetwork() {
- guard networkMonitor == nil else { return }
- let networkMonitor = NWPathMonitor()
- self.networkMonitor = networkMonitor
- networkMonitor.pathUpdateHandler = { [weak self] path in
- guard let strongSelf = self else { return }
- DispatchQueue.main.async {
- switch path.status {
- case .unsatisfied:
- if #available(iOS 14.2, *), path.unsatisfiedReason == .cellularDenied {
- strongSelf.networkStatus = .cellularDenied
- } else {
- strongSelf.networkStatus = .notConnected
- }
- case .satisfied:
- strongSelf.networkStatus = .connected
- default:
- strongSelf.networkStatus = .unknown
- }
- }
+ networkMonitor.cancel()
}
- networkMonitor.start(queue: networkMonitoringQueue)
}
/// Encrypt and upload an archive created with the given builder.
diff --git a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Login/SingleStudyLoginView.swift b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Login/SingleStudyLoginView.swift
index 181090633..5788fab30 100644
--- a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Login/SingleStudyLoginView.swift
+++ b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Login/SingleStudyLoginView.swift
@@ -183,7 +183,7 @@ struct LoginView_Previews: PreviewProvider {
static var previews: some View {
Group {
SingleStudyLoginView()
- .environmentObject(SingleStudyAppManager(appId: kPreviewStudyId))
+ .environmentObject(SingleStudyAppManager(mockType: .preview))
}
}
}
diff --git a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/SingleStudyAppManager.swift b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/SingleStudyAppManager.swift
index dfb709130..829f7cb2d 100644
--- a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/SingleStudyAppManager.swift
+++ b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/SingleStudyAppManager.swift
@@ -39,8 +39,8 @@ open class AbstractSingleStudyAppManager : BridgeClientAppManager {
private var studyManager: NativeStudyManager?
private var notificationManager: NativeScheduledNotificationManager?
- public override init(platformConfig: IOSPlatformConfig, pemPath: String? = nil) {
- super.init(platformConfig: platformConfig, pemPath: pemPath)
+ public override init(platformConfig: IOSPlatformConfig, pemPath: String, backgroundProcessId: String) {
+ super.init(platformConfig: platformConfig, pemPath: pemPath, backgroundProcessId: backgroundProcessId)
let studyId = self.isPreview ? previewStudy.identifier : sharedUserDefaults.string(forKey: kStudyIdKey)
self.study = studyId.map { .init(identifier: $0) }
if self.isPreview {
diff --git a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayFinishedView.swift b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayFinishedView.swift
index 86101fe83..1bd6fbb05 100644
--- a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayFinishedView.swift
+++ b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayFinishedView.swift
@@ -125,6 +125,6 @@ fileprivate struct PreviewAllTodayFinished : View {
struct TodayFinishedView_Previews: PreviewProvider {
static var previews: some View {
PreviewAllTodayFinished()
- .environmentObject(SingleStudyAppManager(appId: kPreviewStudyId))
+ .environmentObject(SingleStudyAppManager(mockType: .preview))
}
}
diff --git a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayView.swift b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayView.swift
index 3fb8064a3..185487fbb 100644
--- a/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayView.swift
+++ b/SwiftPackage/Sources/BridgeClientUI/SingleStudy/Views/TodayView.swift
@@ -166,7 +166,7 @@ extension AnyTransition {
// Used to allow previewing the `TodayView`
fileprivate struct PreviewTodayView : View {
- @StateObject var bridgeManager = SingleStudyAppManager(appId: kPreviewStudyId)
+ @StateObject var bridgeManager = SingleStudyAppManager(mockType: .preview)
@StateObject var viewModel = TodayTimelineViewModel()
var body: some View {
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/MockURLSession.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/MockURLSession.swift
similarity index 98%
rename from SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/MockURLSession.swift
rename to SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/MockURLSession.swift
index 7a68db14c..16c0805df 100644
--- a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/MockURLSession.swift
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/MockURLSession.swift
@@ -11,7 +11,7 @@ class MockHTTPURLResponse : HTTPURLResponse {
}
class MockTask : NSObject, BridgeURLSessionTask {
-
+ var state: URLSessionTask.State = .running
var session: MockURLSession
var request: URLRequest
var mockResponse: MockHTTPURLResponse?
@@ -52,8 +52,10 @@ class MockUploadTask : MockTask, BridgeURLSessionUploadTask {
}
override func resume() {
+ self.state = .running
session.delegateQueue.addOperation { [self] in
(_, mockResponse) = session.dataAndResponse(for: request)
+ self.state = .completed
session.bridgeDelegate?.bridgeUrlSession(session, task: self, didCompleteWithError: nil)
session.remove(mockTask: self)
}
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/BridgeFileUploadManagerTestCase.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/BridgeFileUploadManagerTestCase.swift
similarity index 100%
rename from SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/BridgeFileUploadManagerTestCase.swift
rename to SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/BridgeFileUploadManagerTestCase.swift
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/MockAppManager.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/MockAppManager.swift
similarity index 100%
rename from SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/MockAppManager.swift
rename to SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/MockAppManager.swift
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/ParticipantFileUploadAPITests.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/ParticipantFileUploadAPITests.swift
similarity index 98%
rename from SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/ParticipantFileUploadAPITests.swift
rename to SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/ParticipantFileUploadAPITests.swift
index 4ca0e377f..730b1516a 100644
--- a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/ParticipantFileUploadAPITests.swift
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/ParticipantFileUploadAPITests.swift
@@ -13,7 +13,7 @@ class ParticipantFileUploadAPITests : XCTestCase, BridgeFileUploadManagerTestCas
var uploadRequestSuccessResponseFile: String = "pf-upload-request-success"
var uploadRequestExpiredResponseFile: String = "pf-upload-request-expired"
var mockURLSession: MockURLSession = MockURLSession()
- var mockAppManager: MockBridgeClientAppManager = MockBridgeClientAppManager(appId: "not-a-real-appid")
+ var mockAppManager: MockBridgeClientAppManager = MockBridgeClientAppManager(mockType: .unitTest)
var testFileId: String = "TestFileId"
var uploadApi: BridgeFileUploadAPI {
mockAppManager.uploadManagerV1.participantFileUploadAPI
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/StudyDataUploadAPITests.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/StudyDataUploadAPITests.swift
similarity index 98%
rename from SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/StudyDataUploadAPITests.swift
rename to SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/StudyDataUploadAPITests.swift
index 28d02ad2d..b029ef593 100644
--- a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUploadV1/StudyDataUploadAPITests.swift
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V1/StudyDataUploadAPITests.swift
@@ -13,7 +13,7 @@ class StudyDataUploadAPITests : XCTestCase, BridgeFileUploadManagerTestCaseTyped
var uploadRequestSuccessResponseFile: String = "sd-upload-request-success"
var uploadRequestExpiredResponseFile: String = "sd-upload-request-expired"
var mockURLSession: MockURLSession = MockURLSession()
- var mockAppManager: MockBridgeClientAppManager = MockBridgeClientAppManager(appId: "not-a-real-appid")
+ var mockAppManager: MockBridgeClientAppManager = MockBridgeClientAppManager(mockType: .unitTest)
var testFileId: String = "TestFileId"
var uploadApi: BridgeFileUploadAPI {
mockAppManager.uploadManagerV1.studyDataUploadAPI
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/ArchiveUploadProcessorTests.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/ArchiveUploadProcessorTests.swift
new file mode 100644
index 000000000..cc5e32a34
--- /dev/null
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/ArchiveUploadProcessorTests.swift
@@ -0,0 +1,97 @@
+// Created 9/5/23
+// swift-tools-version:5.0
+
+import XCTest
+@testable import BridgeClientExtension
+import ResultModel
+
+final class ArchiveUploadProcessorTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+
+ // Remove created files in app support directory
+ let baseURL = try FileManager.default.sharedAppSupportDirectory()
+ let url: URL = baseURL.appendingPathComponent("BridgeUploadsV2")
+ try FileManager.default.removeItem(at: url)
+
+ // Remove any unprocessed uploads
+ UserDefaults.standard.removeObject(forKey: "unprocessedUploads")
+ }
+
+ fileprivate func createArchiveUploadProcessor() -> (ArchiveUploadProcessor, Mock) {
+ let mock = Mock()
+ let pemPath = Bundle.module.path(forResource: "sample-study", ofType: "pem")
+ let processor = ArchiveUploadProcessor(pemPath: pemPath, sharedUserDefaults: .init())
+ processor.uploadManager = mock.uploadManager
+ processor.isTestUser = false
+ return (processor, mock)
+ }
+
+ fileprivate func createAssessmentScheduleInfo() -> (Date, AssessmentScheduleInfo) {
+ let now = Date()
+ let schedule = AssessmentScheduleInfo(
+ instanceGuid: "not-a-real-instanceGuid",
+ session: .init(instanceGuid: UUID().uuidString,
+ eventTimestamp: "not-a-real-eventTimestamp",
+ scheduledOn: now.addingTimeInterval(-60*60),
+ assessments: ["not-a-real-guid"]),
+ assessmentInfo: .init(identifier: "not-a-real-assessmentIdentifier",
+ key: nil,
+ guid: "not-a-real-guid",
+ label: nil))
+ return (now, schedule)
+ }
+
+ func testEncryptAndUpload_AssessmentArchiveBuilder() async throws {
+ let (uploadProcessor, mock) = createArchiveUploadProcessor()
+ let (now, schedule) = createAssessmentScheduleInfo()
+ let step = ResultObject(identifier: "step1", startDate: now.addingTimeInterval(-5*60), endDate: now)
+ let result = AssessmentResultObject(identifier: schedule.assessmentIdentifier,
+ startDate: step.startDate,
+ endDate: step.endDate,
+ taskRunUUID: UUID(),
+ stepHistory: [step],
+ path: [.init(identifier: "step1", direction: .forward)])
+ let builder = AssessmentArchiveBuilder(result, schedule: schedule)!
+
+ // -- Method under test
+ await uploadProcessor.asyncEncryptAndUpload(using: builder)
+
+ // This silences warnings about retaining the mock (which can zombie if you don't)
+ XCTAssertEqual(mock.nativeManager.queuedUploadFiles.count, 1)
+ XCTAssertEqual(mock.uploadManager.retryQueue.count, 1)
+
+ // Should not be any markers for unprocessed uploads
+ XCTAssertNil(uploadProcessor.sharedUserDefaults.object(forKey: "unprocessedUploads"))
+
+ // Look for the original encrypted file to have been moved and deleted
+ if let encryptedUrl = builder.archive.encryptedURL {
+ let exists = FileManager.default.fileExists(atPath: encryptedUrl.absoluteString)
+ XCTAssertFalse(exists, "Expecting file \(encryptedUrl) to have been deleted.")
+ } else {
+ XCTFail("The builder encrypted file is missing. Failed to encrypt.")
+ }
+
+ // Look for the file to upload to be staged
+ if let uploadFile = mock.nativeManager.queuedUploadFiles.first {
+ let fullPath = mock.uploadManager.sandboxFileManager.fullyQualifiedPath(of: uploadFile.filePath)
+ let exists = FileManager.default.fileExists(atPath: fullPath)
+ XCTAssertTrue(exists, "Expecting file \(fullPath) to exist.")
+ } else {
+ XCTFail("Failed to queue the encrypted file for upload.")
+ }
+ }
+
+}
+
+fileprivate class Mock {
+ let networkManager: MockBackgroundNetworkManager = .init()
+ let nativeManager: MockNativeUploadManager = .init()
+ lazy var uploadManager: TestUploadManager = .init(backgroundNetworkManager: networkManager,
+ nativeUploadManager: nativeManager)
+}
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/SandboxFileManagerTests.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/SandboxFileManagerTests.swift
new file mode 100644
index 000000000..56f8c1154
--- /dev/null
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/SandboxFileManagerTests.swift
@@ -0,0 +1,58 @@
+// Created 8/1/23
+// swift-tools-version:5.0
+
+import XCTest
+@testable import BridgeClientExtension
+
+fileprivate let simulatorBaseDirectory = "/Users/foo/Library/Developer/CoreSimulator/Devices/ABE156AA-B189-4719-AB02-794DC169A014/data"
+fileprivate let phoneBaseDirectory = "/var/mobile/Containers/Data/Application/258CADCB-F730-4DD4-AB13-4D8B23CDF93D"
+
+final class SandboxFileManagerTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ func createSandboxFileManager(_ simulator: Bool = true) -> SandboxFileManager {
+ let manager = SandboxFileManager()
+ manager.baseDirectory = simulator ? simulatorBaseDirectory : phoneBaseDirectory
+ return manager
+ }
+
+ func testSandBoxRelativePath_Simulator() throws {
+ let manager = createSandboxFileManager()
+ let input = URL(string: "file://\(manager.baseDirectory)/Library/Application%20Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA")!
+ let relativePath = manager.sandboxRelativePath(of: input)
+ let expectedPath = "/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ XCTAssertEqual(expectedPath, relativePath)
+ }
+
+ func testSandBoxRelativePath_Phone() throws {
+ let manager = createSandboxFileManager(false)
+ let input = URL(string: "file://\(manager.baseDirectory)/Library/Application%20Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA")!
+ let relativePath = manager.sandboxRelativePath(of: input)
+ let expectedPath = "/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ XCTAssertEqual(expectedPath, relativePath)
+ }
+
+ func testFullyQualifiedPath_FromRelative_Simulator() throws {
+ let manager = createSandboxFileManager()
+ let input = "/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ let fullPath = manager.fullyQualifiedPath(of: input)
+ let expectedPath = "\(manager.baseDirectory)/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ XCTAssertEqual(expectedPath, fullPath)
+ }
+
+ func testFullyQualifiedPath_FromFullPath_Simulator() throws {
+ let manager = createSandboxFileManager()
+ let input = "\(manager.baseDirectory)/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ let fullPath = manager.fullyQualifiedPath(of: input)
+ let expectedPath = "\(manager.baseDirectory)/Library/Application Support/StudyDataUploads/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+ XCTAssertEqual(expectedPath, fullPath)
+ }
+
+}
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/UploadManagerTests.swift b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/UploadManagerTests.swift
new file mode 100644
index 000000000..2c4a2baee
--- /dev/null
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/FileUpload/V2/UploadManagerTests.swift
@@ -0,0 +1,413 @@
+// Created 8/1/23
+// swift-tools-version:5.0
+
+import XCTest
+import BridgeClient
+import JsonModel
+@testable import BridgeClientExtension
+
+final class UploadManagerTests: XCTestCase {
+
+ override func setUpWithError() throws {
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+ }
+
+ override func tearDownWithError() throws {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ }
+
+ let expectedTempFileDir = "/App Support/BridgeUploadsV2/STUDY_DATA/"
+
+ fileprivate func createUploadManager() -> (manager: TestUploadManager, mock: Mock) {
+ let mock = Mock()
+ let manager = TestUploadManager(
+ backgroundNetworkManager: mock.networkManager,
+ sandboxFileManager: mock.sandboxFileManager,
+ nativeUploadManager: mock.nativeManager)
+ let fakePath = "\(fakeBaseDirectory)/App Support/\(manager.subdir)"
+ manager.uploadDirURL = URL(fileURLWithPath: fakePath, isDirectory: true)
+
+ XCTAssertEqual(1, mock.networkManager.registeredHandlers.count)
+ XCTAssertEqual(manager, mock.networkManager.registeredHandlers.first as? UploadManager)
+
+ return (manager, mock)
+ }
+
+ // MARK: Upload study data archive
+
+ func testUploadArchive_Offline() async {
+ let (manager, mock) = createUploadManager()
+ let (fileURL, schedule, startedOn) = mock.sandboxFileManager.setupFakeArchive()
+
+ // -- Method under test
+ let success = await manager.uploadEncryptedArchive(fileUrl: fileURL, schedule: schedule, startedOn: startedOn)
+ XCTAssertTrue(success)
+
+ // independent of network connectivity, the file should be copied and queued for upload
+ XCTAssertEqual(mock.nativeManager.queuedUploadFiles.count, 1)
+ guard let uploadFile = mock.nativeManager.queuedUploadFiles.first else {
+ XCTFail("Unexpected NULL")
+ return
+ }
+ XCTAssertEqual(fileURL, mock.sandboxFileManager.copyFileCalls[uploadFile.filePath])
+ checkUploadFileExpectations(uploadFile)
+
+ // and b/c the native upload manager failed to return an S3UploadSession, it should be
+ // queued to retry upload
+ XCTAssertEqual([uploadFile.filePath : false], manager.retryQueue)
+ }
+
+ func testUploadArchive_Online() async throws {
+ let (manager, mock) = createUploadManager()
+ let (fileURL, schedule, startedOn) = mock.sandboxFileManager.setupFakeArchive()
+ let requestHeaders = ["foo" : "magoo"]
+ mock.nativeManager.queuedS3UploadSession = .init(
+ filePath: "ignored",
+ contentType: "application/zip",
+ uploadSessionId: "not-a-real-uploadSessionId-0",
+ url: "https://not-a-real-org.org/not-a-real-url",
+ requestHeaders: requestHeaders)
+
+ // -- Method under test
+ let success = await manager.uploadEncryptedArchive(fileUrl: fileURL, schedule: schedule, startedOn: startedOn)
+ XCTAssertTrue(success)
+
+ // Independent of network connectivity, the file should be copied and queued for upload
+ XCTAssertEqual(mock.nativeManager.queuedUploadFiles.count, 1)
+ guard let uploadFile = mock.nativeManager.queuedUploadFiles.first else {
+ XCTFail("Unexpected NULL")
+ return
+ }
+ XCTAssertEqual(fileURL, mock.sandboxFileManager.copyFileCalls[uploadFile.filePath])
+ checkUploadFileExpectations(uploadFile)
+
+ // If successful in requesting an S3UploadSession then the upload manager should
+ // call the network manager with that request.
+ XCTAssertEqual(mock.networkManager.uploadRequests.count, 1)
+ XCTAssertEqual(1, mock.sandboxFileManager.copyTempFiles.count)
+ guard let uploadTask = mock.networkManager.uploadRequests.first,
+ let tempFile = mock.sandboxFileManager.copyTempFiles.first
+ else {
+ XCTFail("Unexpected NULL")
+ return
+ }
+
+ let expectedIdentifier = "not-a-real-uploadSessionId-0|\(uploadFile.filePath)"
+ XCTAssertEqual(expectedIdentifier, uploadTask.taskDescription)
+ XCTAssertEqual(requestHeaders, uploadTask.httpHeaders)
+ XCTAssertEqual(tempFile, uploadTask.fileURL)
+ XCTAssertEqual("https://not-a-real-org.org/not-a-real-url", uploadTask.urlString)
+
+ // Check if the manager can handle the upload response
+ XCTAssertTrue(manager.canHandle(task: uploadTask))
+ }
+
+ func checkUploadFileExpectations(_ uploadFile: UploadFile ) {
+ XCTAssertEqual("application/zip", uploadFile.contentType)
+ XCTAssertEqual(1024, uploadFile.fileLength)
+ XCTAssertEqual("not-a-real-md5Hash", uploadFile.md5Hash)
+ XCTAssertTrue(uploadFile.encrypted)
+ XCTAssertEqual(.studyData, uploadFile.s3UploadType)
+ XCTAssertNotNil(uploadFile.metadata)
+ XCTAssertEqual("not-a-real-instanceGuid", uploadFile.metadata?.instanceGuid)
+ XCTAssertEqual("not-a-real-eventTimestamp", uploadFile.metadata?.eventTimestamp)
+ XCTAssertNotNil(uploadFile.metadata?.startedOn)
+ XCTAssertTrue(uploadFile.filePath.hasPrefix(expectedTempFileDir), "\(uploadFile.filePath) does not have expected prefix")
+ }
+
+ // MARK: Upload S3 responses
+
+ fileprivate func createS3UploadTask(_ manager: TestUploadManager, _ mock: Mock) -> (filePath: String, uploadId: String, uploadTask: MockBackgroundNetworkManager.UploadRequest) {
+ let filePath = "\(expectedTempFileDir)not-a-real-file"
+ let uploadId = "not-a-real-uploadSessionId-0"
+ let uploadTask = MockBackgroundNetworkManager.UploadRequest(
+ fileURL: mock.sandboxFileManager.fileURL(of: filePath),
+ httpHeaders: ["foo" : "magoo"],
+ urlString: "https://not-a-real-org.org/\(uploadId)/not-a-real-url",
+ taskDescription: manager.urlUploadTaskIdentifier(filePath: filePath, uploadSessionId: uploadId))
+
+ // check test setup assumptions
+ XCTAssertTrue(manager.canHandle(task: uploadTask))
+
+ return (filePath, uploadId, uploadTask)
+ }
+
+ func testS3UploadResponse_HappyPath() async throws {
+ let (manager, mock) = createUploadManager()
+ let (filePath, _, uploadTask) = createS3UploadTask(manager, mock)
+
+ // for the happy path, the S3 response code is 200
+ uploadTask.setResponse(statusCode: 200)
+ // and the native upload was successful
+ mock.nativeManager.uploadFinishedResponse[filePath] = true
+
+ // -- Method under test
+
+ // Once the S3 upload is complete, the shared network manager will call back to the upload manager.
+ // This should fire a call to the native upload manager (Kotlin) that will then attempt to mark the
+ // file as done.
+ try await manager.urlSession(task: uploadTask, didCompleteWithError: nil)
+
+ // Check that the file was marked as uploaded
+ XCTAssertEqual([filePath], mock.nativeManager.uploadFinished)
+ // Check for file deleted
+ XCTAssertEqual([filePath], mock.sandboxFileManager.removeTempFiles)
+ // Check that the file was *not* queued for retry
+ XCTAssertEqual([:], manager.retryQueue)
+ }
+
+ func checkRetryS3Failure(statusCode: Int) async throws {
+ let (manager, mock) = createUploadManager()
+ let (filePath, _, uploadTask) = createS3UploadTask(manager, mock)
+
+ uploadTask.setResponse(statusCode: statusCode)
+
+ // -- Methods under test
+
+ // Once the S3 upload is complete, the shared network manager will call back to the upload manager.
+ // This should fire a call to the native upload manager (Kotlin) that will then attempt to mark the
+ // file as done.
+ try await manager.urlSession(task: uploadTask, didCompleteWithError: nil)
+
+ // check that the file was not marked for upload
+ XCTAssertEqual([], mock.nativeManager.uploadFinished)
+ // and that the file was not removed
+ XCTAssertEqual([], mock.sandboxFileManager.removeTempFiles)
+ // but it is marked for retry
+ XCTAssertEqual([filePath:false], manager.retryQueue)
+ }
+
+ func testS3UploadResponse_403() async throws {
+ try await checkRetryS3Failure(statusCode: 403)
+ }
+
+ func testS3UploadResponse_409() async throws {
+ try await checkRetryS3Failure(statusCode: 409)
+ }
+
+ func testS3UploadResponse_500() async throws {
+ try await checkRetryS3Failure(statusCode: 500)
+ }
+
+ func testS3UploadResponse_503() async throws {
+ try await checkRetryS3Failure(statusCode: 503)
+ }
+
+ // MARK: Sanity checks of Kotlin framework
+
+ func testKotlinDataClassEquality() {
+ // Check to make sure that data classes are properly handling equality and hash value.
+ let obj1 = PendingUploadFile(filePath: "/foo/goo/laroo", uploadSessionId: "1234567890")
+ let obj2 = PendingUploadFile(filePath: "/foo/goo/laroo", uploadSessionId: "1234567890")
+ XCTAssertEqual(obj1, obj2)
+ XCTAssertEqual(obj1.hashValue, obj2.hashValue)
+ }
+
+}
+
+fileprivate let fakeBaseDirectory = "/Foo/48B0BAEB-94C4-4158-953C-74AB0935E5FA"
+
+class TestUploadManager : UploadManager {
+ var retryQueue: [String : Bool] = [:]
+
+ override func enqueueForRetry(filePath: String, uploaded: Bool) async {
+ // Do not call through to the sync manager
+ retryQueue[filePath] = uploaded
+ }
+}
+
+/// Used to hold onto the mocks so they aren't prematurely released
+fileprivate class Mock {
+ let networkManager: MockBackgroundNetworkManager = .init()
+ let sandboxFileManager: MockSandboxFileManager = .init()
+ let nativeManager: MockNativeUploadManager = .init()
+}
+
+class MockSandboxFileManager : SandboxFileManager {
+
+ var removeTempFiles: [String] = []
+ var copyTempFiles: [URL] = []
+ var copyFileCalls: [String : URL] = [:]
+ var touchCalls: [URL : Date] = [:]
+ var contentLengthMap: [URL : Int] = [:]
+ var md5Map: [URL : String] = [:]
+
+ override init() {
+ super.init()
+ self.baseDirectory = fakeBaseDirectory
+ }
+
+ override func copyFile(from fileURL: URL, to tempFileURL: URL) -> Bool {
+ let invariantPath = self.sandboxRelativePath(of: tempFileURL)
+ copyTempFiles.append(tempFileURL)
+ copyFileCalls[invariantPath] = fileURL
+ return true
+ }
+
+ override func removeTempFile(filePath: String) {
+ removeTempFiles.append(filePath)
+ }
+
+ override func touch(fileUrl: URL) {
+ touchCalls[fileUrl] = Date()
+ }
+
+ override func fileContentLength(_ fileUrl: URL) -> Int? {
+ contentLengthMap[fileUrl] ?? copyFileCalls[sandboxRelativePath(of: fileUrl)].flatMap { contentLengthMap[$0] }
+ }
+
+ override func fileMD5String(_ fileUrl: URL) -> String? {
+ md5Map[fileUrl] ?? copyFileCalls[sandboxRelativePath(of: fileUrl)].flatMap { md5Map[$0] }
+ }
+
+ override func createSubdirectory(baseURL: URL, subdir: String) throws -> URL {
+ baseURL.appendingPathComponent(subdir, isDirectory: true)
+ }
+
+ func setupFakeArchive() -> (URL, AssessmentScheduleInfo, Date) {
+ let now = Date()
+ let schedule = AssessmentScheduleInfo(
+ instanceGuid: "not-a-real-instanceGuid",
+ session: .init(instanceGuid: UUID().uuidString,
+ eventTimestamp: "not-a-real-eventTimestamp",
+ scheduledOn: now.addingTimeInterval(-60*60),
+ assessments: ["not-a-real-guid"]),
+ assessmentInfo: .init(identifier: "not-a-real-assessmentIdentifier",
+ key: nil,
+ guid: "not-a-real-guid",
+ label: nil))
+ let filename = UUID().uuidString
+ let fileURL = URL(fileURLWithPath: "\(fakeBaseDirectory)/tmp/\(filename).zip.encypted")
+ let startedOn = now.addingTimeInterval(-5*60)
+ contentLengthMap[fileURL] = 1024
+ md5Map[fileURL] = "not-a-real-md5Hash"
+ return (fileURL, schedule, startedOn)
+ }
+
+}
+
+class MockBackgroundNetworkManager : NSObject, SharedBackgroundUploadManager {
+
+ let mockURLSession: MockURLSession = MockURLSession()
+
+ let sessionDelegateQueue: OperationQueue = {
+ let delegateQueue = OperationQueue()
+ delegateQueue.maxConcurrentOperationCount = 1
+ return delegateQueue
+ }()
+
+ var isAppBackground: Bool = false
+
+ var registeredHandlers: [BridgeURLSessionHandler] = []
+ var uploadRequests: [UploadRequest] = []
+ var tasks: [BridgeURLSessionTask] = []
+
+ func registerBackgroundTransferHandler(_ handler: BridgeURLSessionHandler) {
+ registeredHandlers.append(handler)
+ }
+
+ @discardableResult
+ func uploadFile(_ fileURL: URL, httpHeaders: [String : String]?, to urlString: String, taskDescription: String) -> Bool {
+ let request: UploadRequest = .init(fileURL: fileURL, httpHeaders: httpHeaders, urlString: urlString, taskDescription: taskDescription)
+ uploadRequests.append(request)
+ return true
+ }
+
+ class UploadRequest : NSObject, BridgeURLSessionUploadTask {
+ let fileURL: URL
+ let httpHeaders: [String : String]?
+ let urlString: String
+ var taskDescription: String?
+ var state: URLSessionTask.State = .running
+
+ init(fileURL: URL, httpHeaders: [String : String]?, urlString: String, taskDescription: String) {
+ self.fileURL = fileURL
+ self.httpHeaders = httpHeaders
+ self.urlString = urlString
+ self.taskDescription = taskDescription
+ self.originalRequest = URL(string: urlString).map {
+ var request = URLRequest(url: $0)
+ request.allHTTPHeaderFields = httpHeaders
+ request.httpMethod = HTTPMethod.PUT.rawValue
+ return request
+ }
+ }
+
+ var taskType: BridgeClientExtension.BridgeURLSessionTaskType {
+ .upload
+ }
+
+ var originalRequest: URLRequest?
+ var response: URLResponse?
+
+ var didCallResume: Bool = false
+ var didCallCancel: Bool = false
+
+ func setResponse(statusCode: Int) {
+ self.response = HTTPURLResponse(url: URL(string: urlString)!, statusCode: statusCode, httpVersion: nil, headerFields: nil)
+ }
+
+ func resume() {
+ didCallResume = true
+ }
+
+ func cancel() {
+ didCallCancel = true
+ }
+ }
+
+ func getAllTasks() async -> [BridgeURLSessionTask] {
+ tasks
+ }
+}
+
+class MockNativeUploadManager : NSObject, BridgeClientUploadManager {
+
+ var queuedUploadFiles: [UploadFile] = []
+ var queuedS3UploadSession: S3UploadSession?
+
+ var uploadFiles: [String] = []
+ var s3Uploads: [String : S3UploadSession] = [:]
+ var uploadFinished: [String] = []
+ var uploadFailedPermanently: [String] = []
+ var uploadFinishedResponse: [String : Bool] = [:]
+ var processFinishedUploadsResponse: Bool = false
+
+ func getPendingUploadFiles() async -> [PendingUploadFile] {
+ uploadFiles.map {
+ .init(filePath: $0, uploadSessionId: s3Uploads[$0]?.uploadSessionId)
+ }
+ }
+
+ func hasPendingUploads() -> Bool {
+ uploadFiles.count > 0 || uploadFinishedResponse.count > 0
+ }
+
+ func queueAndRequestUploadSession(uploadFile: UploadFile) async -> S3UploadSession? {
+ queuedUploadFiles.append(uploadFile)
+ return queuedS3UploadSession.map {
+ .init(filePath: uploadFile.filePath,
+ contentType: $0.contentType,
+ uploadSessionId: $0.uploadSessionId,
+ url: $0.url,
+ requestHeaders: $0.requestHeaders)
+ }
+ }
+
+ func requestUploadSession(filePath: String) async -> S3UploadSession? {
+ return s3Uploads[filePath]
+ }
+
+ func markUploadFileFinished(filePath: String, uploadSessionId: String) async -> Bool {
+ uploadFinished.append(filePath)
+ return uploadFinishedResponse[filePath] ?? false
+ }
+
+ func markUploadUnrecoverableFailure(filePath: String) {
+ uploadFailedPermanently.append(filePath)
+ }
+
+ func processFinishedUploads() async -> Bool {
+ return processFinishedUploadsResponse
+ }
+}
diff --git a/SwiftPackage/Tests/BridgeClientExtensionTests/Resources/sample-study.pem b/SwiftPackage/Tests/BridgeClientExtensionTests/Resources/sample-study.pem
new file mode 100644
index 000000000..c0762358e
--- /dev/null
+++ b/SwiftPackage/Tests/BridgeClientExtensionTests/Resources/sample-study.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIID/TCCAuWgAwIBAgIUDOpoLAbiQpTp226smOV0Lsahq0kwDQYJKoZIhvcNAQEL
+BQAwgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApXYXNoaW5ndG9uMRAwDgYDVQQH
+DAdTZWF0dGxlMRUwEwYDVQQKDAxGb28gVGVzdCBPcmcxDDAKBgNVBAsMA0dvbzEV
+MBMGA1UEAwwMRm9vIFRlc3QgT3JnMRswGQYJKoZIhvcNAQkBFgxmb29Ac3BhbS5u
+ZXQwHhcNMjMwOTA3MDIyNjQxWhcNMzMwOTA0MDIyNjQxWjCBjTELMAkGA1UEBhMC
+VVMxEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFTATBgNV
+BAoMDEZvbyBUZXN0IE9yZzEMMAoGA1UECwwDR29vMRUwEwYDVQQDDAxGb28gVGVz
+dCBPcmcxGzAZBgkqhkiG9w0BCQEWDGZvb0BzcGFtLm5ldDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAMYrZcghjv9HABNoLw0zTUSa5wfcfwYPVwCUUjTr
+j5C9TugTdyfVvSdy0DRWmGaQMNcEn1BkjeOT9gu+/Edf1ySl6CLjwRUODy4HduvJ
+PXoUGkNrOfRJ1zWQ6zeC1VukZG0E5/PdGDd4o12+XT1UQttXjlUgksUl3wH0gcYX
+0yU0rK8Eg46pr/Vfl31LhOWCotRHVVGDUDBmyS0gbhl2MeCi5cid75UHpyA+d2oc
+Z1xNHGg67RsaDfdHlIPXWVcjYfpj507l3he0tLGR2d2oluMKF87YISxPYkmViOAA
+SUspip/eGEKwjdachik6mFw5C/Iqzww/GnUHkkNujKhwxq8CAwEAAaNTMFEwHQYD
+VR0OBBYEFLq7UrPB4LAuNBi7fUAFVoyrOH/+MB8GA1UdIwQYMBaAFLq7UrPB4LAu
+NBi7fUAFVoyrOH/+MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AJUAU4xY9Sx+1cIQ38jAi75RyWB9GQMzaESW+XwJZl99eEO0qtRpqbqQbOVcRkZY
+nNYurVVj88TTaxbjf3MmXFnioSQ1RwJA6EqzZ0EmnrkkeQ+2sz8FLe5lNvr6L5aI
+HQIBSA3OHasj43/gOvxHTnEriMh9PUIrRF5d44uaCsiJlhUxKlPVnEjKSW8lTzRb
+0bEsIyniQtU2vRyAQszLmLn6Y/Jovligd7kBt9QKVE4Ead8OrQxD9ql3yOBws25W
+MJexrQq9BRIRgYIJl5F6cX3mLKV4hs4bNZ51oQwt1UMrtNBTS/qNqcKwxUN2K2+K
+gqQRZuvx+xDkMuvD/K/PE1c=
+-----END CERTIFICATE-----
diff --git a/SwiftPackage/Tests/BridgeClientUITests/TodayTimelineViewModelTests.swift b/SwiftPackage/Tests/BridgeClientUITests/TodayTimelineViewModelTests.swift
index b3f04fd04..8b5e9e35f 100644
--- a/SwiftPackage/Tests/BridgeClientUITests/TodayTimelineViewModelTests.swift
+++ b/SwiftPackage/Tests/BridgeClientUITests/TodayTimelineViewModelTests.swift
@@ -15,7 +15,7 @@ final class TodayTimelineViewModelTests: XCTestCase {
return
}
- let bridgeManager = SingleStudyAppManager(appId: kPreviewStudyId)
+ let bridgeManager = SingleStudyAppManager(mockType: .preview)
let todayManager = TodayTimelineViewModel()
todayManager.onAppear(bridgeManager: bridgeManager, previewSchedules: previewSchedulesA)
@@ -42,7 +42,7 @@ final class TodayTimelineViewModelTests: XCTestCase {
return
}
- let bridgeManager = SingleStudyAppManager(appId: kPreviewStudyId)
+ let bridgeManager = SingleStudyAppManager(mockType: .preview)
let todayManager = TodayTimelineViewModel()
todayManager.onAppear(bridgeManager: bridgeManager, previewSchedules: previewSchedulesA)
@@ -139,7 +139,7 @@ final class TodayTimelineViewModelTests: XCTestCase {
return
}
- let bridgeManager = SingleStudyAppManager(appId: kPreviewStudyId)
+ let bridgeManager = SingleStudyAppManager(mockType: .preview)
let todayManager = TodayTimelineViewModel()
todayManager.onAppear(bridgeManager: bridgeManager, previewSchedules: previewSchedulesA)
diff --git a/bridge-client/src/androidMain/kotlin/org/sagebionetworks/bridge/kmm/shared/upload/UploadManager.kt b/bridge-client/src/androidMain/kotlin/org/sagebionetworks/bridge/kmm/shared/upload/UploadManager.kt
index 325c3d3f6..2f01bd85b 100644
--- a/bridge-client/src/androidMain/kotlin/org/sagebionetworks/bridge/kmm/shared/upload/UploadManager.kt
+++ b/bridge-client/src/androidMain/kotlin/org/sagebionetworks/bridge/kmm/shared/upload/UploadManager.kt
@@ -69,7 +69,7 @@ internal class UploadManager(
Logger.d("uploadingToS3 $uploadFile")
s3UploadApi.uploadFile(uploadSession.url, uploadFile) //TODO: Handle network exceptions -nbrown 4/26/21
FileSystem.SYSTEM.delete(uploadFile.filePath.toPath()) //TODO: Handle delete failure -nbrown 12/16/20
- markUploadFileFinished(uploadFile)
+ markUploadFileFinished(uploadFile, uploadSession.id)
} catch (error: Throwable) {
Logger.e("Error uploadingToS3 $uploadFile", error)
diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/cache/ResourceType.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/cache/ResourceType.kt
index 076921e97..094eae8e8 100644
--- a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/cache/ResourceType.kt
+++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/cache/ResourceType.kt
@@ -16,7 +16,8 @@ enum class ResourceType {
ADHERENCE_RECORD,
STUDY,
STUDY_INFO,
- PARTICIPANT_REPORT
+ PARTICIPANT_REPORT,
+ UPLOADED_FILE_RECORD,
}
enum class ResourceStatus {
diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadFile.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadFile.kt
index 2b0581d20..dd106863e 100644
--- a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadFile.kt
+++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadFile.kt
@@ -62,6 +62,10 @@ internal fun UploadFileIdentifiable.getUploadSessionResourceId(): String {
return "uploadSession--$filePath"
}
+internal fun UploadFileIdentifiable.getUploadedFileRecordResourceId(): String {
+ return "uploadedFileRecord--$filePath"
+}
+
internal class UploadFileId(override val filePath: String) : UploadFileIdentifiable
enum class S3UploadType {
diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadMetadata.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadMetadata.kt
index f18fe7f51..b2151da75 100644
--- a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadMetadata.kt
+++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadMetadata.kt
@@ -11,11 +11,6 @@ data class UploadMetadata(
val eventTimestamp: String? = null,
val startedOn: String? = null,
) {
-
- fun getId(): String? {
- return if (instanceGuid == null) null else "$instanceGuid|$eventTimestamp|$startedOn"
- }
-
internal fun toJsonMap(): Map {
val jsonCoder = Json {
ignoreUnknownKeys = true
@@ -24,12 +19,4 @@ data class UploadMetadata(
}
return jsonCoder.encodeToJsonElement(UploadMetadata.serializer(),this).jsonObject.toMap()
}
-
- /**
- * Used on iOS to encode metadata.
- * (`JsonElement` does not implement the Swift Codable protocol - syoung 07/10/2023)
- */
- fun toStringMap(): Map {
- return toJsonMap().mapValues { it.toString() }
- }
}
\ No newline at end of file
diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadedFileRecord.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadedFileRecord.kt
new file mode 100644
index 000000000..e5144a83a
--- /dev/null
+++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/models/UploadedFileRecord.kt
@@ -0,0 +1,13 @@
+package org.sagebionetworks.bridge.kmm.shared.models
+
+import kotlinx.datetime.Instant
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UploadedFileRecord(
+ override val filePath: String,
+ val uploadTimestamp: Instant,
+ val uploadSessionId: String? = null,
+ val metadata: UploadMetadata? = null,
+) : UploadFileIdentifiable
+
diff --git a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepo.kt b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepo.kt
index 371ec61a0..e8e188526 100644
--- a/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepo.kt
+++ b/bridge-client/src/commonMain/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepo.kt
@@ -3,7 +3,6 @@ package org.sagebionetworks.bridge.kmm.shared.repo
import co.touchlab.kermit.Logger
import co.touchlab.stately.ensureNeverFrozen
import io.ktor.client.HttpClient
-import io.ktor.http.headers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
@@ -14,7 +13,9 @@ import kotlinx.datetime.plus
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.sagebionetworks.bridge.kmm.shared.apis.UploadsApi
+import org.sagebionetworks.bridge.kmm.shared.cache.Resource
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceDatabaseHelper
+import org.sagebionetworks.bridge.kmm.shared.cache.ResourceStatus
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceType
import org.sagebionetworks.bridge.kmm.shared.cache.loadResource
import org.sagebionetworks.bridge.kmm.shared.models.S3UploadSession
@@ -23,8 +24,10 @@ import org.sagebionetworks.bridge.kmm.shared.models.UploadFileId
import org.sagebionetworks.bridge.kmm.shared.models.UploadFileIdentifiable
import org.sagebionetworks.bridge.kmm.shared.models.UploadRequest
import org.sagebionetworks.bridge.kmm.shared.models.UploadSession
+import org.sagebionetworks.bridge.kmm.shared.models.UploadedFileRecord
import org.sagebionetworks.bridge.kmm.shared.models.getUploadFileResourceId
import org.sagebionetworks.bridge.kmm.shared.models.getUploadSessionResourceId
+import org.sagebionetworks.bridge.kmm.shared.models.getUploadedFileRecordResourceId
internal open class UploadRepo(
httpClient: HttpClient,
@@ -71,6 +74,18 @@ internal open class UploadRepo(
return resources.mapNotNull { it.loadResource() }
}
+ /**
+ * Get a tuple of the filepath and upload session for all pending uploads.
+ */
+ fun getPendingUploadFiles(): List {
+ return getUploadFiles().map {
+ PendingUploadFile(
+ it.filePath,
+ getCachedUploadSession(it)?.id
+ )
+ }
+ }
+
/**
* Remove the [UploadFile] from the local cache and mark the upload session as "completed"
* by setting `needSave = true`. This is called after successfully uploading to S3.
@@ -80,15 +95,60 @@ internal open class UploadRepo(
* complicated - the file location can change with app or OS updates and synchronization is
* a bit brittle. It's less confusing to handle this in Swift. syoung 07/14/2023
*/
- fun markUploadFileFinished(uploadFile: UploadFileIdentifiable) {
+ fun markUploadFileFinished(uploadFile: UploadFileIdentifiable, uploadSessionId: String?) {
val resourceId = uploadFile.getUploadSessionResourceId()
- val resource = database.getResource(resourceId, ResourceType.UPLOAD_SESSION,
+ val uploadSessionResource = database.getResource(resourceId, ResourceType.UPLOAD_SESSION,
ResourceDatabaseHelper.APP_WIDE_STUDY_ID
)?.copy(needSave = true)
+ val uploadedFileRecord = UploadedFileRecord(
+ filePath = uploadFile.filePath,
+ uploadTimestamp = Clock.System.now(),
+ uploadSessionId = uploadSessionId,
+ metadata = getUploadFile(uploadFile.filePath)?.metadata
+ )
database.database.transaction {
- resource?.let {
+ // Mark the upload session as "completed" by updating with `needSave = true`
+ uploadSessionResource?.let {
database.insertUpdateResource(it)
}
+ // Remove the upload file that is queued for upload to S3
+ database.removeResource(
+ uploadFile.getUploadFileResourceId(),
+ ResourceType.FILE_UPLOAD,
+ ResourceDatabaseHelper.APP_WIDE_STUDY_ID
+ )
+ // Insert a record with the information for the uploaded file record
+ database.insertUpdateResource(
+ Resource(
+ identifier = uploadedFileRecord.filePath,
+ type = ResourceType.UPLOADED_FILE_RECORD,
+ secondaryId = uploadedFileRecord.metadata?.instanceGuid ?: ResourceDatabaseHelper.DEFAULT_SECONDARY_ID,
+ studyId = ResourceDatabaseHelper.APP_WIDE_STUDY_ID,
+ json = Json.encodeToString(uploadedFileRecord),
+ lastUpdate = uploadedFileRecord.uploadTimestamp.toEpochMilliseconds(),
+ status = ResourceStatus.SUCCESS,
+ needSave = false
+ )
+ )
+ }
+ }
+
+ /**
+ * Remove the [UploadFile] from the local cache. This is called after an unrecoverable failure
+ * to upload to S3.
+ *
+ * Note: Deleting the file must be handled natively as a part of S3 upload rather than using
+ * expect/actually. This is done b/c on iOS coordinating file read/write is much more
+ * complicated - the file location can change with app or OS updates and synchronization is
+ * a bit brittle. It's less confusing to handle this in Swift. syoung 07/14/2023
+ */
+ fun removeUploadFile(uploadFile: UploadFileIdentifiable) {
+ database.database.transaction {
+ database.removeResource(
+ uploadFile.getUploadSessionResourceId(),
+ ResourceType.UPLOAD_SESSION,
+ ResourceDatabaseHelper.APP_WIDE_STUDY_ID
+ )
database.removeResource(
uploadFile.getUploadFileResourceId(),
ResourceType.FILE_UPLOAD,
@@ -109,6 +169,18 @@ internal open class UploadRepo(
).firstOrNull()?.loadResource()
}
+ /**
+ * If the file has been uploaded, then this will get the record associated with
+ * marking that file as "done".
+ */
+ fun getUploadedFileRecord(filePath: String): UploadedFileRecord? {
+ return database.getResourcesById(
+ filePath,
+ ResourceType.UPLOADED_FILE_RECORD,
+ ResourceDatabaseHelper.APP_WIDE_STUDY_ID
+ ).firstOrNull()?.loadResource()
+ }
+
/**
* Add the given upload file to the queue and request the S3 upload session.
*/
@@ -126,6 +198,9 @@ internal open class UploadRepo(
return getUploadFile(filePath)?.let { getS3UploadSession(it) }
}
+ /**
+ * Get the information needed to spawn an S3 upload.
+ */
suspend fun getS3UploadSession(uploadFile: UploadFile): S3UploadSession? {
val uploadSession = getUploadSession(uploadFile)
return if (uploadSession?.id == null) null
@@ -138,40 +213,48 @@ internal open class UploadRepo(
)
}
+ /**
+ * Retrieves cached UploadSession (if one exists). This method is responsible for ensuring
+ * that upload session has not expired and is valid.
+ */
+ fun getCachedUploadSession(uploadFile: UploadFileIdentifiable): UploadSession? {
+ val uploadSession = database.getResource(
+ uploadFile.getUploadSessionResourceId(),
+ ResourceType.UPLOAD_SESSION,
+ ResourceDatabaseHelper.APP_WIDE_STUDY_ID
+ )?.loadResource()
+ return if (isValid(uploadSession)) uploadSession else null
+ }
+
+ /**
+ * Is the upload session still valid (or has it expired)?
+ */
+ private fun isValid(uploadSession: UploadSession?): Boolean {
+ if (uploadSession?.expires == null) return true
+ val desiredMinimumExpiration = Clock.System.now().plus(
+ UPLOAD_EXPIRY_WINDOW_MINUTES,
+ DateTimeUnit.MINUTE,
+ TimeZone.currentSystemDefault()
+ )
+ val expires = Instant.parse(uploadSession.expires)
+ return (desiredMinimumExpiration < expires)
+ }
+
/**
* Retrieves cached UploadSession (if one exists), else requests one from Bridge and caches session
* before returning it. This method is responsible for ensuring that upload session has not expired and is valid.
*/
suspend fun getUploadSession(uploadFile: UploadFile): UploadSession? {
- val identifier = uploadFile.getUploadSessionResourceId()
- val resource = database.getResource(identifier, ResourceType.UPLOAD_SESSION,
- ResourceDatabaseHelper.APP_WIDE_STUDY_ID
- )
- resource?.let {
- val uploadSession = resource.loadResource()
- uploadSession?.expires?.let {
- val desiredMinimumExpiration = Clock.System.now().plus(
- UPLOAD_EXPIRY_WINDOW_MINUTES,
- DateTimeUnit.MINUTE,
- TimeZone.currentSystemDefault()
- )
- val expires = Instant.parse(uploadSession.expires)
- if (desiredMinimumExpiration < expires) {
- //return cached upload session
- return uploadSession
- }
- }
- }
- val updatedResource = remoteLoadResource(
+ return getCachedUploadSession(uploadFile) ?:
+ remoteLoadResource(
database = database,
- identifier = identifier,
+ identifier = uploadFile.getUploadSessionResourceId(),
resourceType = ResourceType.UPLOAD_SESSION,
studyId = ResourceDatabaseHelper.APP_WIDE_STUDY_ID,
curResource = null,
remoteLoad = {loadRemoteUploadSession(uploadFile.getUploadRequest())}
- )
+ ).loadResource()
//TODO: Need to think through error handling if we are unable to get an UploadSession -nbrown 12/16/20
- return updatedResource.loadResource()
}
private suspend fun loadRemoteUploadSession(uploadRequest: UploadRequest): String {
@@ -210,3 +293,9 @@ internal open class UploadRepo(
}
}
}
+
+data class PendingUploadFile(
+ val filePath: String,
+ val uploadSessionId: String?
+)
+
diff --git a/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepoTest.kt b/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepoTest.kt
index a7a0933a9..99b99e073 100644
--- a/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepoTest.kt
+++ b/bridge-client/src/commonTest/kotlin/org/sagebionetworks/bridge/kmm/shared/repo/UploadRepoTest.kt
@@ -14,6 +14,8 @@ import kotlinx.datetime.minus
import kotlinx.datetime.plus
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.JsonElement
+import kotlinx.serialization.json.JsonPrimitive
import org.sagebionetworks.bridge.kmm.shared.BaseTest
import org.sagebionetworks.bridge.kmm.shared.cache.Resource
import org.sagebionetworks.bridge.kmm.shared.cache.ResourceDatabaseHelper
@@ -25,6 +27,7 @@ import org.sagebionetworks.bridge.kmm.shared.models.S3UploadSession
import org.sagebionetworks.bridge.kmm.shared.models.UploadFile
import org.sagebionetworks.bridge.kmm.shared.models.UploadFileId
import org.sagebionetworks.bridge.kmm.shared.models.UploadFileIdentifiable
+import org.sagebionetworks.bridge.kmm.shared.models.UploadMetadata
import org.sagebionetworks.bridge.kmm.shared.models.UploadSession
import org.sagebionetworks.bridge.kmm.shared.models.UploadStatus
import org.sagebionetworks.bridge.kmm.shared.models.UploadValidationStatus
@@ -53,10 +56,10 @@ class UploadRepoTest : BaseTest() {
fun mockUploadFile(): UploadFile {
return UploadFile(
- randomUUID(),
- "application/zip",
- 1024,
- randomUUID(),
+ filePath = randomUUID(),
+ contentType = "application/zip",
+ fileLength = 1024,
+ md5Hash = randomUUID(),
)
}
@@ -139,7 +142,7 @@ class UploadRepoTest : BaseTest() {
assertEquals(1L, initialCount)
// Pretend to upload the S3 file by marking it as finished
- repo.markUploadFileFinished(UploadFileId(uploadFile.filePath))
+ repo.markUploadFileFinished(UploadFileId(uploadFile.filePath), sessionId)
val afterUploadCount = database.getPendingUploadCount()
assertEquals(1L, afterUploadCount)
@@ -376,4 +379,15 @@ class UploadRepoTest : BaseTest() {
}
}
+ @Test
+ fun testUploadMetadata_toJsonMap() {
+ val metadata = UploadMetadata("foo", "goo", "maloo")
+ val json = metadata.toJsonMap()
+ val expectedJson: Map = mapOf(
+ "instanceGuid" to JsonPrimitive("foo"),
+ "eventTimestamp" to JsonPrimitive("goo"),
+ "startedOn" to JsonPrimitive("maloo"),
+ )
+ assertEquals(expectedJson, json)
+ }
}
\ No newline at end of file
diff --git a/bridge-client/src/iosMain/kotlin/org/sagebionetworks/bridge/kmm/shared/managers/NativeUploadManager.kt b/bridge-client/src/iosMain/kotlin/org/sagebionetworks/bridge/kmm/shared/managers/NativeUploadManager.kt
index bff3f0c54..e84dcd953 100644
--- a/bridge-client/src/iosMain/kotlin/org/sagebionetworks/bridge/kmm/shared/managers/NativeUploadManager.kt
+++ b/bridge-client/src/iosMain/kotlin/org/sagebionetworks/bridge/kmm/shared/managers/NativeUploadManager.kt
@@ -1,5 +1,10 @@
package org.sagebionetworks.bridge.kmm.shared.managers
+import co.touchlab.kermit.Logger
+import co.touchlab.kermit.Severity
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.IO
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
@@ -8,6 +13,7 @@ import org.koin.core.component.inject
import org.sagebionetworks.bridge.kmm.shared.models.S3UploadSession
import org.sagebionetworks.bridge.kmm.shared.models.UploadFile
import org.sagebionetworks.bridge.kmm.shared.models.UploadFileId
+import org.sagebionetworks.bridge.kmm.shared.models.getUploadFileResourceId
import org.sagebionetworks.bridge.kmm.shared.repo.*
class NativeUploadManager : KoinComponent {
@@ -15,7 +21,7 @@ class NativeUploadManager : KoinComponent {
private val repo : UploadRepo by inject(mode = LazyThreadSafetyMode.NONE)
private val adherenceRecordRepo : AdherenceRecordRepo by inject(mode = LazyThreadSafetyMode.NONE)
private val authManager : AuthenticationRepository by inject(mode = LazyThreadSafetyMode.NONE)
- private val scope = MainScope()
+ private val scope = CoroutineScope(Dispatchers.IO) // TODO: syoung 08/24/2023 Decide if this should be main scope?
fun queueAndRequestUploadSession(uploadFile: UploadFile, callBack: (S3UploadSession?) -> Unit) {
scope.launch {
@@ -28,8 +34,15 @@ class NativeUploadManager : KoinComponent {
}
}
- fun getUploadFiles(): List {
- return repo.getUploadFiles().map { it.filePath }
+ fun hasPendingUploads(): Boolean {
+ return repo.database.getPendingUploadCount() > 0
+ }
+
+ fun getPendingUploadFiles(callBack: (List) -> Unit) {
+ scope.launch {
+ val ret = repo.getPendingUploadFiles()
+ callBack(ret)
+ }
}
fun requestUploadSession(filePath: String, callBack: (S3UploadSession?) -> Unit) {
@@ -43,19 +56,26 @@ class NativeUploadManager : KoinComponent {
}
}
- fun markUploadFileFinished(filePath: String, callBack: (Boolean) -> Unit) {
- markAndProcessFinishedUploads(filePath, callBack)
+ fun markUploadUnrecoverableFailure(filePath: String) {
+ repo.removeUploadFile(UploadFileId(filePath))
}
- fun processFinishedUploads(callBack: (Boolean) -> Unit) {
- markAndProcessFinishedUploads(null, callBack)
+ fun markUploadFileFinished(filePath: String, uploadSessionId: String, callBack: (Boolean) -> Unit) {
+ val uploadFile = UploadFileId(filePath)
+ repo.markUploadFileFinished(uploadFile, uploadSessionId)
+ scope.launch {
+ try {
+ repo.completeUploadSession(uploadSessionId, uploadFile.getUploadFileResourceId())
+ callBack(true)
+ } catch (throwable: Throwable) {
+ Logger.i("Failed to send upload complete to server: $uploadSessionId", throwable)
+ callBack(false)
+ }
+ }
}
- private fun markAndProcessFinishedUploads(filePath: String? = null, callBack: (Boolean) -> Unit) {
+ fun processFinishedUploads(callBack: (Boolean) -> Unit) {
scope.launch {
- if (filePath != null) {
- repo.markUploadFileFinished(UploadFileId(filePath))
- }
try {
repo.processFinishedUploads()
authManager.currentStudyId()?.let {
@@ -68,6 +88,9 @@ class NativeUploadManager : KoinComponent {
}
}
+ fun hasMarkedFileAsUploaded(filePath: String): Boolean {
+ return repo.getUploadedFileRecord(filePath) != null
+ }
}
class PendingUploadObserver(
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.pbxproj b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..0183d2ee6
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.pbxproj
@@ -0,0 +1,360 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 56;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ FF9FB31B2A9D61340044E76A /* BackgroundProcessTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9FB31A2A9D61340044E76A /* BackgroundProcessTestApp.swift */; };
+ FF9FB31D2A9D61340044E76A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9FB31C2A9D61340044E76A /* ContentView.swift */; };
+ FF9FB31F2A9D61350044E76A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FF9FB31E2A9D61350044E76A /* Assets.xcassets */; };
+ FF9FB3222A9D61350044E76A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = FF9FB3212A9D61350044E76A /* Preview Assets.xcassets */; };
+ FF9FB32A2A9DCB460044E76A /* BackgroundProcessSyncManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF9FB3292A9DCB460044E76A /* BackgroundProcessSyncManager.swift */; };
+ FFF9174F2A9EDF57004CE632 /* DebugLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF9174E2A9EDF57004CE632 /* DebugLogger.swift */; };
+ FFF917512A9EDFD2004CE632 /* TestUploadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFF917502A9EDFD2004CE632 /* TestUploadManager.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ FF9FB3172A9D61340044E76A /* BackgroundProcessTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BackgroundProcessTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ FF9FB31A2A9D61340044E76A /* BackgroundProcessTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundProcessTestApp.swift; sourceTree = ""; };
+ FF9FB31C2A9D61340044E76A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
+ FF9FB31E2A9D61350044E76A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ FF9FB3212A9D61350044E76A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
+ FF9FB3282A9D61490044E76A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; };
+ FF9FB3292A9DCB460044E76A /* BackgroundProcessSyncManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BackgroundProcessSyncManager.swift; path = "../../SwiftPackage/Sources/BridgeClientExtension/File Uploading/BackgroundProcessSyncManager.swift"; sourceTree = ""; };
+ FFF9174E2A9EDF57004CE632 /* DebugLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLogger.swift; sourceTree = ""; };
+ FFF917502A9EDFD2004CE632 /* TestUploadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestUploadManager.swift; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ FF9FB3142A9D61340044E76A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ FF9FB30E2A9D61340044E76A = {
+ isa = PBXGroup;
+ children = (
+ FF9FB3192A9D61340044E76A /* BackgroundProcessTest */,
+ FF9FB3182A9D61340044E76A /* Products */,
+ );
+ sourceTree = "";
+ };
+ FF9FB3182A9D61340044E76A /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ FF9FB3172A9D61340044E76A /* BackgroundProcessTest.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ FF9FB3192A9D61340044E76A /* BackgroundProcessTest */ = {
+ isa = PBXGroup;
+ children = (
+ FF9FB3282A9D61490044E76A /* Info.plist */,
+ FF9FB31A2A9D61340044E76A /* BackgroundProcessTestApp.swift */,
+ FF9FB31C2A9D61340044E76A /* ContentView.swift */,
+ FFF9174E2A9EDF57004CE632 /* DebugLogger.swift */,
+ FFF917502A9EDFD2004CE632 /* TestUploadManager.swift */,
+ FF9FB3292A9DCB460044E76A /* BackgroundProcessSyncManager.swift */,
+ FF9FB31E2A9D61350044E76A /* Assets.xcassets */,
+ FF9FB3202A9D61350044E76A /* Preview Content */,
+ );
+ path = BackgroundProcessTest;
+ sourceTree = "";
+ };
+ FF9FB3202A9D61350044E76A /* Preview Content */ = {
+ isa = PBXGroup;
+ children = (
+ FF9FB3212A9D61350044E76A /* Preview Assets.xcassets */,
+ );
+ path = "Preview Content";
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ FF9FB3162A9D61340044E76A /* BackgroundProcessTest */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = FF9FB3252A9D61350044E76A /* Build configuration list for PBXNativeTarget "BackgroundProcessTest" */;
+ buildPhases = (
+ FF9FB3132A9D61340044E76A /* Sources */,
+ FF9FB3142A9D61340044E76A /* Frameworks */,
+ FF9FB3152A9D61340044E76A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = BackgroundProcessTest;
+ productName = BackgroundProcessTest;
+ productReference = FF9FB3172A9D61340044E76A /* BackgroundProcessTest.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ FF9FB30F2A9D61340044E76A /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ BuildIndependentTargetsInParallel = 1;
+ LastSwiftUpdateCheck = 1430;
+ LastUpgradeCheck = 1430;
+ TargetAttributes = {
+ FF9FB3162A9D61340044E76A = {
+ CreatedOnToolsVersion = 14.3;
+ };
+ };
+ };
+ buildConfigurationList = FF9FB3122A9D61340044E76A /* Build configuration list for PBXProject "BackgroundProcessTest" */;
+ compatibilityVersion = "Xcode 14.0";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = FF9FB30E2A9D61340044E76A;
+ productRefGroup = FF9FB3182A9D61340044E76A /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ FF9FB3162A9D61340044E76A /* BackgroundProcessTest */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ FF9FB3152A9D61340044E76A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FF9FB3222A9D61350044E76A /* Preview Assets.xcassets in Resources */,
+ FF9FB31F2A9D61350044E76A /* Assets.xcassets in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ FF9FB3132A9D61340044E76A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ FF9FB31D2A9D61340044E76A /* ContentView.swift in Sources */,
+ FF9FB32A2A9DCB460044E76A /* BackgroundProcessSyncManager.swift in Sources */,
+ FFF917512A9EDFD2004CE632 /* TestUploadManager.swift in Sources */,
+ FF9FB31B2A9D61340044E76A /* BackgroundProcessTestApp.swift in Sources */,
+ FFF9174F2A9EDF57004CE632 /* DebugLogger.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin XCBuildConfiguration section */
+ FF9FB3232A9D61350044E76A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.4;
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ FF9FB3242A9D61350044E76A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_ENABLE_OBJC_WEAK = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 16.4;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ MTL_FAST_MATH = YES;
+ SDKROOT = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ FF9FB3262A9D61350044E76A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"BackgroundProcessTest/Preview Content\"";
+ DEVELOPMENT_TEAM = KA9Z8R6M6K;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = BackgroundProcessTest/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.sagebase.BackgroundProcessTest;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ FF9FB3272A9D61350044E76A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
+ CODE_SIGN_STYLE = Automatic;
+ CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_ASSET_PATHS = "\"BackgroundProcessTest/Preview Content\"";
+ DEVELOPMENT_TEAM = KA9Z8R6M6K;
+ ENABLE_PREVIEWS = YES;
+ GENERATE_INFOPLIST_FILE = YES;
+ INFOPLIST_FILE = BackgroundProcessTest/Info.plist;
+ INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
+ INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
+ INFOPLIST_KEY_UILaunchScreen_Generation = YES;
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ MARKETING_VERSION = 1.0;
+ PRODUCT_BUNDLE_IDENTIFIER = org.sagebase.BackgroundProcessTest;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_EMIT_LOC_STRINGS = YES;
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ FF9FB3122A9D61340044E76A /* Build configuration list for PBXProject "BackgroundProcessTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ FF9FB3232A9D61350044E76A /* Debug */,
+ FF9FB3242A9D61350044E76A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ FF9FB3252A9D61350044E76A /* Build configuration list for PBXNativeTarget "BackgroundProcessTest" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ FF9FB3262A9D61350044E76A /* Debug */,
+ FF9FB3272A9D61350044E76A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = FF9FB30F2A9D61340044E76A /* Project object */;
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AccentColor.colorset/Contents.json b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AccentColor.colorset/Contents.json
new file mode 100644
index 000000000..eb8789700
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AccentColor.colorset/Contents.json
@@ -0,0 +1,11 @@
+{
+ "colors" : [
+ {
+ "idiom" : "universal"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AppIcon.appiconset/Contents.json b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..13613e3ee
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,13 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "platform" : "ios",
+ "size" : "1024x1024"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/Contents.json b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/BackgroundProcessTestApp.swift b/iosBackgroundProcessTest/BackgroundProcessTest/BackgroundProcessTestApp.swift
new file mode 100644
index 000000000..35841f683
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/BackgroundProcessTestApp.swift
@@ -0,0 +1,30 @@
+// Created 8/28/23
+// swift-tools-version:5.0
+
+import SwiftUI
+
+
+let bgTaskId = "org.sagebase.BackgroundProcessTest"
+
+class AppDelegate : UIResponder, UIApplicationDelegate {
+
+ let manager = TestUploadManager()
+
+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
+
+ manager.onLaunch(backgroundProcessId: bgTaskId)
+
+ return true
+ }
+}
+
+@main
+struct BackgroundProcessTestApp: App {
+ @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
+
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/ContentView.swift b/iosBackgroundProcessTest/BackgroundProcessTest/ContentView.swift
new file mode 100644
index 000000000..7571c5699
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/ContentView.swift
@@ -0,0 +1,24 @@
+// Created 8/28/23
+// swift-tools-version:5.0
+
+import SwiftUI
+import BackgroundTasks
+
+struct ContentView: View {
+ var body: some View {
+ VStack {
+ Image(systemName: "globe")
+ .imageScale(.large)
+ .foregroundColor(.accentColor)
+ Text("Hello, world!")
+ }
+ .padding()
+ }
+}
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ }
+}
+
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/DebugLogger.swift b/iosBackgroundProcessTest/BackgroundProcessTest/DebugLogger.swift
new file mode 100644
index 000000000..f97722331
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/DebugLogger.swift
@@ -0,0 +1,57 @@
+// Created 8/29/23
+// swift-tools-version:5.0
+
+import Foundation
+
+/// Fake logger that is used to test background processing
+final class Logger {
+
+ static func log(severity: LogSeverity, tag: LoggingTag = .bridgeClientUI, message: String, metadata: [String : Any]? = nil) {
+ logToConsole(severity: severity, tag: tag, message: message, error: nil)
+ }
+
+ static func log(tag: LoggingTag = .bridgeClientUI, error: Error, message: String? = nil, metadata: [String : Any]? = nil) {
+ logToConsole(severity: .error, tag: tag, message: message, error: error)
+ }
+
+ private static func logToConsole(severity: LogSeverity, tag: LoggingTag, message: String?, error: Error?) {
+ print("[\(tag.rawValue):\(Date())] \(severity.name.uppercased()): \(message ?? "")")
+ if let error = error {
+ debugPrint(error)
+ }
+ }
+}
+
+struct LoggingTag : RawRepresentable, Hashable, ExpressibleByStringLiteral {
+
+ public let rawValue: String
+
+ public init(rawValue: String) {
+ self.rawValue = rawValue
+ }
+
+ public init(stringLiteral value: StringLiteralType) {
+ self.rawValue = value
+ }
+
+ public static let upload: LoggingTag = "Upload"
+ public static let bridgeClientUI: LoggingTag = "BridgeClientUI"
+}
+
+enum LogSeverity : String, CaseIterable {
+ case debug, info, warn, error
+
+ var name: String {
+ self.rawValue.uppercased()
+ }
+
+ var ordinal : Int {
+ Self.allCases.firstIndex(of: self)!
+ }
+}
+
+extension LogSeverity : Comparable {
+ public static func < (lhs: LogSeverity, rhs: LogSeverity) -> Bool {
+ lhs.ordinal < rhs.ordinal
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/Info.plist b/iosBackgroundProcessTest/BackgroundProcessTest/Info.plist
new file mode 100644
index 000000000..fd439cde6
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/Info.plist
@@ -0,0 +1,15 @@
+
+
+
+
+ BGTaskSchedulerPermittedIdentifiers
+
+ org.sagebase.BackgroundProcessTest
+
+ UIBackgroundModes
+
+ fetch
+ processing
+
+
+
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/Preview Content/Preview Assets.xcassets/Contents.json b/iosBackgroundProcessTest/BackgroundProcessTest/Preview Content/Preview Assets.xcassets/Contents.json
new file mode 100644
index 000000000..73c00596a
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/Preview Content/Preview Assets.xcassets/Contents.json
@@ -0,0 +1,6 @@
+{
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/iosBackgroundProcessTest/BackgroundProcessTest/TestUploadManager.swift b/iosBackgroundProcessTest/BackgroundProcessTest/TestUploadManager.swift
new file mode 100644
index 000000000..e4027fab7
--- /dev/null
+++ b/iosBackgroundProcessTest/BackgroundProcessTest/TestUploadManager.swift
@@ -0,0 +1,61 @@
+// Created 8/29/23
+// swift-tools-version:5.0
+
+import Foundation
+
+/// Fake upload manager that is used to test background processing
+class TestUploadManager : NSObject, BackgroundProcessSyncDelegate {
+
+ lazy var syncManager: BackgroundProcessSyncManager = .init(delegate: self)
+
+ let subdir = "UrbanDictionary"
+ lazy var uploadDirURL: URL? = {
+ do {
+ let baseURL = try FileManager.default.sharedAppSupportDirectory()
+ let url: URL
+ if #available(iOS 16.0, *) {
+ url = baseURL.appending(component: subdir, directoryHint: .isDirectory)
+ } else {
+ url = baseURL.appendingPathComponent(subdir)
+ }
+ try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
+ return url
+ } catch {
+ Logger.log(tag: .upload, error: error, message: "Error trying to create the uploads directory.")
+ return nil
+ }
+ }()
+
+ @MainActor
+ func onLaunch(backgroundProcessId: String?) {
+ syncManager.onLaunch(backgroundProcessId: backgroundProcessId)
+ }
+
+ var shouldScheduleBackgroundProcess: Bool {
+ true
+ }
+
+ func retryBackgroundProcess(isBackground: Bool) async throws {
+ let dir = uploadDirURL!
+ for ii in 0..<10 {
+ print("retry: START \(ii)")
+ let url = testURL()
+ let (data, _) = try await URLSession.shared.data(from: url)
+ let fileURL = dir.appendingPathComponent("\(UUID()).json")
+ try data.write(to: fileURL)
+ try await Task.sleep(nanoseconds: 5 * 1_000_000_000)
+ print("retry: END \(ii)")
+ }
+ }
+
+ func testURL() -> URL {
+ URL(string: "https://www.urbandictionary.com/random.php")!
+ }
+}
+
+extension FileManager {
+ func sharedAppSupportDirectory() throws -> URL {
+ try self.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
+ }
+}
+