react-native-audio-browser 0.2.0-next.11 → 0.2.0-next.12

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 (333) hide show
  1. package/README.md +4 -4
  2. package/android/src/main/java/com/audiobrowser/AudioBrowser.kt +110 -25
  3. package/android/src/main/java/com/audiobrowser/browser/BrowserManager.kt +254 -188
  4. package/android/src/main/java/com/audiobrowser/browser/BrowserUrlResolution.kt +23 -3
  5. package/android/src/main/java/com/audiobrowser/browser/JsonModels.kt +77 -58
  6. package/android/src/main/java/com/audiobrowser/browser/SectionScope.kt +68 -26
  7. package/android/src/main/java/com/audiobrowser/browser/StyleResolver.kt +49 -0
  8. package/android/src/main/java/com/audiobrowser/extension/ResolvedTrackExt.kt +9 -7
  9. package/android/src/main/java/com/audiobrowser/extension/TappedTrack.kt +20 -0
  10. package/android/src/main/java/com/audiobrowser/extension/TrackIdentity.kt +15 -0
  11. package/android/src/main/java/com/audiobrowser/player/MediaSessionCallback.kt +120 -67
  12. package/android/src/main/java/com/audiobrowser/player/NowPlayingUpdater.kt +21 -24
  13. package/android/src/main/java/com/audiobrowser/player/PlaybackStateStore.kt +93 -73
  14. package/android/src/main/java/com/audiobrowser/player/Player.kt +48 -30
  15. package/android/src/main/java/com/audiobrowser/player/PlayerCapabilitiesExt.kt +2 -15
  16. package/android/src/main/java/com/audiobrowser/util/BrowserPathHelper.kt +44 -40
  17. package/android/src/main/java/com/audiobrowser/util/MediaExtrasBuilder.kt +28 -23
  18. package/android/src/main/java/com/audiobrowser/util/StyleWire.kt +35 -0
  19. package/android/src/main/java/com/audiobrowser/util/TrackFactory.kt +45 -53
  20. package/android/src/test/java/com/audiobrowser/TestFixtures.kt +26 -39
  21. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerFavoritesTest.kt +153 -0
  22. package/android/src/test/java/com/audiobrowser/browser/BrowserManagerTest.kt +228 -50
  23. package/android/src/test/java/com/audiobrowser/browser/BrowserUrlResolutionTest.kt +38 -0
  24. package/android/src/test/java/com/audiobrowser/browser/JsonArtworkTest.kt +1 -1
  25. package/android/src/test/java/com/audiobrowser/browser/JsonStyleTest.kt +91 -0
  26. package/android/src/test/java/com/audiobrowser/browser/SectionScopeTest.kt +128 -53
  27. package/android/src/test/java/com/audiobrowser/browser/StyleResolverTest.kt +87 -0
  28. package/android/src/test/java/com/audiobrowser/extension/TappedTrackTest.kt +43 -0
  29. package/android/src/test/java/com/audiobrowser/extension/TrackIdentityTest.kt +41 -0
  30. package/android/src/test/java/com/audiobrowser/player/CapabilityControlsTest.kt +1 -2
  31. package/android/src/test/java/com/audiobrowser/player/NowPlayingUpdaterTest.kt +42 -2
  32. package/android/src/test/java/com/audiobrowser/player/PlaybackStateStoreTrackJsonTest.kt +55 -0
  33. package/android/src/test/java/com/audiobrowser/util/BrowserPathHelperTest.kt +41 -16
  34. package/android/src/test/java/com/audiobrowser/util/MediaExtrasBuilderTest.kt +161 -0
  35. package/android/src/test/java/com/audiobrowser/util/TrackFactoryMediaIdTest.kt +7 -7
  36. package/ios/Browser/BrowserConfig.swift +102 -5
  37. package/ios/Browser/BrowserManager+URLResolution.swift +18 -14
  38. package/ios/Browser/BrowserManager.swift +131 -143
  39. package/ios/Browser/BrowserPathHelper.swift +44 -44
  40. package/ios/Browser/JsonModels.swift +187 -106
  41. package/ios/Browser/SearchDrillIn.swift +6 -6
  42. package/ios/Browser/SectionPresentation.swift +34 -0
  43. package/ios/Browser/SectionScope.swift +58 -24
  44. package/ios/Browser/StyleResolver.swift +47 -0
  45. package/ios/Browser/TabBarEntries.swift +2 -2
  46. package/ios/Browser/TrackIdTemplate.swift +37 -0
  47. package/ios/CarPlay/CarPlayArtworkResolver.swift +8 -7
  48. package/ios/CarPlay/CarPlayController.swift +105 -67
  49. package/ios/CarPlay/CarPlayImageLoader.swift +38 -18
  50. package/ios/CarPlay/CarPlayListItemFactory.swift +217 -84
  51. package/ios/CarPlay/CarPlayNowPlayingManager.swift +15 -12
  52. package/ios/Extension/Capability+RemoteCommand.swift +1 -1
  53. package/ios/Extension/PlayerCapabilities+Favorite.swift +1 -15
  54. package/ios/Extension/ResolvedTrack+Copying.swift +11 -15
  55. package/ios/Extension/Section+Copying.swift +31 -0
  56. package/ios/Extension/Track+Copying.swift +6 -12
  57. package/ios/Extension/Track+Identity.swift +18 -0
  58. package/ios/HybridAudioBrowser.swift +113 -35
  59. package/ios/Model/NitroTypeStubs.swift +46 -21
  60. package/ios/Model/PlayerUpdateOptions.swift +7 -0
  61. package/ios/Player/NowPlayingUpdater.swift +1 -1
  62. package/ios/Player/PlaybackCoordinator.swift +2 -2
  63. package/ios/Player/RetryManager.swift +4 -3
  64. package/ios/Tests/BrowserPathHelperTests.swift +50 -20
  65. package/ios/Tests/CarPlayArtworkResolverTests.swift +24 -41
  66. package/ios/Tests/JsonModelsTests.swift +75 -1
  67. package/ios/Tests/PlaybackStateStoreTests.swift +4 -3
  68. package/ios/Tests/ResolvedTrackCopyingTests.swift +37 -35
  69. package/ios/Tests/SearchDrillInTests.swift +16 -16
  70. package/ios/Tests/SectionPresentationTests.swift +61 -0
  71. package/ios/Tests/SectionScopeTests.swift +87 -55
  72. package/ios/Tests/StyleResolverTests.swift +67 -0
  73. package/ios/Tests/TabBarEntriesTests.swift +18 -18
  74. package/ios/Tests/TrackIdTemplateTests.swift +67 -0
  75. package/ios/Tests/TrackIdentityTests.swift +20 -0
  76. package/ios/Tests/TrackSelectorTests.swift +168 -33
  77. package/ios/TrackSelector.swift +45 -30
  78. package/ios/Util/ArtworkImageFetcher.swift +16 -1
  79. package/lib/commonjs/AudioBrowser.js +11 -0
  80. package/lib/commonjs/AudioBrowser.js.map +1 -1
  81. package/lib/commonjs/features/browser-config.js +11 -8
  82. package/lib/commonjs/features/browser-config.js.map +1 -1
  83. package/lib/commonjs/features/browser.js +2 -2
  84. package/lib/commonjs/features/favorites.js +13 -10
  85. package/lib/commonjs/features/favorites.js.map +1 -1
  86. package/lib/commonjs/features/playback/controls.js +4 -2
  87. package/lib/commonjs/features/playback/controls.js.map +1 -1
  88. package/lib/commonjs/features/playback/state.js +2 -3
  89. package/lib/commonjs/features/playback/state.js.map +1 -1
  90. package/lib/commonjs/features/player/options.js.map +1 -1
  91. package/lib/commonjs/features/player/setup.js +3 -1
  92. package/lib/commonjs/features/player/setup.js.map +1 -1
  93. package/lib/commonjs/features/player/validateOptions.js +2 -0
  94. package/lib/commonjs/features/player/validateOptions.js.map +1 -1
  95. package/lib/commonjs/features/queue/activeTrack.js +14 -3
  96. package/lib/commonjs/features/queue/activeTrack.js.map +1 -1
  97. package/lib/commonjs/features/queue/queue.js +11 -4
  98. package/lib/commonjs/features/queue/queue.js.map +1 -1
  99. package/lib/commonjs/utils/LazyNativeEmitter.js +21 -10
  100. package/lib/commonjs/utils/LazyNativeEmitter.js.map +1 -1
  101. package/lib/commonjs/utils/NativeUpdatedValue.js +31 -13
  102. package/lib/commonjs/utils/NativeUpdatedValue.js.map +1 -1
  103. package/lib/commonjs/utils/getTrackIdentity.js +24 -0
  104. package/lib/commonjs/utils/getTrackIdentity.js.map +1 -0
  105. package/lib/commonjs/utils/useNativeValueRefreshedBy.js +33 -0
  106. package/lib/commonjs/utils/useNativeValueRefreshedBy.js.map +1 -0
  107. package/lib/commonjs/web/NativeAudioBrowser.js +32 -24
  108. package/lib/commonjs/web/NativeAudioBrowser.js.map +1 -1
  109. package/lib/commonjs/web/browser/BrowserManager.js +144 -87
  110. package/lib/commonjs/web/browser/BrowserManager.js.map +1 -1
  111. package/lib/commonjs/web/browser/FavoriteManager.js +30 -21
  112. package/lib/commonjs/web/browser/FavoriteManager.js.map +1 -1
  113. package/lib/commonjs/web/browser/assertBrowsePageShape.js +2 -2
  114. package/lib/commonjs/web/browser/assertBrowsePageShape.js.map +1 -1
  115. package/lib/commonjs/web/browser/classifyTrackNavigation.js +9 -9
  116. package/lib/commonjs/web/browser/classifyTrackNavigation.js.map +1 -1
  117. package/lib/commonjs/web/browser/sections.js +89 -0
  118. package/lib/commonjs/web/browser/sections.js.map +1 -0
  119. package/lib/commonjs/web/player/NowPlayingManager.js +4 -3
  120. package/lib/commonjs/web/player/NowPlayingManager.js.map +1 -1
  121. package/lib/commonjs/web/util/BrowserPathHelper.js +56 -11
  122. package/lib/commonjs/web/util/BrowserPathHelper.js.map +1 -1
  123. package/lib/module/AudioBrowser.js +1 -0
  124. package/lib/module/AudioBrowser.js.map +1 -1
  125. package/lib/module/features/browser-config.js +11 -8
  126. package/lib/module/features/browser-config.js.map +1 -1
  127. package/lib/module/features/browser.js +2 -2
  128. package/lib/module/features/favorites.js +13 -10
  129. package/lib/module/features/favorites.js.map +1 -1
  130. package/lib/module/features/playback/controls.js +4 -2
  131. package/lib/module/features/playback/controls.js.map +1 -1
  132. package/lib/module/features/playback/state.js +2 -3
  133. package/lib/module/features/playback/state.js.map +1 -1
  134. package/lib/module/features/player/options.js.map +1 -1
  135. package/lib/module/features/player/setup.js +3 -1
  136. package/lib/module/features/player/setup.js.map +1 -1
  137. package/lib/module/features/player/validateOptions.js +2 -0
  138. package/lib/module/features/player/validateOptions.js.map +1 -1
  139. package/lib/module/features/queue/activeTrack.js +14 -3
  140. package/lib/module/features/queue/activeTrack.js.map +1 -1
  141. package/lib/module/features/queue/queue.js +11 -4
  142. package/lib/module/features/queue/queue.js.map +1 -1
  143. package/lib/module/utils/LazyNativeEmitter.js +21 -10
  144. package/lib/module/utils/LazyNativeEmitter.js.map +1 -1
  145. package/lib/module/utils/NativeUpdatedValue.js +31 -13
  146. package/lib/module/utils/NativeUpdatedValue.js.map +1 -1
  147. package/lib/module/utils/getTrackIdentity.js +20 -0
  148. package/lib/module/utils/getTrackIdentity.js.map +1 -0
  149. package/lib/module/utils/useNativeValueRefreshedBy.js +29 -0
  150. package/lib/module/utils/useNativeValueRefreshedBy.js.map +1 -0
  151. package/lib/module/web/NativeAudioBrowser.js +32 -24
  152. package/lib/module/web/NativeAudioBrowser.js.map +1 -1
  153. package/lib/module/web/browser/BrowserManager.js +144 -87
  154. package/lib/module/web/browser/BrowserManager.js.map +1 -1
  155. package/lib/module/web/browser/FavoriteManager.js +31 -21
  156. package/lib/module/web/browser/FavoriteManager.js.map +1 -1
  157. package/lib/module/web/browser/assertBrowsePageShape.js +2 -2
  158. package/lib/module/web/browser/assertBrowsePageShape.js.map +1 -1
  159. package/lib/module/web/browser/classifyTrackNavigation.js +9 -9
  160. package/lib/module/web/browser/classifyTrackNavigation.js.map +1 -1
  161. package/lib/module/web/browser/sections.js +83 -0
  162. package/lib/module/web/browser/sections.js.map +1 -0
  163. package/lib/module/web/player/NowPlayingManager.js +4 -3
  164. package/lib/module/web/player/NowPlayingManager.js.map +1 -1
  165. package/lib/module/web/util/BrowserPathHelper.js +56 -11
  166. package/lib/module/web/util/BrowserPathHelper.js.map +1 -1
  167. package/lib/typescript/src/AudioBrowser.d.ts +1 -0
  168. package/lib/typescript/src/AudioBrowser.d.ts.map +1 -1
  169. package/lib/typescript/src/features/browser-config.d.ts.map +1 -1
  170. package/lib/typescript/src/features/browser.d.ts +2 -2
  171. package/lib/typescript/src/features/favorites.d.ts +13 -10
  172. package/lib/typescript/src/features/favorites.d.ts.map +1 -1
  173. package/lib/typescript/src/features/playback/controls.d.ts +4 -2
  174. package/lib/typescript/src/features/playback/controls.d.ts.map +1 -1
  175. package/lib/typescript/src/features/playback/state.d.ts +2 -3
  176. package/lib/typescript/src/features/playback/state.d.ts.map +1 -1
  177. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts +1 -1
  178. package/lib/typescript/src/features/playback/trackPlaybackTime.d.ts.map +1 -1
  179. package/lib/typescript/src/features/player/options.d.ts +21 -19
  180. package/lib/typescript/src/features/player/options.d.ts.map +1 -1
  181. package/lib/typescript/src/features/player/setup.d.ts.map +1 -1
  182. package/lib/typescript/src/features/player/validateOptions.d.ts +1 -8
  183. package/lib/typescript/src/features/player/validateOptions.d.ts.map +1 -1
  184. package/lib/typescript/src/features/queue/activeTrack.d.ts +8 -1
  185. package/lib/typescript/src/features/queue/activeTrack.d.ts.map +1 -1
  186. package/lib/typescript/src/features/queue/queue.d.ts +5 -2
  187. package/lib/typescript/src/features/queue/queue.d.ts.map +1 -1
  188. package/lib/typescript/src/types/browser-native.d.ts +2 -2
  189. package/lib/typescript/src/types/browser-native.d.ts.map +1 -1
  190. package/lib/typescript/src/types/browser-nodes.d.ts +260 -127
  191. package/lib/typescript/src/types/browser-nodes.d.ts.map +1 -1
  192. package/lib/typescript/src/types/browser.d.ts +33 -53
  193. package/lib/typescript/src/types/browser.d.ts.map +1 -1
  194. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts +16 -17
  195. package/lib/typescript/src/utils/LazyNativeEmitter.d.ts.map +1 -1
  196. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts +24 -19
  197. package/lib/typescript/src/utils/NativeUpdatedValue.d.ts.map +1 -1
  198. package/lib/typescript/src/utils/getTrackIdentity.d.ts +18 -0
  199. package/lib/typescript/src/utils/getTrackIdentity.d.ts.map +1 -0
  200. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts +21 -0
  201. package/lib/typescript/src/utils/useNativeValueRefreshedBy.d.ts.map +1 -0
  202. package/nitrogen/generated/android/AudioBrowser+autolinking.cmake +0 -1
  203. package/nitrogen/generated/android/c++/{JFavoritesMatchMode.hpp → JArtworkRendering.hpp} +15 -15
  204. package/nitrogen/generated/android/c++/JArtworkRequestConfig.hpp +4 -3
  205. package/nitrogen/generated/android/c++/JBrowseResult.hpp +10 -4
  206. package/nitrogen/generated/android/c++/JCarPlayPlayingIndicatorLocation.hpp +58 -0
  207. package/nitrogen/generated/android/c++/JFavoriteChangedEvent.hpp +4 -3
  208. package/nitrogen/generated/android/c++/JFormatNowPlayingParams.hpp +4 -3
  209. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_RequestConfig___Track.hpp +4 -3
  210. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_NowPlayingUpdate____FormatNowPlayingParams.hpp +4 -3
  211. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__optional_std__string____Track.hpp +4 -3
  212. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_RequestConfig_____Track.hpp +4 -3
  213. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_std__vector_Track______SearchParams.hpp +4 -2
  214. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__shared_ptr_Promise_void_____TrackLoadEvent.hpp +4 -2
  215. package/nitrogen/generated/android/c++/JFunc_std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError__std__shared_ptr_Promise_std__variant_ResolvedTrack__BrowseError_______BrowserSourceCallbackParam.hpp +10 -4
  216. package/nitrogen/generated/android/c++/JFunc_void_FavoriteChangedEvent.hpp +4 -3
  217. package/nitrogen/generated/android/c++/JFunc_void_Options.hpp +2 -5
  218. package/nitrogen/generated/android/c++/JFunc_void_PlaybackActiveTrackChangedEvent.hpp +4 -3
  219. package/nitrogen/generated/android/c++/JFunc_void_std__optional_ResolvedTrack_.hpp +9 -3
  220. package/nitrogen/generated/android/c++/JFunc_void_std__vector_Track_.hpp +4 -2
  221. package/nitrogen/generated/android/c++/JHybridAudioBrowserSpec.cpp +21 -14
  222. package/nitrogen/generated/android/c++/JIOSOptions.hpp +9 -3
  223. package/nitrogen/generated/android/c++/JMediaRequestConfig.hpp +4 -3
  224. package/nitrogen/generated/android/c++/JNativeBrowserConfiguration.hpp +16 -10
  225. package/nitrogen/generated/android/c++/JNativeRouteEntry.hpp +8 -2
  226. package/nitrogen/generated/android/c++/JNativeSetupPlayerOptions.hpp +6 -7
  227. package/nitrogen/generated/android/c++/JNativeUpdateOptions.hpp +2 -5
  228. package/nitrogen/generated/android/c++/JNitroIOSUpdateOptions.hpp +9 -3
  229. package/nitrogen/generated/android/c++/JOptions.hpp +2 -5
  230. package/nitrogen/generated/android/c++/JPlaybackActiveTrackChangedEvent.hpp +4 -3
  231. package/nitrogen/generated/android/c++/JPlayerCapabilities.hpp +5 -11
  232. package/nitrogen/generated/android/c++/JResolvedTrack.hpp +49 -51
  233. package/nitrogen/generated/android/c++/JSection.hpp +114 -0
  234. package/nitrogen/generated/android/c++/JSectionStyle.hpp +69 -0
  235. package/nitrogen/generated/android/c++/JStyleDisplay.hpp +58 -0
  236. package/nitrogen/generated/android/c++/JTrack.hpp +19 -48
  237. package/nitrogen/generated/android/c++/JTrackLoadEvent.hpp +4 -2
  238. package/nitrogen/generated/android/c++/JTrackStyle.hpp +25 -18
  239. package/nitrogen/generated/android/c++/JVariant_ResolvedTrack_BrowseError_Promise_BrowseResult_.hpp +10 -4
  240. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoritesMatchMode.kt → ArtworkRendering.kt} +5 -5
  241. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/CarPlayPlayingIndicatorLocation.kt +23 -0
  242. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/IOSOptions.kt +6 -3
  243. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NativeBrowserConfiguration.kt +5 -5
  244. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/NitroIOSUpdateOptions.kt +6 -3
  245. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/PlayerCapabilities.kt +2 -2
  246. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ResolvedTrack.kt +12 -18
  247. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Section.kt +50 -0
  248. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/{FavoriteConfig.kt → SectionStyle.kt} +12 -6
  249. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/StyleDisplay.kt +23 -0
  250. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Track.kt +6 -15
  251. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/TrackStyle.kt +23 -5
  252. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Bridge.hpp +104 -82
  253. package/nitrogen/generated/ios/AudioBrowser-Swift-Cxx-Umbrella.hpp +16 -10
  254. package/nitrogen/generated/ios/c++/HybridAudioBrowserSpecSwift.hpp +16 -10
  255. package/nitrogen/generated/ios/swift/ArtworkRendering.swift +40 -0
  256. package/nitrogen/generated/ios/swift/CarPlayPlayingIndicatorLocation.swift +40 -0
  257. package/nitrogen/generated/ios/swift/IOSOptions.swift +7 -2
  258. package/nitrogen/generated/ios/swift/NativeBrowserConfiguration.swift +5 -5
  259. package/nitrogen/generated/ios/swift/NitroIOSUpdateOptions.swift +12 -1
  260. package/nitrogen/generated/ios/swift/PlayerCapabilities.swift +8 -27
  261. package/nitrogen/generated/ios/swift/ResolvedTrack.swift +54 -83
  262. package/nitrogen/generated/ios/swift/Section.swift +100 -0
  263. package/nitrogen/generated/ios/swift/SectionStyle.swift +64 -0
  264. package/nitrogen/generated/ios/swift/StyleDisplay.swift +40 -0
  265. package/nitrogen/generated/ios/swift/Track.swift +20 -73
  266. package/nitrogen/generated/ios/swift/TrackStyle.swift +28 -22
  267. package/nitrogen/generated/shared/c++/{FavoritesMatchMode.hpp → ArtworkRendering.hpp} +17 -17
  268. package/nitrogen/generated/shared/c++/CarPlayPlayingIndicatorLocation.hpp +76 -0
  269. package/nitrogen/generated/shared/c++/IOSOptions.hpp +9 -2
  270. package/nitrogen/generated/shared/c++/NativeBrowserConfiguration.hpp +6 -6
  271. package/nitrogen/generated/shared/c++/NitroIOSUpdateOptions.hpp +9 -2
  272. package/nitrogen/generated/shared/c++/PlayerCapabilities.hpp +6 -9
  273. package/nitrogen/generated/shared/c++/ResolvedTrack.hpp +30 -38
  274. package/nitrogen/generated/shared/c++/Section.hpp +106 -0
  275. package/nitrogen/generated/shared/c++/SectionStyle.hpp +96 -0
  276. package/nitrogen/generated/shared/c++/StyleDisplay.hpp +76 -0
  277. package/nitrogen/generated/shared/c++/Track.hpp +15 -31
  278. package/nitrogen/generated/shared/c++/TrackStyle.hpp +50 -34
  279. package/package.json +1 -1
  280. package/src/AudioBrowser.ts +1 -0
  281. package/src/features/browser-config.test.ts +21 -6
  282. package/src/features/browser-config.ts +11 -8
  283. package/src/features/browser.test.ts +1 -1
  284. package/src/features/browser.ts +2 -2
  285. package/src/features/favorites.ts +13 -10
  286. package/src/features/hook-wiring.test.ts +63 -15
  287. package/src/features/playback/controls.ts +4 -2
  288. package/src/features/playback/state.ts +2 -3
  289. package/src/features/playback/trackPlaybackTime.ts +1 -1
  290. package/src/features/player/options.ts +27 -19
  291. package/src/features/player/setup.test.ts +12 -3
  292. package/src/features/player/setup.ts +3 -1
  293. package/src/features/player/validateOptions.ts +2 -0
  294. package/src/features/queue/activeTrack.ts +14 -3
  295. package/src/features/queue/queue.ts +11 -4
  296. package/src/nitro-stub-drift.test.ts +40 -0
  297. package/src/public-surface.test.ts +40 -0
  298. package/src/specs/audio-browser.nitro.ts +4 -3
  299. package/src/style-resolution.test.ts +139 -0
  300. package/src/types/browser-native.ts +2 -2
  301. package/src/types/browser-nodes.ts +268 -136
  302. package/src/types/browser.ts +34 -55
  303. package/src/utils/LazyNativeEmitter.ts +20 -10
  304. package/src/utils/NativeUpdatedValue.ts +29 -13
  305. package/src/utils/getTrackIdentity.ts +20 -0
  306. package/src/utils/useNativeValueRefreshedBy.ts +38 -0
  307. package/src/web/NativeAudioBrowser.ts +37 -22
  308. package/src/web/browser/BrowserManager.ts +176 -95
  309. package/src/web/browser/FavoriteManager.test.ts +84 -0
  310. package/src/web/browser/FavoriteManager.ts +28 -23
  311. package/src/web/browser/assertBrowsePageShape.test.ts +1 -1
  312. package/src/web/browser/assertBrowsePageShape.ts +5 -3
  313. package/src/web/browser/classifyTrackNavigation.test.ts +10 -8
  314. package/src/web/browser/classifyTrackNavigation.ts +9 -9
  315. package/src/web/browser/parseSearchResponse.test.ts +8 -2
  316. package/src/web/browser/sections.test.ts +91 -0
  317. package/src/web/browser/sections.ts +72 -0
  318. package/src/web/player/NowPlayingManager.ts +4 -3
  319. package/src/web/util/BrowserPathHelper.test.ts +52 -0
  320. package/src/web/util/BrowserPathHelper.ts +61 -13
  321. package/ios/CarPlay/ImageRowItem+CarPlay.swift +0 -19
  322. package/nitrogen/generated/android/c++/JFavoriteConfig.hpp +0 -58
  323. package/nitrogen/generated/android/c++/JImageRowItem.hpp +0 -105
  324. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.cpp +0 -26
  325. package/nitrogen/generated/android/c++/JVariant_Boolean_FavoriteConfig.hpp +0 -71
  326. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/ImageRowItem.kt +0 -68
  327. package/nitrogen/generated/android/kotlin/com/margelo/nitro/audiobrowser/Variant_Boolean_FavoriteConfig.kt +0 -53
  328. package/nitrogen/generated/ios/swift/FavoriteConfig.swift +0 -29
  329. package/nitrogen/generated/ios/swift/FavoritesMatchMode.swift +0 -40
  330. package/nitrogen/generated/ios/swift/ImageRowItem.swift +0 -195
  331. package/nitrogen/generated/ios/swift/Variant_Bool_FavoriteConfig.swift +0 -18
  332. package/nitrogen/generated/shared/c++/FavoriteConfig.hpp +0 -84
  333. package/nitrogen/generated/shared/c++/ImageRowItem.hpp +0 -129
