@use-voltra/ios-client 2.1.1 → 2.3.0

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.
Files changed (365) hide show
  1. package/README.md +61 -8
  2. package/Voltra.podspec +1 -0
  3. package/build/commonjs/dynamic-widget/api.js +15 -0
  4. package/build/commonjs/dynamic-widget/api.js.map +1 -0
  5. package/build/commonjs/events.js +23 -0
  6. package/build/commonjs/events.js.map +1 -1
  7. package/build/commonjs/index.js +74 -7
  8. package/build/commonjs/index.js.map +1 -1
  9. package/build/commonjs/live-activity/api.js +3 -1
  10. package/build/commonjs/live-activity/api.js.map +1 -1
  11. package/build/commonjs/live-activity/dynamic-api.js +173 -0
  12. package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
  13. package/build/commonjs/native/NativeVoltra.js.map +1 -1
  14. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
  15. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  16. package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
  17. package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
  18. package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
  19. package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
  20. package/build/commonjs/widgets/server-credentials.js +9 -0
  21. package/build/commonjs/widgets/server-credentials.js.map +1 -1
  22. package/build/commonjs/widgets/server-update.js +78 -0
  23. package/build/commonjs/widgets/server-update.js.map +1 -0
  24. package/build/module/dynamic-widget/api.js +10 -0
  25. package/build/module/dynamic-widget/api.js.map +1 -0
  26. package/build/module/events.js +23 -0
  27. package/build/module/events.js.map +1 -1
  28. package/build/module/index.js +4 -0
  29. package/build/module/index.js.map +1 -1
  30. package/build/module/live-activity/api.js +2 -2
  31. package/build/module/live-activity/api.js.map +1 -1
  32. package/build/module/live-activity/dynamic-api.js +167 -0
  33. package/build/module/live-activity/dynamic-api.js.map +1 -0
  34. package/build/module/native/NativeVoltra.js.map +1 -1
  35. package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
  36. package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  37. package/build/module/utils/enableWidgetHotReload.js +9 -11
  38. package/build/module/utils/enableWidgetHotReload.js.map +1 -1
  39. package/build/module/utils/useUpdateOnHMR.js +3 -7
  40. package/build/module/utils/useUpdateOnHMR.js.map +1 -1
  41. package/build/module/widgets/server-credentials.js +9 -0
  42. package/build/module/widgets/server-credentials.js.map +1 -1
  43. package/build/module/widgets/server-update.js +72 -0
  44. package/build/module/widgets/server-update.js.map +1 -0
  45. package/build/typescript/commonjs/dynamic-widget/api.d.ts +8 -0
  46. package/build/typescript/commonjs/dynamic-widget/api.d.ts.map +1 -0
  47. package/build/typescript/commonjs/events.d.ts +7 -0
  48. package/build/typescript/commonjs/events.d.ts.map +1 -1
  49. package/build/typescript/commonjs/index.d.ts +4 -0
  50. package/build/typescript/commonjs/index.d.ts.map +1 -1
  51. package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
  52. package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
  53. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
  54. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
  55. package/build/typescript/commonjs/native/NativeVoltra.d.ts +21 -0
  56. package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
  57. package/build/typescript/commonjs/types.d.ts +1 -1
  58. package/build/typescript/commonjs/types.d.ts.map +1 -1
  59. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  60. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  61. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
  62. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
  63. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
  64. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
  65. package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
  66. package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
  67. package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
  68. package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
  69. package/build/typescript/module/dynamic-widget/api.d.ts +8 -0
  70. package/build/typescript/module/dynamic-widget/api.d.ts.map +1 -0
  71. package/build/typescript/module/events.d.ts +7 -0
  72. package/build/typescript/module/events.d.ts.map +1 -1
  73. package/build/typescript/module/index.d.ts +4 -0
  74. package/build/typescript/module/index.d.ts.map +1 -1
  75. package/build/typescript/module/live-activity/api.d.ts +7 -0
  76. package/build/typescript/module/live-activity/api.d.ts.map +1 -1
  77. package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
  78. package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
  79. package/build/typescript/module/native/NativeVoltra.d.ts +21 -0
  80. package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
  81. package/build/typescript/module/types.d.ts +1 -1
  82. package/build/typescript/module/types.d.ts.map +1 -1
  83. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  84. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  85. package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
  86. package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
  87. package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
  88. package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
  89. package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
  90. package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
  91. package/build/typescript/module/widgets/server-update.d.ts +64 -0
  92. package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
  93. package/expo-plugin/build/cjs/constants.js +20 -1
  94. package/expo-plugin/build/cjs/constants.js.map +1 -1
  95. package/expo-plugin/build/cjs/index.js +9 -1
  96. package/expo-plugin/build/cjs/index.js.map +1 -1
  97. package/expo-plugin/build/cjs/ios/index.js +1 -0
  98. package/expo-plugin/build/cjs/ios/index.js.map +1 -1
  99. package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
  100. package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
  101. package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
  102. package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
  103. package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
  104. package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
  105. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
  106. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
  107. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
  108. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  109. package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
  110. package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
  111. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
  112. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
  113. package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
  114. package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
  115. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
  116. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
  117. package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
  118. package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
  119. package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
  120. package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
  121. package/expo-plugin/build/cjs/ios-widget/podfile.js +38 -12
  122. package/expo-plugin/build/cjs/ios-widget/podfile.js.map +1 -1
  123. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
  124. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
  125. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +194 -118
  126. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
  127. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js +30 -22
  128. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js.map +1 -1
  129. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js +50 -0
  130. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js.map +1 -0
  131. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js +45 -43
  132. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js.map +1 -1
  133. package/expo-plugin/build/cjs/ios-widget/xcode/index.js +93 -91
  134. package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
  135. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js +41 -39
  136. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js.map +1 -1
  137. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js +2 -2
  138. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js.map +1 -1
  139. package/expo-plugin/build/cjs/ios-widget/xcode/target.js +29 -63
  140. package/expo-plugin/build/cjs/ios-widget/xcode/target.js.map +1 -1
  141. package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
  142. package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
  143. package/expo-plugin/build/cjs/validation.js +54 -0
  144. package/expo-plugin/build/cjs/validation.js.map +1 -1
  145. package/expo-plugin/build/esm/constants.js +17 -0
  146. package/expo-plugin/build/esm/constants.js.map +1 -1
  147. package/expo-plugin/build/esm/index.js +9 -1
  148. package/expo-plugin/build/esm/index.js.map +1 -1
  149. package/expo-plugin/build/esm/ios/index.js +1 -0
  150. package/expo-plugin/build/esm/ios/index.js.map +1 -1
  151. package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
  152. package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
  153. package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
  154. package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
  155. package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
  156. package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
  157. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
  158. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
  159. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
  160. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  161. package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
  162. package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
  163. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
  164. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
  165. package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
  166. package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
  167. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
  168. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
  169. package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
  170. package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
  171. package/expo-plugin/build/esm/ios-widget/index.js +9 -3
  172. package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
  173. package/expo-plugin/build/esm/ios-widget/podfile.js +36 -11
  174. package/expo-plugin/build/esm/ios-widget/podfile.js.map +1 -1
  175. package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
  176. package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
  177. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +194 -117
  178. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
  179. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js +31 -22
  180. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js.map +1 -1
  181. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js +45 -0
  182. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js.map +1 -0
  183. package/expo-plugin/build/esm/ios-widget/xcode/groups.js +45 -42
  184. package/expo-plugin/build/esm/ios-widget/xcode/groups.js.map +1 -1
  185. package/expo-plugin/build/esm/ios-widget/xcode/index.js +97 -96
  186. package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
  187. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js +41 -39
  188. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js.map +1 -1
  189. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js +3 -2
  190. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js.map +1 -1
  191. package/expo-plugin/build/esm/ios-widget/xcode/target.js +28 -62
  192. package/expo-plugin/build/esm/ios-widget/xcode/target.js.map +1 -1
  193. package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
  194. package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
  195. package/expo-plugin/build/esm/validation.js +54 -1
  196. package/expo-plugin/build/esm/validation.js.map +1 -1
  197. package/expo-plugin/build/types/constants.d.ts +10 -1
  198. package/expo-plugin/build/types/constants.d.ts.map +1 -1
  199. package/expo-plugin/build/types/index.d.ts +1 -1
  200. package/expo-plugin/build/types/index.d.ts.map +1 -1
  201. package/expo-plugin/build/types/ios/index.d.ts +2 -0
  202. package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
  203. package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
  204. package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
  205. package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
  206. package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
  207. package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
  208. package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
  209. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
  210. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
  211. package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
  212. package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
  213. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
  214. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
  215. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
  216. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
  217. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
  218. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
  219. package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
  220. package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
  221. package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
  222. package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
  223. package/expo-plugin/build/types/ios-widget/podfile.d.ts +19 -1
  224. package/expo-plugin/build/types/ios-widget/podfile.d.ts.map +1 -1
  225. package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
  226. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +6 -8
  227. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
  228. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts +15 -10
  229. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts.map +1 -1
  230. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts +16 -0
  231. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts.map +1 -0
  232. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts +0 -4
  233. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts.map +1 -1
  234. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +23 -3
  235. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
  236. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts +16 -4
  237. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts.map +1 -1
  238. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts +0 -4
  239. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts.map +1 -1
  240. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts +11 -26
  241. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts.map +1 -1
  242. package/expo-plugin/build/types/types.d.ts +23 -4
  243. package/expo-plugin/build/types/types.d.ts.map +1 -1
  244. package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
  245. package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
  246. package/expo-plugin/build/types/validation.d.ts +2 -1
  247. package/expo-plugin/build/types/validation.d.ts.map +1 -1
  248. package/expo-plugin/src/constants.ts +24 -1
  249. package/expo-plugin/src/index.ts +14 -1
  250. package/expo-plugin/src/ios/index.ts +3 -0
  251. package/expo-plugin/src/ios/infoPlist.ts +33 -5
  252. package/expo-plugin/src/ios/serverUpdate.ts +29 -0
  253. package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
  254. package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
  255. package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
  256. package/expo-plugin/src/ios-widget/files/index.ts +15 -4
  257. package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
  258. package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
  259. package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
  260. package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
  261. package/expo-plugin/src/ios-widget/index.ts +13 -3
  262. package/expo-plugin/src/ios-widget/podfile.ts +46 -13
  263. package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
  264. package/expo-plugin/src/ios-widget/xcode/__fixtures__/fresh.pbxproj +197 -0
  265. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-existing-extension.pbxproj +321 -0
  266. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-per-config-signing.pbxproj +203 -0
  267. package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +363 -0
  268. package/expo-plugin/src/ios-widget/xcode/__tests__/pbxConsistency.ts +130 -0
  269. package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +210 -164
  270. package/expo-plugin/src/ios-widget/xcode/configurationList.ts +52 -35
  271. package/expo-plugin/src/ios-widget/xcode/fileReferences.ts +51 -0
  272. package/expo-plugin/src/ios-widget/xcode/groups.ts +52 -54
  273. package/expo-plugin/src/ios-widget/xcode/index.ts +124 -119
  274. package/expo-plugin/src/ios-widget/xcode/mainAppSettings.ts +59 -54
  275. package/expo-plugin/src/ios-widget/xcode/productFile.ts +3 -2
  276. package/expo-plugin/src/ios-widget/xcode/target.ts +31 -83
  277. package/expo-plugin/src/types.ts +28 -4
  278. package/expo-plugin/src/utils/urlScheme.ts +47 -17
  279. package/expo-plugin/src/validation.ts +76 -1
  280. package/ios/Package.swift +39 -4
  281. package/ios/VoltraWidget.podspec +1 -0
  282. package/ios/app/NativeVoltra.mm +126 -7
  283. package/ios/app/VoltraLiveActivityManager.swift +30 -0
  284. package/ios/app/VoltraLiveActivityService.swift +77 -5
  285. package/ios/app/VoltraModule.swift +117 -1
  286. package/ios/app/VoltraModuleImpl.swift +116 -2
  287. package/ios/app/VoltraViewComponentView.mm +3 -3
  288. package/ios/app/VoltraWidgetService.swift +147 -12
  289. package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
  290. package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
  291. package/ios/shared/DynamicWidgetPropsCodec.swift +51 -0
  292. package/ios/shared/DynamicWidgetPropsStorage.swift +53 -0
  293. package/ios/shared/DynamicWidgetPropsStore.swift +117 -0
  294. package/ios/shared/DynamicWidgetRenderCoordinator.swift +31 -0
  295. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
  296. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
  297. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
  298. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
  299. package/ios/shared/DynamicWidgetUpdater.swift +30 -0
  300. package/ios/shared/ServerWidgetContentResolver.swift +60 -0
  301. package/ios/shared/ServerWidgetResponseStore.swift +25 -0
  302. package/ios/shared/ShortNames.swift +6 -0
  303. package/ios/shared/VoltraConfig.swift +41 -0
  304. package/ios/shared/VoltraConstants.swift +19 -0
  305. package/ios/shared/VoltraElement.swift +127 -43
  306. package/ios/shared/VoltraEventBus.swift +52 -7
  307. package/ios/shared/VoltraJSRenderer.swift +93 -23
  308. package/ios/shared/VoltraNode.swift +7 -111
  309. package/ios/shared/VoltraRefreshIntent.swift +1 -1
  310. package/ios/shared/VoltraWidgetDefaults.swift +9 -1
  311. package/ios/shared/VoltraWidgetKind.swift +41 -0
  312. package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
  313. package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
  314. package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
  315. package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
  316. package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
  317. package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
  318. package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
  319. package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
  320. package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
  321. package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
  322. package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
  323. package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
  324. package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
  325. package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
  326. package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
  327. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
  328. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
  329. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
  330. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
  331. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
  332. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
  333. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
  334. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
  335. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
  336. package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
  337. package/ios/target/VoltraClientWidgetRuntime.swift +60 -10
  338. package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
  339. package/ios/target/VoltraHomeWidget.swift +91 -92
  340. package/ios/target/VoltraWidget.swift +3 -1
  341. package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
  342. package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
  343. package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +5 -3
  344. package/ios/ui/Helpers/VoltraProgressDriver.swift +6 -2
  345. package/ios/ui/Layout/VoltraFlexStackLayout.swift +9 -3
  346. package/ios/ui/Style/JSColorParser.swift +24 -8
  347. package/ios/ui/Style/JSGradientParser.swift +36 -12
  348. package/ios/ui/Style/JSStyleParser.swift +36 -12
  349. package/ios/ui/Style/LayoutStyle.swift +12 -4
  350. package/ios/ui/Views/VoltraChart.swift +160 -16
  351. package/ios/ui/Views/VoltraLinearGradient.swift +6 -2
  352. package/package.json +5 -5
  353. package/src/dynamic-widget/api.ts +25 -0
  354. package/src/events.ts +34 -0
  355. package/src/index.ts +23 -0
  356. package/src/live-activity/api.ts +2 -2
  357. package/src/live-activity/dynamic-api.ts +240 -0
  358. package/src/native/NativeVoltra.ts +22 -0
  359. package/src/types.ts +4 -0
  360. package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
  361. package/src/utils/enableWidgetHotReload.ts +9 -16
  362. package/src/utils/useUpdateOnHMR.ts +3 -12
  363. package/src/widgets/server-credentials.ts +8 -0
  364. package/src/widgets/server-update.ts +84 -0
  365. /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
