@use-voltra/ios-client 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Voltra.podspec +1 -0
- package/build/commonjs/events.js +23 -0
- package/build/commonjs/events.js.map +1 -1
- package/build/commonjs/index.js +59 -0
- package/build/commonjs/index.js.map +1 -1
- package/build/commonjs/live-activity/api.js +3 -1
- package/build/commonjs/live-activity/api.js.map +1 -1
- package/build/commonjs/live-activity/dynamic-api.js +173 -0
- package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
- package/build/commonjs/native/NativeVoltra.js.map +1 -1
- package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
- package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
- package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
- package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
- package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
- package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
- package/build/commonjs/widgets/server-credentials.js +9 -0
- package/build/commonjs/widgets/server-credentials.js.map +1 -1
- package/build/commonjs/widgets/server-update.js +78 -0
- package/build/commonjs/widgets/server-update.js.map +1 -0
- package/build/module/events.js +23 -0
- package/build/module/events.js.map +1 -1
- package/build/module/index.js +3 -0
- package/build/module/index.js.map +1 -1
- package/build/module/live-activity/api.js +2 -2
- package/build/module/live-activity/api.js.map +1 -1
- package/build/module/live-activity/dynamic-api.js +167 -0
- package/build/module/live-activity/dynamic-api.js.map +1 -0
- package/build/module/native/NativeVoltra.js.map +1 -1
- package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
- package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
- package/build/module/utils/enableWidgetHotReload.js +9 -11
- package/build/module/utils/enableWidgetHotReload.js.map +1 -1
- package/build/module/utils/useUpdateOnHMR.js +3 -7
- package/build/module/utils/useUpdateOnHMR.js.map +1 -1
- package/build/module/widgets/server-credentials.js +9 -0
- package/build/module/widgets/server-credentials.js.map +1 -1
- package/build/module/widgets/server-update.js +72 -0
- package/build/module/widgets/server-update.js.map +1 -0
- package/build/typescript/commonjs/events.d.ts +7 -0
- package/build/typescript/commonjs/events.d.ts.map +1 -1
- package/build/typescript/commonjs/index.d.ts +3 -0
- package/build/typescript/commonjs/index.d.ts.map +1 -1
- package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
- package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
- package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
- package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts +20 -0
- package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
- package/build/typescript/commonjs/types.d.ts +1 -1
- package/build/typescript/commonjs/types.d.ts.map +1 -1
- package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
- package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
- package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
- package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
- package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
- package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
- package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
- package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
- package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
- package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
- package/build/typescript/module/events.d.ts +7 -0
- package/build/typescript/module/events.d.ts.map +1 -1
- package/build/typescript/module/index.d.ts +3 -0
- package/build/typescript/module/index.d.ts.map +1 -1
- package/build/typescript/module/live-activity/api.d.ts +7 -0
- package/build/typescript/module/live-activity/api.d.ts.map +1 -1
- package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
- package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
- package/build/typescript/module/native/NativeVoltra.d.ts +20 -0
- package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
- package/build/typescript/module/types.d.ts +1 -1
- package/build/typescript/module/types.d.ts.map +1 -1
- package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
- package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
- package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
- package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
- package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
- package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
- package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
- package/build/typescript/module/widgets/server-update.d.ts +64 -0
- package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
- package/expo-plugin/build/cjs/constants.js +20 -1
- package/expo-plugin/build/cjs/constants.js.map +1 -1
- package/expo-plugin/build/cjs/index.js +6 -0
- package/expo-plugin/build/cjs/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios/index.js +1 -0
- package/expo-plugin/build/cjs/ios/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
- package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
- package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
- package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
- package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
- package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
- package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
- package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
- package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
- package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
- package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
- package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
- package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +51 -11
- package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js +21 -5
- package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
- package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
- package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
- package/expo-plugin/build/cjs/validation.js +54 -0
- package/expo-plugin/build/cjs/validation.js.map +1 -1
- package/expo-plugin/build/esm/constants.js +17 -0
- package/expo-plugin/build/esm/constants.js.map +1 -1
- package/expo-plugin/build/esm/index.js +6 -0
- package/expo-plugin/build/esm/index.js.map +1 -1
- package/expo-plugin/build/esm/ios/index.js +1 -0
- package/expo-plugin/build/esm/ios/index.js.map +1 -1
- package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
- package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
- package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
- package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
- package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
- package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
- package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
- package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
- package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
- package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
- package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
- package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/index.js +9 -3
- package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
- package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +51 -11
- package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
- package/expo-plugin/build/esm/ios-widget/xcode/index.js +21 -5
- package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
- package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
- package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
- package/expo-plugin/build/esm/validation.js +54 -1
- package/expo-plugin/build/esm/validation.js.map +1 -1
- package/expo-plugin/build/types/constants.d.ts +10 -1
- package/expo-plugin/build/types/constants.d.ts.map +1 -1
- package/expo-plugin/build/types/index.d.ts +1 -1
- package/expo-plugin/build/types/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/index.d.ts +2 -0
- package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
- package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
- package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
- package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
- package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
- package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
- package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
- package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +3 -1
- package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +2 -1
- package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
- package/expo-plugin/build/types/types.d.ts +23 -4
- package/expo-plugin/build/types/types.d.ts.map +1 -1
- package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
- package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
- package/expo-plugin/build/types/validation.d.ts +2 -1
- package/expo-plugin/build/types/validation.d.ts.map +1 -1
- package/expo-plugin/src/constants.ts +24 -1
- package/expo-plugin/src/index.ts +9 -0
- package/expo-plugin/src/ios/index.ts +3 -0
- package/expo-plugin/src/ios/infoPlist.ts +33 -5
- package/expo-plugin/src/ios/serverUpdate.ts +29 -0
- package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
- package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
- package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
- package/expo-plugin/src/ios-widget/files/index.ts +15 -4
- package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
- package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
- package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
- package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
- package/expo-plugin/src/ios-widget/index.ts +13 -3
- package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
- package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +34 -28
- package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +59 -12
- package/expo-plugin/src/ios-widget/xcode/index.ts +23 -6
- package/expo-plugin/src/types.ts +28 -4
- package/expo-plugin/src/utils/urlScheme.ts +47 -17
- package/expo-plugin/src/validation.ts +76 -1
- package/ios/Package.swift +32 -3
- package/ios/VoltraWidget.podspec +1 -0
- package/ios/app/NativeVoltra.mm +107 -7
- package/ios/app/VoltraLiveActivityManager.swift +30 -0
- package/ios/app/VoltraLiveActivityService.swift +77 -5
- package/ios/app/VoltraModule.swift +99 -1
- package/ios/app/VoltraModuleImpl.swift +100 -2
- package/ios/app/VoltraViewComponentView.mm +3 -3
- package/ios/app/VoltraWidgetService.swift +147 -12
- package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
- package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
- package/ios/shared/DynamicWidgetPropsStore.swift +73 -6
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
- package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
- package/ios/shared/ServerWidgetContentResolver.swift +60 -0
- package/ios/shared/ServerWidgetResponseStore.swift +25 -0
- package/ios/shared/ShortNames.swift +6 -0
- package/ios/shared/VoltraConfig.swift +41 -0
- package/ios/shared/VoltraConstants.swift +15 -0
- package/ios/shared/VoltraElement.swift +127 -43
- package/ios/shared/VoltraEventBus.swift +52 -7
- package/ios/shared/VoltraJSRenderer.swift +90 -22
- package/ios/shared/VoltraNode.swift +1 -109
- package/ios/shared/VoltraRefreshIntent.swift +1 -1
- package/ios/shared/VoltraWidgetKind.swift +41 -0
- package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
- package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
- package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
- package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
- package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
- package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
- package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
- package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
- package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
- package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
- package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
- package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
- package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
- package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
- package/ios/target/VoltraClientWidgetRuntime.swift +44 -6
- package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
- package/ios/target/VoltraHomeWidget.swift +85 -90
- package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
- package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
- package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +2 -2
- package/ios/ui/Views/VoltraChart.swift +108 -0
- package/package.json +4 -4
- package/src/events.ts +34 -0
- package/src/index.ts +22 -0
- package/src/live-activity/api.ts +2 -2
- package/src/live-activity/dynamic-api.ts +240 -0
- package/src/native/NativeVoltra.ts +21 -0
- package/src/types.ts +4 -0
- package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
- package/src/utils/enableWidgetHotReload.ts +9 -16
- package/src/utils/useUpdateOnHMR.ts +3 -12
- package/src/widgets/server-credentials.ts +8 -0
- package/src/widgets/server-update.ts +84 -0
- /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
/// Checks the portion of a Dynamic Live Activity payload ActivityKit stores.
|
|
4
|
+
/// This is intentionally uncompressed: ActivityKit's 4 KB limit applies to its
|
|
5
|
+
/// encoded attributes and content state, not Voltra's legacy rendered payload.
|
|
6
|
+
public enum VoltraDynamicLiveActivityPayloadValidator {
|
|
7
|
+
private struct Attributes: Encodable {
|
|
8
|
+
let name: String
|
|
9
|
+
let deepLinkUrl: String?
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
public static func decodeProps(_ jsonString: String) throws -> [String: VoltraDynamicLiveActivityJSONValue] {
|
|
13
|
+
let data = Data(jsonString.utf8)
|
|
14
|
+
return try JSONDecoder().decode([String: VoltraDynamicLiveActivityJSONValue].self, from: data)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public static func validate(
|
|
18
|
+
name: String,
|
|
19
|
+
deepLinkUrl: String?,
|
|
20
|
+
props: [String: VoltraDynamicLiveActivityJSONValue]
|
|
21
|
+
) throws {
|
|
22
|
+
let attributesSize = try encodedSize(Attributes(name: name, deepLinkUrl: deepLinkUrl))
|
|
23
|
+
let contentStateSize = try encodedSize(VoltraDynamicLiveActivityContentState(props: props))
|
|
24
|
+
try validateSize(attributesSize + contentStateSize)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static func validateContentState(_ props: [String: VoltraDynamicLiveActivityJSONValue]) throws {
|
|
28
|
+
try validateSize(encodedSize(VoltraDynamicLiveActivityContentState(props: props)))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public static func encodedContentStateSize(
|
|
32
|
+
_ props: [String: VoltraDynamicLiveActivityJSONValue]
|
|
33
|
+
) throws -> Int {
|
|
34
|
+
try encodedSize(VoltraDynamicLiveActivityContentState(props: props))
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public static func encodedStartSize(
|
|
38
|
+
name: String,
|
|
39
|
+
deepLinkUrl: String?,
|
|
40
|
+
props: [String: VoltraDynamicLiveActivityJSONValue]
|
|
41
|
+
) throws -> Int {
|
|
42
|
+
try encodedSize(Attributes(name: name, deepLinkUrl: deepLinkUrl)) +
|
|
43
|
+
encodedSize(VoltraDynamicLiveActivityContentState(props: props))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private static func encodedSize(_ value: some Encodable) throws -> Int {
|
|
47
|
+
try JSONEncoder().encode(value).count
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static func validateSize(_ size: Int) throws {
|
|
51
|
+
guard size <= VoltraConstants.maxPayloadSizeBytes else {
|
|
52
|
+
throw VoltraDynamicLiveActivityError.payloadTooLarge(size: size)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import ActivityKit
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Type-erased operations for one generated ActivityKit attributes type.
|
|
5
|
+
///
|
|
6
|
+
/// Generated source registers descriptors at runtime. Pod-owned code only
|
|
7
|
+
/// depends on this registry and never names a type compiled into the host app.
|
|
8
|
+
private struct VoltraDynamicLiveActivityDescriptor {
|
|
9
|
+
let definitionId: String
|
|
10
|
+
let create: (VoltraDynamicLiveActivityCreateRequest) async throws -> VoltraDynamicLiveActivityReference
|
|
11
|
+
let update: (String, VoltraDynamicLiveActivityUpdateRequest) async -> Bool
|
|
12
|
+
let end: (String, ActivityUIDismissalPolicy) async -> Bool
|
|
13
|
+
let endAll: (ActivityUIDismissalPolicy) async -> Void
|
|
14
|
+
let activities: () -> [VoltraDynamicLiveActivityReference]
|
|
15
|
+
let observe: (VoltraDynamicLiveActivityObserver) async -> Void
|
|
16
|
+
let reload: () async -> Void
|
|
17
|
+
|
|
18
|
+
init<Attributes: VoltraDynamicLiveActivityDefinition>(_ attributes: Attributes.Type) {
|
|
19
|
+
definitionId = Attributes.definitionId
|
|
20
|
+
create = { try await VoltraDynamicLiveActivityOperations.create(attributes, request: $0) }
|
|
21
|
+
update = { await VoltraDynamicLiveActivityOperations.update(attributes, byName: $0, request: $1) }
|
|
22
|
+
end = { await VoltraDynamicLiveActivityOperations.end(attributes, byName: $0, dismissalPolicy: $1) }
|
|
23
|
+
endAll = { await VoltraDynamicLiveActivityOperations.endAll(attributes, dismissalPolicy: $0) }
|
|
24
|
+
activities = { VoltraDynamicLiveActivityOperations.activities(attributes) }
|
|
25
|
+
observe = { await $0.observe(attributes) }
|
|
26
|
+
reload = { await VoltraDynamicLiveActivityOperations.reload(attributes) }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// Runtime-owned catalog populated by the generated host source.
|
|
31
|
+
///
|
|
32
|
+
/// `VoltraGeneratedDynamicLiveActivityRegistration` has a fixed Objective-C
|
|
33
|
+
/// runtime name in both the app and extension products. This lets the pod ask
|
|
34
|
+
/// the host target to register its generated types without creating a reverse
|
|
35
|
+
/// compile-time dependency from the pod to the host.
|
|
36
|
+
public final class VoltraDynamicLiveActivityRegistry {
|
|
37
|
+
public static let shared = VoltraDynamicLiveActivityRegistry()
|
|
38
|
+
|
|
39
|
+
private let lock = NSLock()
|
|
40
|
+
private var descriptors: [String: VoltraDynamicLiveActivityDescriptor] = [:]
|
|
41
|
+
private var attemptedGeneratedRegistration = false
|
|
42
|
+
|
|
43
|
+
private init() {}
|
|
44
|
+
|
|
45
|
+
public func register<Attributes: VoltraDynamicLiveActivityDefinition>(_ attributes: Attributes.Type) {
|
|
46
|
+
lock.lock()
|
|
47
|
+
descriptors[Attributes.definitionId] = VoltraDynamicLiveActivityDescriptor(attributes)
|
|
48
|
+
lock.unlock()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
public func contains(_ definitionId: String) -> Bool {
|
|
52
|
+
ensureGeneratedRegistration()
|
|
53
|
+
lock.lock()
|
|
54
|
+
defer { lock.unlock() }
|
|
55
|
+
return descriptors[definitionId] != nil
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
public func definitionIds() -> [String] {
|
|
59
|
+
ensureGeneratedRegistration()
|
|
60
|
+
lock.lock()
|
|
61
|
+
defer { lock.unlock() }
|
|
62
|
+
return descriptors.keys.sorted()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public func create(_ request: VoltraDynamicLiveActivityCreateRequest) async throws -> VoltraDynamicLiveActivityReference? {
|
|
66
|
+
guard let descriptor = descriptor(for: request.definitionId) else { return nil }
|
|
67
|
+
return try await descriptor.create(request)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public func update(byName name: String, request: VoltraDynamicLiveActivityUpdateRequest) async -> Bool {
|
|
71
|
+
for descriptor in descriptorSnapshot() {
|
|
72
|
+
if await descriptor.update(name, request) {
|
|
73
|
+
return true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
public func end(byName name: String, dismissalPolicy: ActivityUIDismissalPolicy) async -> Bool {
|
|
80
|
+
var ended = false
|
|
81
|
+
for descriptor in descriptorSnapshot() {
|
|
82
|
+
if await descriptor.end(name, dismissalPolicy) {
|
|
83
|
+
ended = true
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return ended
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public func endAll(dismissalPolicy: ActivityUIDismissalPolicy) async {
|
|
90
|
+
for descriptor in descriptorSnapshot() {
|
|
91
|
+
await descriptor.endAll(dismissalPolicy)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public func activities() -> [VoltraDynamicLiveActivityReference] {
|
|
96
|
+
descriptorSnapshot().flatMap { $0.activities() }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public func startObserving(with observer: VoltraDynamicLiveActivityObserver) async {
|
|
100
|
+
for descriptor in descriptorSnapshot() {
|
|
101
|
+
await descriptor.observe(observer)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
public func reload(definitionIds: Set<String>?) async {
|
|
106
|
+
for descriptor in descriptorSnapshot() where definitionIds?.contains(descriptor.definitionId) != false {
|
|
107
|
+
await descriptor.reload()
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private func descriptor(for definitionId: String) -> VoltraDynamicLiveActivityDescriptor? {
|
|
112
|
+
ensureGeneratedRegistration()
|
|
113
|
+
lock.lock()
|
|
114
|
+
defer { lock.unlock() }
|
|
115
|
+
return descriptors[definitionId]
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private func descriptorSnapshot() -> [VoltraDynamicLiveActivityDescriptor] {
|
|
119
|
+
ensureGeneratedRegistration()
|
|
120
|
+
lock.lock()
|
|
121
|
+
defer { lock.unlock() }
|
|
122
|
+
return descriptors.values.sorted { $0.definitionId < $1.definitionId }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private func ensureGeneratedRegistration() {
|
|
126
|
+
lock.lock()
|
|
127
|
+
guard !attemptedGeneratedRegistration else {
|
|
128
|
+
lock.unlock()
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
attemptedGeneratedRegistration = true
|
|
132
|
+
lock.unlock()
|
|
133
|
+
|
|
134
|
+
let className = "VoltraGeneratedDynamicLiveActivityRegistration"
|
|
135
|
+
let selector = NSSelectorFromString("registerDefinitions")
|
|
136
|
+
guard let registration = NSClassFromString(className) as? NSObject.Type,
|
|
137
|
+
registration.responds(to: selector)
|
|
138
|
+
else {
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
registration.perform(selector)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
|
|
3
|
+
#if canImport(Darwin)
|
|
4
|
+
import Darwin
|
|
5
|
+
#else
|
|
6
|
+
import Glibc
|
|
7
|
+
#endif
|
|
8
|
+
|
|
9
|
+
/// The intentionally minimal diagnostic record sent from the widget extension
|
|
10
|
+
/// to the app after a Dynamic Live Activity definition cannot be rendered.
|
|
11
|
+
/// Do not add props, tokens, stages, or arbitrary error details here.
|
|
12
|
+
public struct VoltraDynamicLiveActivityRenderFailure: Codable, Equatable {
|
|
13
|
+
public static let eventType = "dynamicLiveActivityRenderFailed"
|
|
14
|
+
|
|
15
|
+
public let type: String
|
|
16
|
+
public let source: String
|
|
17
|
+
public let timestamp: TimeInterval
|
|
18
|
+
public let activityName: String
|
|
19
|
+
public let definitionId: String
|
|
20
|
+
public let message: String
|
|
21
|
+
|
|
22
|
+
public init(activityName: String, definitionId: String, message: String, timestamp: Date = Date()) {
|
|
23
|
+
type = Self.eventType
|
|
24
|
+
source = activityName
|
|
25
|
+
self.timestamp = timestamp.timeIntervalSince1970
|
|
26
|
+
self.activityName = activityName
|
|
27
|
+
self.definitionId = definitionId
|
|
28
|
+
self.message = Self.sanitize(message)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public var dictionary: [String: Any] {
|
|
32
|
+
[
|
|
33
|
+
"type": type,
|
|
34
|
+
"source": source,
|
|
35
|
+
"timestamp": timestamp,
|
|
36
|
+
"activityName": activityName,
|
|
37
|
+
"definitionId": definitionId,
|
|
38
|
+
"message": message,
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Error descriptions can contain line breaks and unbounded implementation
|
|
43
|
+
/// details. Keep the diagnostic useful while making the persisted contract
|
|
44
|
+
/// predictable and safe to expose to JavaScript.
|
|
45
|
+
public static func sanitize(_ message: String) -> String {
|
|
46
|
+
let collapsed = message
|
|
47
|
+
.components(separatedBy: .whitespacesAndNewlines)
|
|
48
|
+
.filter { !$0.isEmpty }
|
|
49
|
+
.joined(separator: " ")
|
|
50
|
+
let fallback = "Dynamic Live Activity rendering failed."
|
|
51
|
+
return String((collapsed.isEmpty ? fallback : collapsed).prefix(500))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public protocol VoltraDynamicLiveActivityRenderFailureStorage {
|
|
56
|
+
func append(_ failure: VoltraDynamicLiveActivityRenderFailure, capacity: Int) throws
|
|
57
|
+
func drain() throws -> [VoltraDynamicLiveActivityRenderFailure]
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Cross-process-safe file storage for the dedicated failure queue.
|
|
61
|
+
///
|
|
62
|
+
/// The app and widget extension take an advisory lock on the same App Group
|
|
63
|
+
/// lock file before every read/modify/write transaction. Atomic replacement of
|
|
64
|
+
/// the JSON data file prevents partial writes. Corrupt data is quarantined and
|
|
65
|
+
/// treated as an empty queue so one damaged record cannot permanently disable
|
|
66
|
+
/// diagnostics.
|
|
67
|
+
public final class VoltraDynamicLiveActivityRenderFailureFileStorage: VoltraDynamicLiveActivityRenderFailureStorage {
|
|
68
|
+
private let directoryURL: URL
|
|
69
|
+
private let fileManager: FileManager
|
|
70
|
+
private let queueFileName = "dynamic-live-activity-render-failures-v1.json"
|
|
71
|
+
private let lockFileName = "dynamic-live-activity-render-failures-v1.lock"
|
|
72
|
+
|
|
73
|
+
public init(directoryURL: URL, fileManager: FileManager = .default) {
|
|
74
|
+
self.directoryURL = directoryURL
|
|
75
|
+
self.fileManager = fileManager
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public func append(_ failure: VoltraDynamicLiveActivityRenderFailure, capacity: Int) throws {
|
|
79
|
+
try withExclusiveAccess { queueURL in
|
|
80
|
+
var failures = loadRecoveringCorruption(from: queueURL)
|
|
81
|
+
failures.append(failure)
|
|
82
|
+
if failures.count > capacity {
|
|
83
|
+
failures.removeFirst(failures.count - capacity)
|
|
84
|
+
}
|
|
85
|
+
try write(failures, to: queueURL)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public func drain() throws -> [VoltraDynamicLiveActivityRenderFailure] {
|
|
90
|
+
try withExclusiveAccess { queueURL in
|
|
91
|
+
let failures = loadRecoveringCorruption(from: queueURL)
|
|
92
|
+
try write([], to: queueURL)
|
|
93
|
+
return failures
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private func withExclusiveAccess<Result>(
|
|
98
|
+
_ operation: (URL) throws -> Result
|
|
99
|
+
) throws -> Result {
|
|
100
|
+
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
|
|
101
|
+
let lockURL = directoryURL.appendingPathComponent(lockFileName, isDirectory: false)
|
|
102
|
+
let descriptor = open(lockURL.path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)
|
|
103
|
+
guard descriptor >= 0 else { throw POSIXError(.EIO) }
|
|
104
|
+
defer { close(descriptor) }
|
|
105
|
+
guard flock(descriptor, LOCK_EX) == 0 else { throw POSIXError(.EIO) }
|
|
106
|
+
defer { flock(descriptor, LOCK_UN) }
|
|
107
|
+
return try operation(directoryURL.appendingPathComponent(queueFileName, isDirectory: false))
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private func loadRecoveringCorruption(from url: URL) -> [VoltraDynamicLiveActivityRenderFailure] {
|
|
111
|
+
guard fileManager.fileExists(atPath: url.path) else { return [] }
|
|
112
|
+
do {
|
|
113
|
+
return try JSONDecoder().decode(
|
|
114
|
+
[VoltraDynamicLiveActivityRenderFailure].self,
|
|
115
|
+
from: Data(contentsOf: url)
|
|
116
|
+
)
|
|
117
|
+
} catch {
|
|
118
|
+
let quarantineURL = directoryURL.appendingPathComponent(
|
|
119
|
+
"\(queueFileName).corrupt-\(UUID().uuidString)",
|
|
120
|
+
isDirectory: false
|
|
121
|
+
)
|
|
122
|
+
try? fileManager.moveItem(at: url, to: quarantineURL)
|
|
123
|
+
return []
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private func write(_ failures: [VoltraDynamicLiveActivityRenderFailure], to url: URL) throws {
|
|
128
|
+
try JSONEncoder().encode(failures).write(to: url, options: .atomic)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// A dedicated, bounded queue whose storage owns the append/drain transaction.
|
|
133
|
+
public final class VoltraDynamicLiveActivityRenderFailureQueue {
|
|
134
|
+
public static let capacity = 100
|
|
135
|
+
|
|
136
|
+
private let storage: VoltraDynamicLiveActivityRenderFailureStorage
|
|
137
|
+
public init(storage: VoltraDynamicLiveActivityRenderFailureStorage) {
|
|
138
|
+
self.storage = storage
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
@discardableResult
|
|
142
|
+
public func record(_ failure: VoltraDynamicLiveActivityRenderFailure) -> Bool {
|
|
143
|
+
do {
|
|
144
|
+
try storage.append(failure, capacity: Self.capacity)
|
|
145
|
+
return true
|
|
146
|
+
} catch {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
public func drain() -> [VoltraDynamicLiveActivityRenderFailure] {
|
|
152
|
+
do {
|
|
153
|
+
return try storage.drain()
|
|
154
|
+
} catch {
|
|
155
|
+
return []
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import CoreFoundation
|
|
2
|
+
import Foundation
|
|
3
|
+
|
|
4
|
+
/// Cross-process notification relay for the App Group backed failure queue.
|
|
5
|
+
/// Darwin notifications carry no payload, so consumers always drain storage.
|
|
6
|
+
private final class VoltraDynamicLiveActivityRenderFailureNotifier {
|
|
7
|
+
static let shared = VoltraDynamicLiveActivityRenderFailureNotifier()
|
|
8
|
+
|
|
9
|
+
private let lock = NSLock()
|
|
10
|
+
private var handlers: [UUID: () -> Void] = [:]
|
|
11
|
+
private let name = "com.voltra.dynamicLiveActivityRenderFailures" as CFString
|
|
12
|
+
|
|
13
|
+
private init() {
|
|
14
|
+
CFNotificationCenterAddObserver(
|
|
15
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
16
|
+
Unmanaged.passUnretained(self).toOpaque(),
|
|
17
|
+
Self.didReceive,
|
|
18
|
+
name,
|
|
19
|
+
nil,
|
|
20
|
+
.deliverImmediately
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
deinit {
|
|
25
|
+
CFNotificationCenterRemoveObserver(
|
|
26
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
27
|
+
Unmanaged.passUnretained(self).toOpaque(),
|
|
28
|
+
CFNotificationName(name),
|
|
29
|
+
nil
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func add(_ handler: @escaping () -> Void) -> UUID {
|
|
34
|
+
let token = UUID()
|
|
35
|
+
lock.lock()
|
|
36
|
+
handlers[token] = handler
|
|
37
|
+
lock.unlock()
|
|
38
|
+
return token
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
func remove(_ token: UUID) {
|
|
42
|
+
lock.lock()
|
|
43
|
+
handlers.removeValue(forKey: token)
|
|
44
|
+
lock.unlock()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func post() {
|
|
48
|
+
notifyHandlers()
|
|
49
|
+
CFNotificationCenterPostNotification(
|
|
50
|
+
CFNotificationCenterGetDarwinNotifyCenter(),
|
|
51
|
+
CFNotificationName(name),
|
|
52
|
+
nil,
|
|
53
|
+
nil,
|
|
54
|
+
true
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private static let didReceive: CFNotificationCallback = { _, observer, _, _, _ in
|
|
59
|
+
guard let observer else { return }
|
|
60
|
+
Unmanaged<VoltraDynamicLiveActivityRenderFailureNotifier>
|
|
61
|
+
.fromOpaque(observer)
|
|
62
|
+
.takeUnretainedValue()
|
|
63
|
+
.notifyHandlers()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
private func notifyHandlers() {
|
|
67
|
+
lock.lock()
|
|
68
|
+
let currentHandlers = Array(handlers.values)
|
|
69
|
+
lock.unlock()
|
|
70
|
+
currentHandlers.forEach { $0() }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Owns the production App Group queue and OS logging path. The extension only
|
|
75
|
+
/// records failures; the app's event bus drains them when JavaScript can listen.
|
|
76
|
+
public enum VoltraDynamicLiveActivityRenderFailureReporter {
|
|
77
|
+
private static var queue: VoltraDynamicLiveActivityRenderFailureQueue? {
|
|
78
|
+
guard
|
|
79
|
+
let group = VoltraConfig.groupIdentifier(),
|
|
80
|
+
let directoryURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: group)
|
|
81
|
+
else {
|
|
82
|
+
return nil
|
|
83
|
+
}
|
|
84
|
+
return VoltraDynamicLiveActivityRenderFailureQueue(
|
|
85
|
+
storage: VoltraDynamicLiveActivityRenderFailureFileStorage(directoryURL: directoryURL)
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@discardableResult
|
|
90
|
+
public static func record(activityName: String, definitionId: String, message: String) -> Bool {
|
|
91
|
+
let failure = VoltraDynamicLiveActivityRenderFailure(
|
|
92
|
+
activityName: activityName,
|
|
93
|
+
definitionId: definitionId,
|
|
94
|
+
message: message
|
|
95
|
+
)
|
|
96
|
+
let persisted = queue?.record(failure) ?? false
|
|
97
|
+
VoltraLogger.activity.error(
|
|
98
|
+
"[DynamicLiveActivity] activity=\(failure.activityName, privacy: .public) definitionId=\(failure.definitionId, privacy: .public) \(failure.message, privacy: .public)"
|
|
99
|
+
)
|
|
100
|
+
if persisted {
|
|
101
|
+
VoltraDynamicLiveActivityRenderFailureNotifier.shared.post()
|
|
102
|
+
} else {
|
|
103
|
+
VoltraLogger.event.error("Failed to persist Dynamic Live Activity render failure")
|
|
104
|
+
}
|
|
105
|
+
return persisted
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
public static func drain() -> [VoltraDynamicLiveActivityRenderFailure] {
|
|
109
|
+
queue?.drain() ?? []
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/// The returned token must be removed when the JS bridge listener goes away.
|
|
113
|
+
public static func observeChanges(_ handler: @escaping () -> Void) -> UUID {
|
|
114
|
+
VoltraDynamicLiveActivityRenderFailureNotifier.shared.add(handler)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
public static func removeChangeObserver(_ token: UUID) {
|
|
118
|
+
VoltraDynamicLiveActivityRenderFailureNotifier.shared.remove(token)
|
|
119
|
+
}
|
|
120
|
+
}
|