@@ -0,0 +1,161 @@
1
+ package com.audiobrowser.util
2
+
3
+ import androidx.media.utils.MediaConstants
4
+ import com.audiobrowser.TestFixtures
5
+ import com.audiobrowser.browser.styleResolvedSections
6
+ import com.margelo.nitro.audiobrowser.SectionStyle
7
+ import com.margelo.nitro.audiobrowser.StyleDisplay
8
+ import com.margelo.nitro.audiobrowser.TrackStyle
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertFalse
11
+ import org.junit.Test
12
+ import org.junit.runner.RunWith
13
+ import org.robolectric.RobolectricTestRunner
14
+
15
+ /**
16
+ * The extras are where a section survives the flat MediaBrowser protocol (ADR 0010): the owning
17
+ * section stamps the Android Auto group-title header hint and the per-item display hint. Per-item
18
+ * hints derive from the section resolution; a track's own `display` is positional — the page
19
+ * promise a browsable track emits as the parent-level hint, never its own tile shape (ADR 0011).
20
+ */
21
+ @RunWith(RobolectricTestRunner::class)
22
+ class MediaExtrasBuilderTest {
23
+ private val track = TestFixtures.track()
24
+
25
+ private fun sectionStyle(display: StyleDisplay?) =
26
+ SectionStyle(display = display, artworkRendering = null, gridWrap = null)
27
+
28
+ @Test
29
+ fun `owning section title stamps the group-title hint`() {
30
+ val section = TestFixtures.section(arrayOf(track), title = "Popular")
31
+ val extras = MediaExtrasBuilder.build(track, section)
32
+ assertEquals(
33
+ "Popular",
34
+ extras.getString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE),
35
+ )
36
+ }
37
+
38
+ @Test
39
+ fun `an untitled section stamps no group-title hint`() {
40
+ val section = TestFixtures.section(arrayOf(track))
41
+ val extras = MediaExtrasBuilder.build(track, section)
42
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE))
43
+ }
44
+
45
+ @Test
46
+ fun `grid section display stamps the grid item hint`() {
47
+ val section = TestFixtures.section(arrayOf(track), style = sectionStyle(StyleDisplay.GRID))
48
+ val extras = MediaExtrasBuilder.build(track, section)
49
+ assertEquals(
50
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
51
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
52
+ )
53
+ }
54
+
55
+ @Test
56
+ fun `list section display stamps the list item hint`() {
57
+ val section = TestFixtures.section(arrayOf(track), style = sectionStyle(StyleDisplay.LIST))
58
+ val extras = MediaExtrasBuilder.build(track, section)
59
+ assertEquals(
60
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM,
61
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
62
+ )
63
+ }
64
+
65
+ @Test
66
+ fun `a track's own display never sets its item hint - display is positional`() {
67
+ val gridPromiseTrack =
68
+ track.copy(style = TrackStyle(display = StyleDisplay.GRID, artworkRendering = null))
69
+ val section = TestFixtures.section(arrayOf(gridPromiseTrack), style = null)
70
+ val extras = MediaExtrasBuilder.build(gridPromiseTrack, section)
71
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
72
+ }
73
+
74
+ @Test
75
+ fun `a browsable track's declared display emits the parent-level promise`() {
76
+ val handle =
77
+ TestFixtures.browseTrack()
78
+ .copy(style = TrackStyle(display = StyleDisplay.GRID, artworkRendering = null))
79
+ val extras = MediaExtrasBuilder.build(handle)
80
+ val expected = MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
81
+ assertEquals(
82
+ expected,
83
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE),
84
+ )
85
+ assertEquals(
86
+ expected,
87
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE),
88
+ )
89
+ }
90
+
91
+ @Test
92
+ fun `an undeclared promise emits no parent-level hint - never derived`() {
93
+ val handle = TestFixtures.browseTrack()
94
+ val extras = MediaExtrasBuilder.build(handle)
95
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE))
96
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE))
97
+ }
98
+
99
+ @Test
100
+ fun `a playable track's display is inert - playables open no page`() {
101
+ val playable =
102
+ track.copy(style = TrackStyle(display = StyleDisplay.GRID, artworkRendering = null))
103
+ val extras = MediaExtrasBuilder.build(playable)
104
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE))
105
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE))
106
+ }
107
+
108
+ @Test
109
+ fun `no style anywhere stamps no style hint`() {
110
+ val extras = MediaExtrasBuilder.build(track, TestFixtures.section(arrayOf(track)))
111
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
112
+ }
113
+
114
+ @Test
115
+ fun `no section (queue and now-playing surfaces) stamps neither section hint`() {
116
+ val extras = MediaExtrasBuilder.build(track)
117
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE))
118
+ assertFalse(extras.containsKey(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM))
119
+ }
120
+
121
+ @Test
122
+ fun `the view-all navigation track carries the section's declared display as its promise`() {
123
+ // A "view all" page has no consumer-authored handle — the synthetic
124
+ // navigation track projects the section's declared block, the only
125
+ // declared promise that page can ever have.
126
+ val section =
127
+ TestFixtures.section(
128
+ arrayOf(track),
129
+ title = "Popular",
130
+ path = "/popular",
131
+ style = SectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = false),
132
+ )
133
+ val navigation = TrackFactory.navigationTrack(section)
134
+ assertEquals(StyleDisplay.GRID, navigation.style?.display)
135
+
136
+ val extras = MediaExtrasBuilder.build(navigation)
137
+ assertEquals(
138
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
139
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE),
140
+ )
141
+ }
142
+
143
+ @Test
144
+ fun `an unstyled section under a grid page stamps the grid hint end to end`() {
145
+ // The wire that makes ResolvedTrack.style real: page block ->
146
+ // styleResolvedSections fold -> per-item hint. Without the fold, every
147
+ // narrower unit test stays green while the page tier goes dark.
148
+ val page =
149
+ TestFixtures.resolvedTrack(
150
+ style = SectionStyle(display = StyleDisplay.GRID, artworkRendering = null, gridWrap = null),
151
+ sections = arrayOf(TestFixtures.section(arrayOf(track))),
152
+ )
153
+
154
+ val folded = page.styleResolvedSections()!!.single()
155
+ val extras = MediaExtrasBuilder.build(track, folded)
156
+ assertEquals(
157
+ MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM,
158
+ extras.getInt(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM),
159
+ )
160
+ }
161
+ }
@@ -22,27 +22,27 @@ class TrackFactoryMediaIdTest {
22
22
  }