@@ -0,0 +1,97 @@
1
+ import ActivityKit
2
+ import Foundation
3
+
4
+ /// Observes every concrete generated Dynamic Live Activity type. ActivityKit's
5
+ /// activity streams are type-specific, so the generated catalog invokes this
6
+ /// observer once for each bundled attributes type.
7
+ public actor VoltraDynamicLiveActivityObserver {
8
+ private let onActivityDiscovered: (@Sendable (String) -> Void)?
9
+ private let onTokenUpdated: (@Sendable (String, String) -> Void)?
10
+ private let onStateChanged: (@Sendable (String, String) -> Void)?
11
+
12
+ private var definitionTasks: [String: Task<Void, Never>] = [:]
13
+ private var tokenTasks: [String: Task<Void, Never>] = [:]
14
+ private var stateTasks: [String: Task<Void, Never>] = [:]
15
+
16
+ public init(
17
+ onActivityDiscovered: (@Sendable (String) -> Void)? = nil,
18
+ onTokenUpdated: (@Sendable (String, String) -> Void)? = nil,
19
+ onStateChanged: (@Sendable (String, String) -> Void)? = nil
20
+ ) {
21
+ self.onActivityDiscovered = onActivityDiscovered
22
+ self.onTokenUpdated = onTokenUpdated
23
+ self.onStateChanged = onStateChanged
24
+ }
25
+
26
+ public func observe<Attributes: VoltraDynamicLiveActivityDefinition>(_: Attributes.Type) {
27
+ let definitionId = Attributes.definitionId
28
+ guard definitionTasks[definitionId] == nil else { return }
29
+
30
+ definitionTasks[definitionId] = Task { [weak self] in
31
+ for activity in Activity<Attributes>.activities {
32
+ await self?.observe(activity, definitionId: definitionId)
33
+ }
34
+ for await activity in Activity<Attributes>.activityUpdates {
35
+ await self?.observe(activity, definitionId: definitionId)
36
+ }
37
+ await self?.removeDefinitionTask(for: definitionId)
38
+ }
39
+ }
40
+
41
+ public func stopObserving() {
42
+ for task in definitionTasks.values {
43
+ task.cancel()
44
+ }
45
+ for task in tokenTasks.values {
46
+ task.cancel()
47
+ }
48
+ for task in stateTasks.values {
49
+ task.cancel()
50
+ }
51
+ definitionTasks.removeAll()
52
+ tokenTasks.removeAll()
53
+ stateTasks.removeAll()
54
+ }
55
+
56
+ private func observe<Attributes: VoltraDynamicLiveActivityDefinition>(
57
+ _ activity: Activity<Attributes>,
58
+ definitionId: String
59
+ ) {
60
+ let key = "\(definitionId):\(activity.id)"
61
+ let name = activity.attributes.name
62
+ onActivityDiscovered?(activity.id)
63
+
64
+ if let onTokenUpdated, tokenTasks[key] == nil {
65
+ tokenTasks[key] = Task { [weak self] in
66
+ for await token in activity.pushTokenUpdates {
67
+ onTokenUpdated(name, token.hexString)
68
+ }
69
+ await self?.removeTokenTask(for: key)
70
+ }
71
+ }
72
+
73
+ if let onStateChanged, stateTasks[key] == nil {
74
+ stateTasks[key] = Task { [weak self] in
75
+ for await state in activity.activityStateUpdates {
76
+ onStateChanged(name, String(describing: state))
77
+ }
78
+ await self?.removeStateTask(for: key)
79
+ }
80
+ }
81
+ }
82
+
83
+ private func removeDefinitionTask(for definitionId: String) {
84
+ definitionTasks[definitionId]?.cancel()
85
+ definitionTasks.removeValue(forKey: definitionId)
86
+ }
87
+
88
+ private func removeTokenTask(for key: String) {
89
+ tokenTasks[key]?.cancel()
90
+ tokenTasks.removeValue(forKey: key)
91
+ }
92
+
93
+ private func removeStateTask(for key: String) {
94
+ stateTasks[key]?.cancel()
95
+ stateTasks.removeValue(forKey: key)
96
+ }
97
+ }
@@ -0,0 +1,105 @@
1
+ import ActivityKit
2
+ import Foundation
3
+
4
+ public struct VoltraDynamicLiveActivityCreateRequest {
5
+ public let definitionId: String
6
+ public let name: String
7
+ public let deepLinkUrl: String?
8
+ public let props: [String: VoltraDynamicLiveActivityJSONValue]
9
+ public let staleDate: Date?
10
+ public let relevanceScore: Double
11
+ public let pushType: PushType?
12
+ }
13
+
14
+ public struct VoltraDynamicLiveActivityUpdateRequest {
15
+ public let props: [String: VoltraDynamicLiveActivityJSONValue]
16
+ public let staleDate: Date?
17
+ public let relevanceScore: Double
18
+ }
19
+
20
+ public struct VoltraDynamicLiveActivityReference: Hashable {
21
+ public let id: String
22
+ public let name: String
23
+ public let definitionId: String
24
+ }
25
+
26
+ /// Generic ActivityKit operations called by the generated catalog. The catalog
27
+ /// supplies the concrete generated Attributes type without exposing it to the
28
+ /// legacy service or the React Native bridge.
29
+ public enum VoltraDynamicLiveActivityOperations {
30
+ public static func create<Attributes: VoltraDynamicLiveActivityDefinition>(
31
+ _: Attributes.Type,
32
+ request: VoltraDynamicLiveActivityCreateRequest
33
+ ) async throws -> VoltraDynamicLiveActivityReference {
34
+ let attributes = Attributes(name: request.name, deepLinkUrl: request.deepLinkUrl)
35
+ let state = VoltraDynamicLiveActivityContentState(props: request.props)
36
+ let activity = try Activity.request(
37
+ attributes: attributes,
38
+ content: ActivityContent(state: state, staleDate: request.staleDate, relevanceScore: request.relevanceScore),
39
+ pushType: request.pushType
40
+ )
41
+ return VoltraDynamicLiveActivityReference(
42
+ id: activity.id,
43
+ name: activity.attributes.name,
44
+ definitionId: Attributes.definitionId
45
+ )
46
+ }
47
+
48
+ public static func update<Attributes: VoltraDynamicLiveActivityDefinition>(
49
+ _: Attributes.Type,
50
+ byName name: String,
51
+ request: VoltraDynamicLiveActivityUpdateRequest
52
+ ) async -> Bool {
53
+ let activities = Activity<Attributes>.activities.filter { $0.attributes.name == name }
54
+ for activity in activities {
55
+ // State is deliberately replaced in full; V1 does not merge props.
56
+ await activity.update(ActivityContent(
57
+ state: VoltraDynamicLiveActivityContentState(props: request.props),
58
+ staleDate: request.staleDate,
59
+ relevanceScore: request.relevanceScore
60
+ ))
61
+ }
62
+ return !activities.isEmpty
63
+ }
64
+
65
+ public static func end<Attributes: VoltraDynamicLiveActivityDefinition>(
66
+ _: Attributes.Type,
67
+ byName name: String,
68
+ dismissalPolicy: ActivityUIDismissalPolicy
69
+ ) async -> Bool {
70
+ let activities = Activity<Attributes>.activities.filter { $0.attributes.name == name }
71
+ for activity in activities {
72
+ await activity.end(ActivityContent(state: activity.content.state, staleDate: nil), dismissalPolicy: dismissalPolicy)
73
+ }
74
+ return !activities.isEmpty
75
+ }
76
+
77
+ public static func endAll<Attributes: VoltraDynamicLiveActivityDefinition>(
78
+ _: Attributes.Type,
79
+ dismissalPolicy: ActivityUIDismissalPolicy
80
+ ) async {
81
+ for activity in Activity<Attributes>.activities {
82
+ await activity.end(ActivityContent(state: activity.content.state, staleDate: nil), dismissalPolicy: dismissalPolicy)
83
+ }
84
+ }
85
+
86
+ public static func activities<Attributes: VoltraDynamicLiveActivityDefinition>(
87
+ _: Attributes.Type
88
+ ) -> [VoltraDynamicLiveActivityReference] {
89
+ Activity<Attributes>.activities.map {
90
+ VoltraDynamicLiveActivityReference(id: $0.id, name: $0.attributes.name, definitionId: Attributes.definitionId)
91
+ }
92
+ }
93
+
94
+ public static func reload<Attributes: VoltraDynamicLiveActivityDefinition>(
95
+ _: Attributes.Type
96
+ ) async {
97
+ for activity in Activity<Attributes>.activities {
98
+ await activity.update(ActivityContent(
99
+ state: activity.content.state,
100
+ staleDate: activity.content.staleDate,
101
+ relevanceScore: activity.content.relevanceScore
102
+ ))
103
+ }
104
+ }
105
+ }
@@ -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
+ }