23
23
 
24
24
  @Test
25
- fun `stable id wins over the contextual url for playable tracks`() {
25
+ fun `stable id wins over the contextual path for playable tracks`() {
26
26
  val track =
27
27
  TestFixtures.track(id = "abc123", src = "/listen/abc123/channel.mp3")
28
- .copy(url = "/home?__trackId=%2Flisten%2Fabc123%2Fchannel.mp3")
28
+ .copy(path = "/home?__trackId=%2Flisten%2Fabc123%2Fchannel.mp3")
29
29
  assertEquals("abc123", TrackFactory.toMedia3(track).mediaId)
30
30
  }
31
31
 
32
32
  @Test
33
- fun `playable track without id falls back to url then src`() {
34
- val withUrl = TestFixtures.track(src = "/a.mp3").copy(url = "/home?__trackId=%2Fa.mp3")
35
- assertEquals("/home?__trackId=%2Fa.mp3", TrackFactory.toMedia3(withUrl).mediaId)
33
+ fun `playable track without id falls back to path then src`() {
34
+ val withPath = TestFixtures.track(src = "/a.mp3").copy(path = "/home?__trackId=%2Fa.mp3")
35
+ assertEquals("/home?__trackId=%2Fa.mp3", TrackFactory.toMedia3(withPath).mediaId)
36
36
 
37
37
  val srcOnly = TestFixtures.track(src = "/a.mp3")
38
38
  assertEquals("/a.mp3", TrackFactory.toMedia3(srcOnly).mediaId)
39
39
  }
40
40
 
41
41
  @Test
42
- fun `browsable-only track keeps url as mediaId even with an id`() {
42
+ fun `browsable-only track keeps path as mediaId even with an id`() {
43
43
  // Navigation parentIds must stay resolvable paths, so a container's id is
44
44
  // never its mediaId.
45
- val track = TestFixtures.browseTrack(url = "/stations").copy(id = "abc123")
45
+ val track = TestFixtures.browseTrack(path = "/stations").copy(id = "abc123")
46
46
  assertEquals("/stations", TrackFactory.toMedia3(track).mediaId)
47
47
  }
48
48
 
@@ -48,9 +48,9 @@ struct BrowserConfig {
48
48
  let carPlayLoadingTitle: String?
49
49
 
50
50
  /// Resolves a browse path for the CarPlay Now Playing album line when the
51
- /// active track has no albumUrl. Invoked on track changes (not at tap) so
51
+ /// active track has no albumPath. Invoked on track changes (not at tap) so
52
52
  /// the album line only becomes tappable when a destination exists.
53
- let resolveAlbumUrl: ((_ track: Track) -> Promise<String?>)?
53
+ let resolveAlbumPath: ((_ track: Track) -> Promise<String?>)?
54
54
 
55
55
  /// Custom handler for track load events (overrides default load behavior)
56
56
  let handleTrackLoad: ((_ event: TrackLoadEvent) -> Promise<Promise<Void>>)?
@@ -72,7 +72,7 @@ struct BrowserConfig {
72
72
  handleTrackLoad: ((_ event: TrackLoadEvent) -> Promise<Promise<Void>>)? = nil,
73
73
  androidControllerOfflineError: Bool = true,
74
74
  carPlayLoadingTitle: String? = nil,
75
- resolveAlbumUrl: ((_ track: Track) -> Promise<String?>)? = nil,
75
+ resolveAlbumPath: ((_ track: Track) -> Promise<String?>)? = nil,
76
76
  formatNavigationError: ((_ params: FormatNavigationErrorParams) -> Promise<FormattedNavigationError?>)? = nil,
77
77
  ) {
78
78
  self.request = request
@@ -87,7 +87,7 @@ struct BrowserConfig {
87
87
  self.handleTrackLoad = handleTrackLoad
88
88
  self.androidControllerOfflineError = androidControllerOfflineError
89
89
  self.carPlayLoadingTitle = carPlayLoadingTitle
90
- self.resolveAlbumUrl = resolveAlbumUrl
90
+ self.resolveAlbumPath = resolveAlbumPath
91
91
  self.formatNavigationError = formatNavigationError
92
92
  }
93
93
 
@@ -105,10 +105,31 @@ struct BrowserConfig {
105
105
  handleTrackLoad = config.handleTrackLoad
106
106
  androidControllerOfflineError = config.androidControllerOfflineError ?? true
107
107
  carPlayLoadingTitle = config.carPlayLoadingTitle
108
- resolveAlbumUrl = config.resolveAlbumUrl
108
+ resolveAlbumPath = config.resolveAlbumPath
109
109
  formatNavigationError = config.formatNavigationError
110
110
  }
111
111
 
112
+ /// A copy of this config with every JS-provided function dropped — at every
113
+ /// level, not just the top; each nested type's strip below nils its own —
114
+ /// keeping the static fields (URLs, headers, routes' shape). Makes the config
115
+ /// safe to hold after its JS runtime is gone: callers take their
116
+ /// static/built-in fallback paths (a callback-only route degrades to its
117
+ /// no-content path). See `HybridAudioBrowser.dropStaleJSCallbacks` for why
118
+ /// invoking a stale callback is fatal.
119
+ func strippingJSCallbacks() -> BrowserConfig {
120
+ BrowserConfig(
121
+ request: request?.strippingJSCallbacks(),
122
+ browse: browse?.strippingJSCallbacks(),
123
+ media: media?.strippingJSCallbacks(),
124
+ artwork: artwork?.strippingJSCallbacks(),
125
+ nowPlayingArtwork: nowPlayingArtwork?.strippingJSCallbacks(),
126
+ routes: routes?.map { $0.strippingJSCallbacks() },
127
+ singleTrack: singleTrack,
128
+ androidControllerOfflineError: androidControllerOfflineError,
129
+ carPlayLoadingTitle: carPlayLoadingTitle,
130
+ )
131
+ }
132
+
112
133
  // MARK: - Track Load Handler
113
134
 
114
135
  private static let logger = Logger(subsystem: "com.audiobrowser", category: "BrowserConfig")
@@ -139,3 +160,79 @@ struct BrowserConfig {
139
160
  // MARK: - NativeRouteEntry + RouteEntry conformance
140
161
 
141
162
  extension NativeRouteEntry: RouteEntry {}
163
+
164
+ // MARK: - Deep JS-callback stripping
165
+
166
+ // The generated structs are get-only from Swift, so each strip rebuilds the
167
+ // value through the memberwise initializer with the function fields nil'd.
168
+
169
+ private extension TransformableRequestConfig {
170
+ func strippingJSCallbacks() -> TransformableRequestConfig {
171
+ TransformableRequestConfig(
172
+ transform: nil,
173
+ transformSync: nil,
174
+ method: method,
175
+ path: path,
176
+ baseUrl: baseUrl,
177
+ headers: headers,
178
+ query: query,
179
+ body: body,
180
+ contentType: contentType,
181
+ userAgent: userAgent,
182
+ )
183
+ }
184
+ }
185
+
186
+ private extension MediaRequestConfig {
187
+ func strippingJSCallbacks() -> MediaRequestConfig {
188
+ MediaRequestConfig(
189
+ resolve: nil,
190
+ resolveSync: nil,
191
+ transform: nil,
192
+ transformSync: nil,
193
+ method: method,
194
+ path: path,
195
+ baseUrl: baseUrl,
196
+ headers: headers,
197
+ query: query,
198
+ body: body,
199
+ contentType: contentType,
200
+ userAgent: userAgent,
201
+ )
202
+ }
203
+ }
204
+
205
+ private extension ArtworkRequestConfig {
206
+ func strippingJSCallbacks() -> ArtworkRequestConfig {
207
+ ArtworkRequestConfig(
208
+ resolve: nil,
209
+ resolveSync: nil,
210
+ transform: nil,
211
+ transformSync: nil,
212
+ imageQueryParams: imageQueryParams,
213
+ method: method,
214
+ path: path,
215
+ baseUrl: baseUrl,
216
+ headers: headers,
217
+ query: query,
218
+ body: body,
219
+ contentType: contentType,
220
+ userAgent: userAgent,
221
+ )
222
+ }
223
+ }
224
+
225
+ private extension NativeRouteEntry {
226
+ func strippingJSCallbacks() -> NativeRouteEntry {
227
+ NativeRouteEntry(
228
+ path: path,
229
+ browseCallback: nil,
230
+ browseConfig: browseConfig?.strippingJSCallbacks(),
231
+ browseStatic: browseStatic,
232
+ searchCallback: nil,
233
+ searchConfig: searchConfig?.strippingJSCallbacks(),
234
+ media: media?.strippingJSCallbacks(),
235
+ artwork: artwork?.strippingJSCallbacks(),
236
+ )
237
+ }
238
+ }
@@ -262,10 +262,14 @@ extension BrowserManager {
262
262
  }
263
263
 
264
264
  // Substitute the `{id}` template token with the track's id across path/query/header values.
265
- // (Configs without the token are unaffected — e.g. browse artwork.) Only when the track has an id.
266
- if let id = track.id, !id.isEmpty {
267
- mergedConfig = substituteTrackId(in: mergedConfig, id: id)
268
- }
265
+ // (Configs without the token are unaffected — e.g. browse artwork.) A track without a
266
+ // non-blank id leaves the token LITERALLY in place — the request then visibly 404s, which
267
+ // is intended (self-describing error trail) — with a single warning logged below once the
268
+ // URL is built.
269
+ mergedConfig = substituteTrackId(in: mergedConfig, id: track.id)
270
+ let hasUnfilledIdToken = TrackIdTemplate.containsToken(mergedConfig.path)
271
+ || TrackIdTemplate.containsToken(mergedConfig.query)
272
+ || TrackIdTemplate.containsToken(mergedConfig.headers)
269
273
 
270
274
  // Build final URL - if no path after merging, no artwork to transform
271
275
  guard mergedConfig.path != nil else {
@@ -280,6 +284,10 @@ extension BrowserManager {
280
284
  headers["User-Agent"] = userAgent
281
285
  }
282
286
 
287
+ if hasUnfilledIdToken {
288
+ logger.warning("nowPlayingArtwork: track has no id — '{id}' left unfilled in \(uri)")
289
+ }
290
+
283
291
  logger.debug("Artwork URL transformed: \(track.artwork?.url ?? "nil") -> \(uri)")
284
292
 
285
293
  return ImageSource(
@@ -315,18 +323,14 @@ extension BrowserManager {
315
323
 
316
324
  /// Replaces the `{id}` token with the track id in a request config's path, query values, and
317
325
  /// header values. Used so a `nowPlayingArtwork` like `{ path: "/artwork/{id}" }` resolves.
318
- private func substituteTrackId(in config: RequestConfig, id: String) -> RequestConfig {
319
- func sub(_ s: String?) -> String? { s?.replacingOccurrences(of: "{id}", with: id) }
320
- func subDict(_ d: [String: String]?) -> [String: String]? {
321
- guard let d else { return nil }
322
- return d.mapValues { $0.replacingOccurrences(of: "{id}", with: id) }
323
- }
324
- return RequestConfig(
326
+ /// A nil/blank id leaves the values unchanged (token left literal — see TrackIdTemplate).
327
+ private func substituteTrackId(in config: RequestConfig, id: String?) -> RequestConfig {
328
+ RequestConfig(
325
329
  method: config.method,
326
- path: sub(config.path),
330
+ path: TrackIdTemplate.substitute(config.path, id: id),
327
331
  baseUrl: config.baseUrl,
328
- headers: subDict(config.headers),
329
- query: subDict(config.query),
332
+ headers: TrackIdTemplate.substitute(config.headers, id: id),
333
+ query: TrackIdTemplate.substitute(config.query, id: id),
330
334
  body: config.body,
331
335
  contentType: config.contentType,
332
336
  userAgent: config.userAgent,