@wwdrew/expo-apple-music 1.0.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 (452) hide show
  1. package/ATTRIBUTION.md +24 -0
  2. package/LICENSE +190 -0
  3. package/NOTICE +7 -0
  4. package/README.md +81 -0
  5. package/android/build.gradle +26 -0
  6. package/android/libs/mediaplayback-release-1.1.1.aar +0 -0
  7. package/android/libs/musickitauth-release-1.1.2.aar +0 -0
  8. package/android/src/main/AndroidManifest.xml +16 -0
  9. package/android/src/main/java/expo/modules/applemusic/AndroidCatalogService.kt +86 -0
  10. package/android/src/main/java/expo/modules/applemusic/AndroidDeveloperToken.kt +39 -0
  11. package/android/src/main/java/expo/modules/applemusic/AndroidHistoryService.kt +24 -0
  12. package/android/src/main/java/expo/modules/applemusic/AndroidLibraryMutationsService.kt +30 -0
  13. package/android/src/main/java/expo/modules/applemusic/AndroidLibraryService.kt +61 -0
  14. package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackController.kt +484 -0
  15. package/android/src/main/java/expo/modules/applemusic/AndroidPlaybackObserver.kt +173 -0
  16. package/android/src/main/java/expo/modules/applemusic/AndroidQueueService.kt +78 -0
  17. package/android/src/main/java/expo/modules/applemusic/AndroidRatingsService.kt +27 -0
  18. package/android/src/main/java/expo/modules/applemusic/AndroidRecommendationsService.kt +15 -0
  19. package/android/src/main/java/expo/modules/applemusic/AndroidSubscriptionService.kt +24 -0
  20. package/android/src/main/java/expo/modules/applemusic/AppleMusicErrorCodes.kt +13 -0
  21. package/android/src/main/java/expo/modules/applemusic/AppleMusicErrors.kt +46 -0
  22. package/android/src/main/java/expo/modules/applemusic/AppleMusicHttpMethod.kt +8 -0
  23. package/android/src/main/java/expo/modules/applemusic/AppleMusicJsonMapper.kt +258 -0
  24. package/android/src/main/java/expo/modules/applemusic/AppleMusicNativeLoader.kt +32 -0
  25. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestJson.kt +40 -0
  26. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestQuery.kt +12 -0
  27. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestStack.kt +19 -0
  28. package/android/src/main/java/expo/modules/applemusic/AppleMusicRestTransport.kt +118 -0
  29. package/android/src/main/java/expo/modules/applemusic/AuthenticatedSession.kt +57 -0
  30. package/android/src/main/java/expo/modules/applemusic/BridgeResponses.kt +55 -0
  31. package/android/src/main/java/expo/modules/applemusic/CatalogRestClient.kt +306 -0
  32. package/android/src/main/java/expo/modules/applemusic/ExpoAppleMusicModule.kt +152 -0
  33. package/android/src/main/java/expo/modules/applemusic/HistoryRestClient.kt +60 -0
  34. package/android/src/main/java/expo/modules/applemusic/LibraryIds.kt +6 -0
  35. package/android/src/main/java/expo/modules/applemusic/LibraryMutationsRestClient.kt +95 -0
  36. package/android/src/main/java/expo/modules/applemusic/LibraryRestClient.kt +195 -0
  37. package/android/src/main/java/expo/modules/applemusic/MusicKitAuthContract.kt +78 -0
  38. package/android/src/main/java/expo/modules/applemusic/MusicKitAuthStorage.kt +76 -0
  39. package/android/src/main/java/expo/modules/applemusic/MusicKitTokenProvider.kt +13 -0
  40. package/android/src/main/java/expo/modules/applemusic/PaginationOptions.kt +14 -0
  41. package/android/src/main/java/expo/modules/applemusic/RatingsRestClient.kt +72 -0
  42. package/android/src/main/java/expo/modules/applemusic/RecommendationsRestClient.kt +37 -0
  43. package/android/src/main/java/expo/modules/applemusic/StorefrontRestClient.kt +44 -0
  44. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeAuth.kt +69 -0
  45. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeCatalog.kt +76 -0
  46. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeHistory.kt +35 -0
  47. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibrary.kt +54 -0
  48. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeLibraryMutations.kt +30 -0
  49. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgePlayer.kt +89 -0
  50. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRatings.kt +29 -0
  51. package/android/src/main/java/expo/modules/applemusic/bridge/ExpoBridgeRecommendations.kt +18 -0
  52. package/app.plugin.js +1 -0
  53. package/build/ExpoAppleMusicModule.web.d.ts +15 -0
  54. package/build/ExpoAppleMusicModule.web.d.ts.map +1 -0
  55. package/build/ExpoAppleMusicModule.web.js +33 -0
  56. package/build/ExpoAppleMusicModule.web.js.map +1 -0
  57. package/build/api/call-native.d.ts +6 -0
  58. package/build/api/call-native.d.ts.map +1 -0
  59. package/build/api/call-native.js +35 -0
  60. package/build/api/call-native.js.map +1 -0
  61. package/build/api/decode-jwt-exp.d.ts +5 -0
  62. package/build/api/decode-jwt-exp.d.ts.map +1 -0
  63. package/build/api/decode-jwt-exp.js +28 -0
  64. package/build/api/decode-jwt-exp.js.map +1 -0
  65. package/build/api/library-ids.d.ts +4 -0
  66. package/build/api/library-ids.d.ts.map +1 -0
  67. package/build/api/library-ids.js +11 -0
  68. package/build/api/library-ids.js.map +1 -0
  69. package/build/api/pagination.d.ts +12 -0
  70. package/build/api/pagination.d.ts.map +1 -0
  71. package/build/api/pagination.js +13 -0
  72. package/build/api/pagination.js.map +1 -0
  73. package/build/api/parse-authorize-result.d.ts +3 -0
  74. package/build/api/parse-authorize-result.d.ts.map +1 -0
  75. package/build/api/parse-authorize-result.js +27 -0
  76. package/build/api/parse-authorize-result.js.map +1 -0
  77. package/build/api/require-music-user-token.d.ts +2 -0
  78. package/build/api/require-music-user-token.d.ts.map +1 -0
  79. package/build/api/require-music-user-token.js +14 -0
  80. package/build/api/require-music-user-token.js.map +1 -0
  81. package/build/api/sync-developer-token.d.ts +4 -0
  82. package/build/api/sync-developer-token.d.ts.map +1 -0
  83. package/build/api/sync-developer-token.js +27 -0
  84. package/build/api/sync-developer-token.js.map +1 -0
  85. package/build/bridge/bridge-methods.d.ts +17 -0
  86. package/build/bridge/bridge-methods.d.ts.map +1 -0
  87. package/build/bridge/bridge-methods.js +71 -0
  88. package/build/bridge/bridge-methods.js.map +1 -0
  89. package/build/bridge/bridge-responses.d.ts +64 -0
  90. package/build/bridge/bridge-responses.d.ts.map +1 -0
  91. package/build/bridge/bridge-responses.js +67 -0
  92. package/build/bridge/bridge-responses.js.map +1 -0
  93. package/build/bridge/handlers/auth-bridge.d.ts +11 -0
  94. package/build/bridge/handlers/auth-bridge.d.ts.map +1 -0
  95. package/build/bridge/handlers/auth-bridge.js +49 -0
  96. package/build/bridge/handlers/auth-bridge.js.map +1 -0
  97. package/build/bridge/handlers/catalog-bridge.d.ts +34 -0
  98. package/build/bridge/handlers/catalog-bridge.d.ts.map +1 -0
  99. package/build/bridge/handlers/catalog-bridge.js +58 -0
  100. package/build/bridge/handlers/catalog-bridge.js.map +1 -0
  101. package/build/bridge/handlers/history-bridge.d.ts +19 -0
  102. package/build/bridge/handlers/history-bridge.d.ts.map +1 -0
  103. package/build/bridge/handlers/history-bridge.js +31 -0
  104. package/build/bridge/handlers/history-bridge.js.map +1 -0
  105. package/build/bridge/handlers/index.d.ts +124 -0
  106. package/build/bridge/handlers/index.d.ts.map +1 -0
  107. package/build/bridge/handlers/index.js +21 -0
  108. package/build/bridge/handlers/index.js.map +1 -0
  109. package/build/bridge/handlers/library-bridge.d.ts +23 -0
  110. package/build/bridge/handlers/library-bridge.d.ts.map +1 -0
  111. package/build/bridge/handlers/library-bridge.js +41 -0
  112. package/build/bridge/handlers/library-bridge.js.map +1 -0
  113. package/build/bridge/handlers/library-mutations-bridge.d.ts +16 -0
  114. package/build/bridge/handlers/library-mutations-bridge.d.ts.map +1 -0
  115. package/build/bridge/handlers/library-mutations-bridge.js +20 -0
  116. package/build/bridge/handlers/library-mutations-bridge.js.map +1 -0
  117. package/build/bridge/handlers/player-bridge.d.ts +18 -0
  118. package/build/bridge/handlers/player-bridge.d.ts.map +1 -0
  119. package/build/bridge/handlers/player-bridge.js +42 -0
  120. package/build/bridge/handlers/player-bridge.js.map +1 -0
  121. package/build/bridge/handlers/ratings-bridge.d.ts +15 -0
  122. package/build/bridge/handlers/ratings-bridge.d.ts.map +1 -0
  123. package/build/bridge/handlers/ratings-bridge.js +16 -0
  124. package/build/bridge/handlers/ratings-bridge.js.map +1 -0
  125. package/build/bridge/handlers/recommendations-bridge.d.ts +10 -0
  126. package/build/bridge/handlers/recommendations-bridge.d.ts.map +1 -0
  127. package/build/bridge/handlers/recommendations-bridge.js +14 -0
  128. package/build/bridge/handlers/recommendations-bridge.js.map +1 -0
  129. package/build/constants/apple-music-error-codes.d.ts +22 -0
  130. package/build/constants/apple-music-error-codes.d.ts.map +1 -0
  131. package/build/constants/apple-music-error-codes.js +21 -0
  132. package/build/constants/apple-music-error-codes.js.map +1 -0
  133. package/build/hooks/use-current-song.d.ts +10 -0
  134. package/build/hooks/use-current-song.d.ts.map +1 -0
  135. package/build/hooks/use-current-song.js +35 -0
  136. package/build/hooks/use-current-song.js.map +1 -0
  137. package/build/hooks/use-is-playing.d.ts +6 -0
  138. package/build/hooks/use-is-playing.d.ts.map +1 -0
  139. package/build/hooks/use-is-playing.js +21 -0
  140. package/build/hooks/use-is-playing.js.map +1 -0
  141. package/build/hooks/use-playback-state.d.ts +12 -0
  142. package/build/hooks/use-playback-state.d.ts.map +1 -0
  143. package/build/hooks/use-playback-state.js +41 -0
  144. package/build/hooks/use-playback-state.js.map +1 -0
  145. package/build/index.d.ts +52 -0
  146. package/build/index.d.ts.map +1 -0
  147. package/build/index.js +44 -0
  148. package/build/index.js.map +1 -0
  149. package/build/mappers/apple-music-json-mapper.d.ts +94 -0
  150. package/build/mappers/apple-music-json-mapper.d.ts.map +1 -0
  151. package/build/mappers/apple-music-json-mapper.js +212 -0
  152. package/build/mappers/apple-music-json-mapper.js.map +1 -0
  153. package/build/modules/auth.d.ts +32 -0
  154. package/build/modules/auth.d.ts.map +1 -0
  155. package/build/modules/auth.js +60 -0
  156. package/build/modules/auth.js.map +1 -0
  157. package/build/modules/catalog.d.ts +46 -0
  158. package/build/modules/catalog.d.ts.map +1 -0
  159. package/build/modules/catalog.js +47 -0
  160. package/build/modules/catalog.js.map +1 -0
  161. package/build/modules/history.d.ts +17 -0
  162. package/build/modules/history.d.ts.map +1 -0
  163. package/build/modules/history.js +28 -0
  164. package/build/modules/history.js.map +1 -0
  165. package/build/modules/library-mutations.d.ts +9 -0
  166. package/build/modules/library-mutations.d.ts.map +1 -0
  167. package/build/modules/library-mutations.js +41 -0
  168. package/build/modules/library-mutations.js.map +1 -0
  169. package/build/modules/library.d.ts +21 -0
  170. package/build/modules/library.d.ts.map +1 -0
  171. package/build/modules/library.js +38 -0
  172. package/build/modules/library.js.map +1 -0
  173. package/build/modules/player.d.ts +53 -0
  174. package/build/modules/player.d.ts.map +1 -0
  175. package/build/modules/player.js +68 -0
  176. package/build/modules/player.js.map +1 -0
  177. package/build/modules/ratings.d.ts +10 -0
  178. package/build/modules/ratings.d.ts.map +1 -0
  179. package/build/modules/ratings.js +37 -0
  180. package/build/modules/ratings.js.map +1 -0
  181. package/build/modules/recommendations.d.ts +7 -0
  182. package/build/modules/recommendations.d.ts.map +1 -0
  183. package/build/modules/recommendations.js +15 -0
  184. package/build/modules/recommendations.js.map +1 -0
  185. package/build/native-module.d.ts +5 -0
  186. package/build/native-module.d.ts.map +1 -0
  187. package/build/native-module.js +5 -0
  188. package/build/native-module.js.map +1 -0
  189. package/build/native-module.web.d.ts +11 -0
  190. package/build/native-module.web.d.ts.map +1 -0
  191. package/build/native-module.web.js +11 -0
  192. package/build/native-module.web.js.map +1 -0
  193. package/build/rest/apple-music-rest-stack.d.ts +21 -0
  194. package/build/rest/apple-music-rest-stack.d.ts.map +1 -0
  195. package/build/rest/apple-music-rest-stack.js +21 -0
  196. package/build/rest/apple-music-rest-stack.js.map +1 -0
  197. package/build/rest/apple-music-rest-transport.d.ts +8 -0
  198. package/build/rest/apple-music-rest-transport.d.ts.map +1 -0
  199. package/build/rest/apple-music-rest-transport.js +2 -0
  200. package/build/rest/apple-music-rest-transport.js.map +1 -0
  201. package/build/rest/catalog-rest-client.d.ts +38 -0
  202. package/build/rest/catalog-rest-client.d.ts.map +1 -0
  203. package/build/rest/catalog-rest-client.js +189 -0
  204. package/build/rest/catalog-rest-client.js.map +1 -0
  205. package/build/rest/history-rest-client.d.ts +37 -0
  206. package/build/rest/history-rest-client.d.ts.map +1 -0
  207. package/build/rest/history-rest-client.js +30 -0
  208. package/build/rest/history-rest-client.js.map +1 -0
  209. package/build/rest/library-ids.d.ts +2 -0
  210. package/build/rest/library-ids.d.ts.map +1 -0
  211. package/build/rest/library-ids.js +2 -0
  212. package/build/rest/library-ids.js.map +1 -0
  213. package/build/rest/library-mutations-rest-client.d.ts +22 -0
  214. package/build/rest/library-mutations-rest-client.d.ts.map +1 -0
  215. package/build/rest/library-mutations-rest-client.js +37 -0
  216. package/build/rest/library-mutations-rest-client.js.map +1 -0
  217. package/build/rest/library-rest-client.d.ts +59 -0
  218. package/build/rest/library-rest-client.d.ts.map +1 -0
  219. package/build/rest/library-rest-client.js +141 -0
  220. package/build/rest/library-rest-client.js.map +1 -0
  221. package/build/rest/ratings-rest-client.d.ts +18 -0
  222. package/build/rest/ratings-rest-client.d.ts.map +1 -0
  223. package/build/rest/ratings-rest-client.js +37 -0
  224. package/build/rest/ratings-rest-client.js.map +1 -0
  225. package/build/rest/recommendations-rest-client.d.ts +32 -0
  226. package/build/rest/recommendations-rest-client.d.ts.map +1 -0
  227. package/build/rest/recommendations-rest-client.js +26 -0
  228. package/build/rest/recommendations-rest-client.js.map +1 -0
  229. package/build/rest/resource-ids-query.d.ts +2 -0
  230. package/build/rest/resource-ids-query.d.ts.map +1 -0
  231. package/build/rest/resource-ids-query.js +10 -0
  232. package/build/rest/resource-ids-query.js.map +1 -0
  233. package/build/rest/rest-json.d.ts +11 -0
  234. package/build/rest/rest-json.d.ts.map +1 -0
  235. package/build/rest/rest-json.js +29 -0
  236. package/build/rest/rest-json.js.map +1 -0
  237. package/build/rest/storefront-rest-client.d.ts +14 -0
  238. package/build/rest/storefront-rest-client.d.ts.map +1 -0
  239. package/build/rest/storefront-rest-client.js +36 -0
  240. package/build/rest/storefront-rest-client.js.map +1 -0
  241. package/build/types/album.d.ts +8 -0
  242. package/build/types/album.d.ts.map +1 -0
  243. package/build/types/album.js +2 -0
  244. package/build/types/album.js.map +1 -0
  245. package/build/types/albums-response.d.ts +5 -0
  246. package/build/types/albums-response.d.ts.map +1 -0
  247. package/build/types/albums-response.js +2 -0
  248. package/build/types/albums-response.js.map +1 -0
  249. package/build/types/android-authorize-options.d.ts +18 -0
  250. package/build/types/android-authorize-options.d.ts.map +1 -0
  251. package/build/types/android-authorize-options.js +2 -0
  252. package/build/types/android-authorize-options.js.map +1 -0
  253. package/build/types/artist.d.ts +9 -0
  254. package/build/types/artist.d.ts.map +1 -0
  255. package/build/types/artist.js +2 -0
  256. package/build/types/artist.js.map +1 -0
  257. package/build/types/auth-status.d.ts +19 -0
  258. package/build/types/auth-status.d.ts.map +1 -0
  259. package/build/types/auth-status.js +18 -0
  260. package/build/types/auth-status.js.map +1 -0
  261. package/build/types/authorize-result.d.ts +8 -0
  262. package/build/types/authorize-result.d.ts.map +1 -0
  263. package/build/types/authorize-result.js +2 -0
  264. package/build/types/authorize-result.js.map +1 -0
  265. package/build/types/catalog-album-tracks.d.ts +5 -0
  266. package/build/types/catalog-album-tracks.d.ts.map +1 -0
  267. package/build/types/catalog-album-tracks.js +2 -0
  268. package/build/types/catalog-album-tracks.js.map +1 -0
  269. package/build/types/catalog-charts.d.ts +25 -0
  270. package/build/types/catalog-charts.d.ts.map +1 -0
  271. package/build/types/catalog-charts.js +7 -0
  272. package/build/types/catalog-charts.js.map +1 -0
  273. package/build/types/catalog-resource-type.d.ts +11 -0
  274. package/build/types/catalog-resource-type.d.ts.map +1 -0
  275. package/build/types/catalog-resource-type.js +10 -0
  276. package/build/types/catalog-resource-type.js.map +1 -0
  277. package/build/types/catalog-search.d.ts +24 -0
  278. package/build/types/catalog-search.d.ts.map +1 -0
  279. package/build/types/catalog-search.js +9 -0
  280. package/build/types/catalog-search.js.map +1 -0
  281. package/build/types/check-subscription.d.ts +31 -0
  282. package/build/types/check-subscription.d.ts.map +1 -0
  283. package/build/types/check-subscription.js +5 -0
  284. package/build/types/check-subscription.js.map +1 -0
  285. package/build/types/library-music-videos.d.ts +5 -0
  286. package/build/types/library-music-videos.d.ts.map +1 -0
  287. package/build/types/library-music-videos.js +2 -0
  288. package/build/types/library-music-videos.js.map +1 -0
  289. package/build/types/library-mutations.d.ts +21 -0
  290. package/build/types/library-mutations.d.ts.map +1 -0
  291. package/build/types/library-mutations.js +8 -0
  292. package/build/types/library-mutations.js.map +1 -0
  293. package/build/types/library-search.d.ts +22 -0
  294. package/build/types/library-search.d.ts.map +1 -0
  295. package/build/types/library-search.js +9 -0
  296. package/build/types/library-search.js.map +1 -0
  297. package/build/types/music-item.d.ts +8 -0
  298. package/build/types/music-item.d.ts.map +1 -0
  299. package/build/types/music-item.js +7 -0
  300. package/build/types/music-item.js.map +1 -0
  301. package/build/types/music-video.d.ts +8 -0
  302. package/build/types/music-video.d.ts.map +1 -0
  303. package/build/types/music-video.js +2 -0
  304. package/build/types/music-video.js.map +1 -0
  305. package/build/types/pagination.d.ts +11 -0
  306. package/build/types/pagination.d.ts.map +1 -0
  307. package/build/types/pagination.js +2 -0
  308. package/build/types/pagination.js.map +1 -0
  309. package/build/types/playback-state.d.ts +9 -0
  310. package/build/types/playback-state.d.ts.map +1 -0
  311. package/build/types/playback-state.js +2 -0
  312. package/build/types/playback-state.js.map +1 -0
  313. package/build/types/playback-status.d.ts +10 -0
  314. package/build/types/playback-status.d.ts.map +1 -0
  315. package/build/types/playback-status.js +9 -0
  316. package/build/types/playback-status.js.map +1 -0
  317. package/build/types/playlist.d.ts +15 -0
  318. package/build/types/playlist.d.ts.map +1 -0
  319. package/build/types/playlist.js +2 -0
  320. package/build/types/playlist.js.map +1 -0
  321. package/build/types/rating.d.ts +34 -0
  322. package/build/types/rating.d.ts.map +1 -0
  323. package/build/types/rating.js +26 -0
  324. package/build/types/rating.js.map +1 -0
  325. package/build/types/recent-resource.d.ts +11 -0
  326. package/build/types/recent-resource.d.ts.map +1 -0
  327. package/build/types/recent-resource.js +2 -0
  328. package/build/types/recent-resource.js.map +1 -0
  329. package/build/types/recommendation.d.ts +37 -0
  330. package/build/types/recommendation.d.ts.map +1 -0
  331. package/build/types/recommendation.js +2 -0
  332. package/build/types/recommendation.js.map +1 -0
  333. package/build/types/song.d.ts +8 -0
  334. package/build/types/song.d.ts.map +1 -0
  335. package/build/types/song.js +2 -0
  336. package/build/types/song.js.map +1 -0
  337. package/build/types/station.d.ts +9 -0
  338. package/build/types/station.d.ts.map +1 -0
  339. package/build/types/station.js +2 -0
  340. package/build/types/station.js.map +1 -0
  341. package/build/types/storefront.d.ts +4 -0
  342. package/build/types/storefront.d.ts.map +1 -0
  343. package/build/types/storefront.js +2 -0
  344. package/build/types/storefront.js.map +1 -0
  345. package/build/types/tracks-from-library.d.ts +11 -0
  346. package/build/types/tracks-from-library.d.ts.map +1 -0
  347. package/build/types/tracks-from-library.js +2 -0
  348. package/build/types/tracks-from-library.js.map +1 -0
  349. package/build/utils/apple-music-error.d.ts +10 -0
  350. package/build/utils/apple-music-error.d.ts.map +1 -0
  351. package/build/utils/apple-music-error.js +13 -0
  352. package/build/utils/apple-music-error.js.map +1 -0
  353. package/build/utils/get-error-message.d.ts +2 -0
  354. package/build/utils/get-error-message.d.ts.map +1 -0
  355. package/build/utils/get-error-message.js +21 -0
  356. package/build/utils/get-error-message.js.map +1 -0
  357. package/build/utils/is-library-item.d.ts +2 -0
  358. package/build/utils/is-library-item.d.ts.map +1 -0
  359. package/build/utils/is-library-item.js +4 -0
  360. package/build/utils/is-library-item.js.map +1 -0
  361. package/build/utils/normalize-resource-ids.d.ts +4 -0
  362. package/build/utils/normalize-resource-ids.d.ts.map +1 -0
  363. package/build/utils/normalize-resource-ids.js +12 -0
  364. package/build/utils/normalize-resource-ids.js.map +1 -0
  365. package/build/web/MusicKitLoader.d.ts +11 -0
  366. package/build/web/MusicKitLoader.d.ts.map +1 -0
  367. package/build/web/MusicKitLoader.js +135 -0
  368. package/build/web/MusicKitLoader.js.map +1 -0
  369. package/build/web/WebAppleMusicApiClient.d.ts +151 -0
  370. package/build/web/WebAppleMusicApiClient.d.ts.map +1 -0
  371. package/build/web/WebAppleMusicApiClient.js +139 -0
  372. package/build/web/WebAppleMusicApiClient.js.map +1 -0
  373. package/build/web/WebAppleMusicRestTransport.d.ts +9 -0
  374. package/build/web/WebAppleMusicRestTransport.d.ts.map +1 -0
  375. package/build/web/WebAppleMusicRestTransport.js +31 -0
  376. package/build/web/WebAppleMusicRestTransport.js.map +1 -0
  377. package/build/web/WebPlaybackController.d.ts +12 -0
  378. package/build/web/WebPlaybackController.d.ts.map +1 -0
  379. package/build/web/WebPlaybackController.js +90 -0
  380. package/build/web/WebPlaybackController.js.map +1 -0
  381. package/build/web/WebPlaybackObserver.d.ts +22 -0
  382. package/build/web/WebPlaybackObserver.d.ts.map +1 -0
  383. package/build/web/WebPlaybackObserver.js +106 -0
  384. package/build/web/WebPlaybackObserver.js.map +1 -0
  385. package/build/web/WebQueueService.d.ts +10 -0
  386. package/build/web/WebQueueService.d.ts.map +1 -0
  387. package/build/web/WebQueueService.js +53 -0
  388. package/build/web/WebQueueService.js.map +1 -0
  389. package/build/web/WebSubscriptionService.d.ts +7 -0
  390. package/build/web/WebSubscriptionService.d.ts.map +1 -0
  391. package/build/web/WebSubscriptionService.js +24 -0
  392. package/build/web/WebSubscriptionService.js.map +1 -0
  393. package/build/web/apple-music-errors.d.ts +11 -0
  394. package/build/web/apple-music-errors.d.ts.map +1 -0
  395. package/build/web/apple-music-errors.js +31 -0
  396. package/build/web/apple-music-errors.js.map +1 -0
  397. package/build/web/extract-music-user-token.d.ts +4 -0
  398. package/build/web/extract-music-user-token.d.ts.map +1 -0
  399. package/build/web/extract-music-user-token.js +15 -0
  400. package/build/web/extract-music-user-token.js.map +1 -0
  401. package/build/web/map-auth-status.d.ts +17 -0
  402. package/build/web/map-auth-status.d.ts.map +1 -0
  403. package/build/web/map-auth-status.js +85 -0
  404. package/build/web/map-auth-status.js.map +1 -0
  405. package/build/web/music-kit-api.d.ts +18 -0
  406. package/build/web/music-kit-api.d.ts.map +1 -0
  407. package/build/web/music-kit-api.js +120 -0
  408. package/build/web/music-kit-api.js.map +1 -0
  409. package/build/web/musickit-types.d.ts +70 -0
  410. package/build/web/musickit-types.d.ts.map +1 -0
  411. package/build/web/musickit-types.js +3 -0
  412. package/build/web/musickit-types.js.map +1 -0
  413. package/build/web/pagination.d.ts +2 -0
  414. package/build/web/pagination.d.ts.map +1 -0
  415. package/build/web/pagination.js +2 -0
  416. package/build/web/pagination.js.map +1 -0
  417. package/expo-module.config.json +19 -0
  418. package/ios/AppleMusicBridgeError.swift +60 -0
  419. package/ios/AppleMusicErrorCodes.swift +11 -0
  420. package/ios/AppleMusicRestClient.swift +213 -0
  421. package/ios/AuthenticatedSession.swift +64 -0
  422. package/ios/BridgePagination.swift +17 -0
  423. package/ios/BridgeResponses.swift +82 -0
  424. package/ios/CatalogSearchStore.swift +13 -0
  425. package/ios/CatalogSearchStoreFactory.swift +31 -0
  426. package/ios/CatalogService.swift +307 -0
  427. package/ios/ExpoAppleMusic.podspec +29 -0
  428. package/ios/ExpoAppleMusicModule.swift +505 -0
  429. package/ios/HistoryService.swift +53 -0
  430. package/ios/LibraryMutationsService.swift +63 -0
  431. package/ios/LibraryService.swift +313 -0
  432. package/ios/MusicItemMapper.swift +171 -0
  433. package/ios/MusicKitAuthStorage.swift +38 -0
  434. package/ios/MusicKitCatalogSearchStore.swift +62 -0
  435. package/ios/PlaybackController.swift +201 -0
  436. package/ios/PlaybackObserver.swift +225 -0
  437. package/ios/QueueService.swift +166 -0
  438. package/ios/RatingsService.swift +66 -0
  439. package/ios/RecommendationsService.swift +34 -0
  440. package/ios/RestCatalogSearchStore.swift +62 -0
  441. package/ios/RestJsonMapper.swift +268 -0
  442. package/ios/StorefrontService.swift +55 -0
  443. package/ios/SubscriptionService.swift +119 -0
  444. package/ios/bridge/ExpoBridgeCatalog.swift +98 -0
  445. package/ios/bridge/ExpoBridgeHistory.swift +71 -0
  446. package/ios/bridge/ExpoBridgeLibrary.swift +93 -0
  447. package/ios/bridge/ExpoBridgeRecommendations.swift +28 -0
  448. package/package.json +89 -0
  449. package/plugin/build/index.d.ts +5 -0
  450. package/plugin/build/index.js +10 -0
  451. package/plugin/build/with-expo-apple-music.d.ts +10 -0
  452. package/plugin/build/with-expo-apple-music.js +50 -0
@@ -0,0 +1,62 @@
1
+ // RestCatalogSearchStore.swift
2
+ // Catalog search via Apple Music REST API (aligned with Android catalog paths).
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ struct RestCatalogSearchStore: CatalogSearchStore {
8
+
9
+ func search(
10
+ term: String,
11
+ types: [String],
12
+ options: CatalogService.SearchOptions
13
+ ) async throws -> CatalogService.SearchResult {
14
+ let storefront = StorefrontService.getCatalogStorefront()
15
+ let typeParam = Array(Set(types.compactMap { catalogSearchTypeParam($0) })).sorted().joined(
16
+ separator: ",")
17
+ let typesQuery = typeParam.isEmpty ? "songs,albums" : typeParam
18
+
19
+ let json = try await AppleMusicRestClient.get(
20
+ path: "/v1/catalog/\(storefront)/search",
21
+ query: [
22
+ "term": term,
23
+ "types": typesQuery,
24
+ "limit": "\(options.limit)",
25
+ "offset": "\(options.offset)",
26
+ ]
27
+ )
28
+
29
+ let results = json["results"] as? [String: Any] ?? [:]
30
+
31
+ return CatalogService.SearchResult(
32
+ songs: parseSearchBucket(results: results, key: "songs", mapper: RestJsonMapper.mapSong),
33
+ albums: parseSearchBucket(results: results, key: "albums", mapper: RestJsonMapper.mapAlbum),
34
+ artists: parseSearchBucket(results: results, key: "artists", mapper: RestJsonMapper.mapArtist),
35
+ playlists: parseSearchBucket(
36
+ results: results, key: "playlists", mapper: RestJsonMapper.mapPlaylist),
37
+ stations: parseSearchBucket(results: results, key: "stations", mapper: RestJsonMapper.mapStation),
38
+ musicVideos: parseSearchBucket(
39
+ results: results, key: "music-videos", mapper: RestJsonMapper.mapMusicVideo)
40
+ )
41
+ }
42
+
43
+ private func catalogSearchTypeParam(_ type: String) -> String? {
44
+ switch type {
45
+ case "songs", "albums", "artists", "playlists", "stations", "music-videos": return type
46
+ default: return nil
47
+ }
48
+ }
49
+
50
+ private func parseSearchBucket(
51
+ results: [String: Any],
52
+ key: String,
53
+ mapper: ([String: Any]) -> [String: Any]
54
+ ) -> [[String: Any]] {
55
+ guard let bucket = results[key] as? [String: Any],
56
+ let data = bucket["data"] as? [[String: Any]]
57
+ else {
58
+ return []
59
+ }
60
+ return data.map(mapper)
61
+ }
62
+ }
@@ -0,0 +1,268 @@
1
+ // RestJsonMapper.swift
2
+ // Maps Apple Music API JSON objects to bridge dictionaries (matches Android mapper).
3
+ // Golden REST cases: fixtures/*.json + fixtures/expected/*.json — see docs/BRIDGE_CONTRACT.md.
4
+
5
+ import Foundation
6
+
7
+ @available(iOS 16.0, *)
8
+ enum RestJsonMapper {
9
+
10
+ static func mapAlbum(_ resource: [String: Any]) -> [String: Any] {
11
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
12
+ let trackCount: Int
13
+ if let count = attributes["trackCount"] as? Int {
14
+ trackCount = count
15
+ } else if let count = attributes["trackCount"] as? Double {
16
+ trackCount = Int(count)
17
+ } else {
18
+ trackCount = 0
19
+ }
20
+ return [
21
+ "id": resource["id"] as? String ?? "",
22
+ "title": attributes["name"] as? String ?? "",
23
+ "artistName": attributes["artistName"] as? String ?? "",
24
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
25
+ "trackCount": trackCount,
26
+ ]
27
+ }
28
+
29
+ static func mapSong(_ resource: [String: Any]) -> [String: Any] {
30
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
31
+ let id = catalogPlaybackId(resource) ?? (resource["id"] as? String ?? "")
32
+ return [
33
+ "id": id,
34
+ "title": attributes["name"] as? String ?? "",
35
+ "artistName": attributes["artistName"] as? String ?? "",
36
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
37
+ "duration": durationMillis(attributes),
38
+ ]
39
+ }
40
+
41
+ static func mapArtist(_ resource: [String: Any]) -> [String: Any] {
42
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
43
+ return [
44
+ "id": resource["id"] as? String ?? "",
45
+ "name": attributes["name"] as? String ?? "",
46
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
47
+ ]
48
+ }
49
+
50
+ static func mapRecentResource(_ resource: [String: Any]) -> [String: Any] {
51
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
52
+ let apiType = resource["type"] as? String ?? ""
53
+ let itemType: String
54
+ if apiType.contains("album") {
55
+ itemType = "album"
56
+ } else if apiType.contains("playlist") {
57
+ itemType = "playlist"
58
+ } else if apiType.contains("station") {
59
+ itemType = "station"
60
+ } else {
61
+ itemType = "unknown"
62
+ }
63
+
64
+ var subtitle = attributes["artistName"] as? String ?? ""
65
+ if subtitle.isEmpty {
66
+ subtitle = attributes["curatorName"] as? String ?? ""
67
+ }
68
+ if subtitle.isEmpty, let description = attributes["description"] as? [String: Any] {
69
+ subtitle = description["standard"] as? String ?? ""
70
+ }
71
+
72
+ return [
73
+ "id": resource["id"] as? String ?? "",
74
+ "title": attributes["name"] as? String ?? "",
75
+ "subtitle": subtitle,
76
+ "type": itemType,
77
+ ]
78
+ }
79
+
80
+ static func mapRecentlyPlayed(_ resource: [String: Any]) -> [String: Any] {
81
+ mapRecentResource(resource)
82
+ }
83
+
84
+ static func mapPlaylist(_ resource: [String: Any]) -> [String: Any] {
85
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
86
+ var trackCount = 0
87
+ if let count = attributes["trackCount"] as? Int {
88
+ trackCount = count
89
+ } else if let count = attributes["trackCount"] as? Double {
90
+ trackCount = Int(count)
91
+ }
92
+ var description = attributes["description"] as? String ?? ""
93
+ if description.isEmpty, let desc = attributes["description"] as? [String: Any] {
94
+ description = desc["standard"] as? String ?? ""
95
+ }
96
+ return [
97
+ "id": resource["id"] as? String ?? "",
98
+ "name": attributes["name"] as? String ?? "",
99
+ "description": description,
100
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
101
+ "trackCount": trackCount,
102
+ ]
103
+ }
104
+
105
+ static func mapMusicVideo(_ resource: [String: Any]) -> [String: Any] {
106
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
107
+ let id = catalogPlaybackId(resource) ?? (resource["id"] as? String ?? "")
108
+ return [
109
+ "id": id,
110
+ "title": attributes["name"] as? String ?? "",
111
+ "artistName": attributes["artistName"] as? String ?? "",
112
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
113
+ "duration": durationMillis(attributes),
114
+ ]
115
+ }
116
+
117
+ static func mapStation(_ resource: [String: Any]) -> [String: Any] {
118
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
119
+ return [
120
+ "id": resource["id"] as? String ?? "",
121
+ "name": attributes["name"] as? String ?? "",
122
+ "artworkUrl": artworkUrl(attributes["artwork"] as? [String: Any]),
123
+ ]
124
+ }
125
+
126
+ private static func catalogPlaybackId(_ resource: [String: Any]) -> String? {
127
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
128
+ guard let playParams = attributes["playParams"] as? [String: Any] else { return nil }
129
+ if let id = playParams["id"] as? String, !id.isEmpty { return id }
130
+ if let catalogId = playParams["catalogId"] as? String, !catalogId.isEmpty { return catalogId }
131
+ return nil
132
+ }
133
+
134
+ /// Candidate catalog song ids for MusicKit lookup (playback id, resource id, playParams).
135
+ static func catalogSongLookupIds(primaryId: String, resource: [String: Any]) -> [String] {
136
+ var ids: [String] = []
137
+ func append(_ value: String?) {
138
+ guard let value, !value.isEmpty, !ids.contains(value) else { return }
139
+ ids.append(value)
140
+ }
141
+ append(primaryId)
142
+ append(resource["id"] as? String)
143
+ append(catalogPlaybackId(resource))
144
+ return ids
145
+ }
146
+
147
+ private static func durationMillis(_ attributes: [String: Any]) -> Int {
148
+ if let millis = attributes["durationInMillis"] as? Int {
149
+ return millis
150
+ }
151
+ if let millis = attributes["durationInMillis"] as? Double {
152
+ return Int(millis)
153
+ }
154
+ if let duration = attributes["duration"] as? Double {
155
+ return Int(duration * 1000)
156
+ }
157
+ return 0
158
+ }
159
+
160
+ private static func artworkUrl(_ artwork: [String: Any]?, width: Int = 200, height: Int = 200) -> String {
161
+ guard let artwork, let template = artwork["url"] as? String, !template.isEmpty else {
162
+ return ""
163
+ }
164
+ return template
165
+ .replacingOccurrences(of: "{w}", with: "\(width)")
166
+ .replacingOccurrences(of: "{h}", with: "\(height)")
167
+ }
168
+
169
+ static func mapRating(_ json: [String: Any]) -> [String: Any]? {
170
+ guard let data = json["data"] as? [[String: Any]], let first = data.first else {
171
+ return nil
172
+ }
173
+ let attributes = first["attributes"] as? [String: Any] ?? [:]
174
+ let value = attributes["value"] as? Int ?? (attributes["value"] as? Double).map { Int($0) }
175
+ guard let value else { return nil }
176
+ return [
177
+ "id": first["id"] as? String ?? "",
178
+ "value": value,
179
+ ]
180
+ }
181
+
182
+ static func mapRecommendation(_ resource: [String: Any]) -> [String: Any] {
183
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
184
+ let titleDict = attributes["title"] as? [String: Any]
185
+ let title = titleDict?["stringForDisplay"] as? String ?? ""
186
+ let resourceTypes = attributes["resourceTypes"] as? [String] ?? []
187
+ let contents = mapRecommendationContents(resource)
188
+ return [
189
+ "id": resource["id"] as? String ?? "",
190
+ "title": title,
191
+ "resourceTypes": resourceTypes,
192
+ "playlists": contents.playlists,
193
+ "albums": contents.albums,
194
+ "stations": contents.stations,
195
+ ]
196
+ }
197
+
198
+ static func mapReplaySummary(_ resource: [String: Any]) -> [String: Any] {
199
+ let attributes = resource["attributes"] as? [String: Any] ?? [:]
200
+ let year = attributes["year"] as? Int ?? (attributes["year"] as? Double).map { Int($0) }
201
+ var result: [String: Any] = [
202
+ "id": resource["id"] as? String ?? "",
203
+ "type": resource["type"] as? String ?? "",
204
+ "name": attributes["name"] as? String ?? "",
205
+ "topSongs": mapRelationshipResources(resource, key: "top-songs", mapper: mapSong),
206
+ "topAlbums": mapRelationshipResources(resource, key: "top-albums", mapper: mapAlbum),
207
+ "topArtists": mapRelationshipResources(resource, key: "top-artists", mapper: mapArtist),
208
+ ]
209
+ if let year {
210
+ result["year"] = year
211
+ }
212
+ return result
213
+ }
214
+
215
+ private struct RecommendationContents {
216
+ let playlists: [[String: Any]]
217
+ let albums: [[String: Any]]
218
+ let stations: [[String: Any]]
219
+ }
220
+
221
+ private static func mapRecommendationContents(_ resource: [String: Any]) -> RecommendationContents {
222
+ var playlists: [[String: Any]] = []
223
+ var albums: [[String: Any]] = []
224
+ var stations: [[String: Any]] = []
225
+ guard let relationships = resource["relationships"] as? [String: Any],
226
+ let contents = relationships["contents"] as? [String: Any],
227
+ let data = contents["data"] as? [[String: Any]]
228
+ else {
229
+ return RecommendationContents(playlists: playlists, albums: albums, stations: stations)
230
+ }
231
+ for item in data {
232
+ let type = item["type"] as? String ?? ""
233
+ if type.contains("playlist") {
234
+ playlists.append(mapPlaylist(item))
235
+ } else if type.contains("album") {
236
+ albums.append(mapAlbum(item))
237
+ } else if type.contains("station") {
238
+ stations.append(mapStation(item))
239
+ }
240
+ }
241
+ return RecommendationContents(playlists: playlists, albums: albums, stations: stations)
242
+ }
243
+
244
+ private static func mapRelationshipResources(
245
+ _ resource: [String: Any],
246
+ key: String,
247
+ mapper: ([String: Any]) -> [String: Any]
248
+ ) -> [[String: Any]] {
249
+ guard let relationships = resource["relationships"] as? [String: Any],
250
+ let relation = relationships[key] as? [String: Any],
251
+ let data = relation["data"] as? [[String: Any]]
252
+ else {
253
+ return []
254
+ }
255
+ return data.map(mapper)
256
+ }
257
+
258
+ static func buildIdsQuery(_ resourceIds: [String: [String]]) -> [String: String] {
259
+ var query: [String: String] = [:]
260
+ for (type, ids) in resourceIds {
261
+ let filtered = ids.filter { !$0.isEmpty }
262
+ if !filtered.isEmpty {
263
+ query["ids[\(type)]"] = filtered.joined(separator: ",")
264
+ }
265
+ }
266
+ return query
267
+ }
268
+ }
@@ -0,0 +1,55 @@
1
+ // StorefrontService.swift
2
+ // Resolves the user's Apple Music storefront via MusicKit-authenticated API requests.
3
+
4
+ import Foundation
5
+ import MusicKit
6
+
7
+ @available(iOS 16.0, *)
8
+ enum StorefrontService {
9
+
10
+ enum StorefrontError: LocalizedError {
11
+ case invalidResponse
12
+ case unauthorized
13
+
14
+ var errorDescription: String? {
15
+ switch self {
16
+ case .invalidResponse: return "Invalid storefront response from Apple Music"
17
+ case .unauthorized: return "Not authorized for Apple Music"
18
+ }
19
+ }
20
+ }
21
+
22
+ /// Catalog REST paths — cached storefront or device locale (no music user token).
23
+ static func getCatalogStorefront() -> String {
24
+ if let cached = AuthenticatedSessionCache.cachedStorefrontId() {
25
+ return cached
26
+ }
27
+ return localeStorefrontId()
28
+ }
29
+
30
+ static func getStorefrontId(musicUserToken: String) async throws -> String {
31
+ if let cached = AuthenticatedSessionCache.cachedStorefrontId() {
32
+ return cached
33
+ }
34
+
35
+ let session = AuthenticatedSession.current
36
+ if session.hasDeveloperToken {
37
+ let data = try await AppleMusicRestClient.getDataArray(
38
+ path: "/v1/me/storefront",
39
+ musicUserToken: musicUserToken)
40
+ if let id = data.first?["id"] as? String, !id.isEmpty {
41
+ AuthenticatedSessionCache.setStorefrontId(id)
42
+ return id
43
+ }
44
+ }
45
+ return localeStorefrontId()
46
+ }
47
+
48
+ /// Fallback when MusicKit auto-token or `/v1/me/storefront` is unavailable (catalog REST only).
49
+ static func localeStorefrontId() -> String {
50
+ if let region = Locale.current.region?.identifier, !region.isEmpty {
51
+ return region.lowercased()
52
+ }
53
+ return "us"
54
+ }
55
+ }
@@ -0,0 +1,119 @@
1
+ // SubscriptionService.swift
2
+ // Handles Apple Music authorization and subscription status checks.
3
+
4
+ import Foundation
5
+ import MusicKit
6
+ import StoreKit
7
+
8
+ @available(iOS 16.0, *)
9
+ final class SubscriptionService {
10
+
11
+ // MARK: - Authorization
12
+
13
+ enum AuthorizationStatus: String {
14
+ case authorized
15
+ case denied
16
+ case notDetermined
17
+ case restricted
18
+ case unknown
19
+ }
20
+
21
+ func requestAuthorization() async -> AuthorizationStatus {
22
+ await withCheckedContinuation { continuation in
23
+ SKCloudServiceController.requestAuthorization { status in
24
+ let result: AuthorizationStatus
25
+ switch status {
26
+ case .authorized: result = .authorized
27
+ case .denied: result = .denied
28
+ case .notDetermined: result = .notDetermined
29
+ case .restricted: result = .restricted
30
+ @unknown default: result = .unknown
31
+ }
32
+ continuation.resume(returning: result)
33
+ }
34
+ }
35
+ }
36
+
37
+ /// Fetches a music user token when a developer JWT is available (not persisted by the module).
38
+ func fetchMusicUserToken(developerToken: String) async -> String? {
39
+ guard !developerToken.isEmpty else { return nil }
40
+ return await withCheckedContinuation { continuation in
41
+ SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { token, _ in
42
+ if let token, !token.isEmpty {
43
+ continuation.resume(returning: token)
44
+ } else {
45
+ continuation.resume(returning: nil)
46
+ }
47
+ }
48
+ }
49
+ }
50
+
51
+ // MARK: - Subscription Check
52
+
53
+ struct SubscriptionDetails {
54
+ let canPlayCatalogContent: Bool
55
+ let canBecomeSubscriber: Bool
56
+ let hasCloudLibraryEnabled: Bool
57
+ let isMusicCatalogSubscriptionEligible: Bool
58
+
59
+ func toDictionary() -> [String: Any] {
60
+ [
61
+ "canPlayCatalogContent": canPlayCatalogContent,
62
+ "canBecomeSubscriber": canBecomeSubscriber,
63
+ "hasCloudLibraryEnabled": hasCloudLibraryEnabled,
64
+ "isMusicCatalogSubscriptionEligible": isMusicCatalogSubscriptionEligible,
65
+ ]
66
+ }
67
+ }
68
+
69
+ func checkSubscription() async throws -> SubscriptionDetails {
70
+ let subscription = try await MusicSubscription.current
71
+ return SubscriptionDetails(
72
+ canPlayCatalogContent: subscription.canPlayCatalogContent,
73
+ canBecomeSubscriber: subscription.canBecomeSubscriber,
74
+ hasCloudLibraryEnabled: subscription.hasCloudLibraryEnabled,
75
+ isMusicCatalogSubscriptionEligible: subscription.canBecomeSubscriber
76
+ )
77
+ }
78
+ }
79
+
80
+ // MARK: - Subscription Error Handling
81
+
82
+ @available(iOS 16.0, *)
83
+ extension SubscriptionService {
84
+
85
+ struct SubscriptionError: LocalizedError {
86
+ let code: String
87
+ let message: String
88
+ let failureReason: String?
89
+ let recoverySuggestion: String?
90
+ let helpAnchor: String?
91
+
92
+ var errorDescription: String? { message }
93
+
94
+ func toNSError() -> NSError {
95
+ var userInfo: [String: Any] = [NSLocalizedDescriptionKey: message]
96
+ if let reason = failureReason {
97
+ userInfo[NSLocalizedFailureReasonErrorKey] = reason
98
+ }
99
+ if let suggestion = recoverySuggestion {
100
+ userInfo[NSLocalizedRecoverySuggestionErrorKey] = suggestion
101
+ }
102
+ if let anchor = helpAnchor {
103
+ userInfo[NSHelpAnchorErrorKey] = anchor
104
+ }
105
+ return NSError(domain: "MusicSubscription", code: 0, userInfo: userInfo)
106
+ }
107
+ }
108
+
109
+ static func wrapSubscriptionError(_ error: Error) -> SubscriptionError? {
110
+ guard let subError = error as? MusicSubscription.Error else { return nil }
111
+ return SubscriptionError(
112
+ code: subError.rawValue,
113
+ message: subError.localizedDescription,
114
+ failureReason: subError.failureReason,
115
+ recoverySuggestion: subError.recoverySuggestion,
116
+ helpAnchor: subError.helpAnchor
117
+ )
118
+ }
119
+ }
@@ -0,0 +1,98 @@
1
+ import Foundation
2
+
3
+ @available(iOS 16.0, *)
4
+ enum ExpoBridgeCatalog {
5
+ static func catalogSearch(
6
+ service: CatalogService,
7
+ term: String,
8
+ types: [String],
9
+ options: NSDictionary
10
+ ) async throws -> [String: Any] {
11
+ try await AppleMusicBridgeError.rethrow {
12
+ let pagination = BridgePagination(from: options)
13
+ let searchOptions = CatalogService.SearchOptions(limit: pagination.limit, offset: pagination.offset)
14
+ let result = try await service.search(term: term, types: types, options: searchOptions)
15
+ return BridgeResponses.catalogSearch(result)
16
+ }
17
+ }
18
+
19
+ static func getCatalogAlbumTracks(
20
+ service: CatalogService,
21
+ albumId: String,
22
+ options: NSDictionary
23
+ ) async throws -> [String: Any] {
24
+ try await AppleMusicBridgeError.rethrow {
25
+ let pagination = BridgePagination(from: options)
26
+ let searchOptions = CatalogService.SearchOptions(limit: pagination.limit, offset: pagination.offset)
27
+ let songs = try await service.getAlbumTracks(albumId: albumId, options: searchOptions)
28
+ return BridgeResponses.songs(songs)
29
+ }
30
+ }
31
+
32
+ static func getCatalogArtistAlbums(
33
+ service: CatalogService,
34
+ artistId: String,
35
+ options: NSDictionary
36
+ ) async throws -> [String: Any] {
37
+ try await AppleMusicBridgeError.rethrow {
38
+ let pagination = BridgePagination(from: options)
39
+ let searchOptions = CatalogService.SearchOptions(limit: pagination.limit, offset: pagination.offset)
40
+ let albums = try await service.getArtistAlbums(artistId: artistId, options: searchOptions)
41
+ return BridgeResponses.albums(albums)
42
+ }
43
+ }
44
+
45
+ static func getCatalogPlaylistTracks(
46
+ service: CatalogService,
47
+ playlistId: String,
48
+ options: NSDictionary
49
+ ) async throws -> [String: Any] {
50
+ try await AppleMusicBridgeError.rethrow {
51
+ let pagination = BridgePagination(from: options)
52
+ let searchOptions = CatalogService.SearchOptions(limit: pagination.limit, offset: pagination.offset)
53
+ let songs = try await service.getPlaylistTracks(playlistId: playlistId, options: searchOptions)
54
+ return BridgeResponses.songs(songs)
55
+ }
56
+ }
57
+
58
+ static func getCatalogCharts(
59
+ service: CatalogService,
60
+ types: [String],
61
+ options: [String: Any]
62
+ ) async throws -> [String: Any] {
63
+ try await AppleMusicBridgeError.rethrow {
64
+ let pagination = BridgePagination(from: options as NSDictionary)
65
+ let searchOptions = CatalogService.SearchOptions(limit: pagination.limit, offset: pagination.offset)
66
+ let genre = options["genre"] as? String
67
+ let chart = options["chart"] as? String
68
+ let result = try await service.getCharts(types: types, options: searchOptions, genre: genre, chart: chart)
69
+ return BridgeResponses.catalogCharts(result)
70
+ }
71
+ }
72
+
73
+ static func getCatalogResources(
74
+ service: CatalogService,
75
+ type: String,
76
+ ids: [String]
77
+ ) async throws -> [String: Any] {
78
+ try await AppleMusicBridgeError.rethrow {
79
+ let items = try await service.getResources(type: type, ids: ids)
80
+ switch type {
81
+ case "songs":
82
+ return BridgeResponses.songs(items)
83
+ case "albums":
84
+ return BridgeResponses.albums(items)
85
+ case "artists":
86
+ return BridgeResponses.artists(items)
87
+ case "playlists":
88
+ return BridgeResponses.playlists(items)
89
+ case "stations":
90
+ return BridgeResponses.stations(items)
91
+ case "music-videos":
92
+ return BridgeResponses.musicVideos(items)
93
+ default:
94
+ throw CatalogService.CatalogServiceError.unknownResourceType(type)
95
+ }
96
+ }
97
+ }
98
+ }
@@ -0,0 +1,71 @@
1
+ import Foundation
2
+
3
+ @available(iOS 16.0, *)
4
+ enum ExpoBridgeHistory {
5
+ static func getRecentlyPlayedResources(
6
+ service: HistoryService,
7
+ musicUserToken: String
8
+ ) async throws -> [String: Any] {
9
+ try await AppleMusicBridgeError.rethrow {
10
+ let tracks = try await service.getRecentlyPlayedResources(musicUserToken: musicUserToken)
11
+ return BridgeResponses.recentlyPlayedResources(tracks)
12
+ }
13
+ }
14
+
15
+ static func getRecentlyPlayedTracks(
16
+ service: HistoryService,
17
+ musicUserToken: String,
18
+ options: NSDictionary
19
+ ) async throws -> [String: Any] {
20
+ try await AppleMusicBridgeError.rethrow {
21
+ let pagination = BridgePagination(from: options)
22
+ let songs = try await service.getRecentlyPlayedTracks(
23
+ musicUserToken: musicUserToken,
24
+ options: pagination)
25
+ return BridgeResponses.songs(songs)
26
+ }
27
+ }
28
+
29
+ static func getHeavyRotation(
30
+ service: HistoryService,
31
+ musicUserToken: String,
32
+ options: NSDictionary
33
+ ) async throws -> [String: Any] {
34
+ try await AppleMusicBridgeError.rethrow {
35
+ let pagination = BridgePagination(from: options)
36
+ let items = try await service.getHeavyRotation(
37
+ musicUserToken: musicUserToken,
38
+ limit: pagination.limit)
39
+ return BridgeResponses.recentItems(items)
40
+ }
41
+ }
42
+
43
+ static func getRecentlyPlayedStations(
44
+ service: HistoryService,
45
+ musicUserToken: String,
46
+ options: NSDictionary
47
+ ) async throws -> [String: Any] {
48
+ try await AppleMusicBridgeError.rethrow {
49
+ let pagination = BridgePagination(from: options)
50
+ let stations = try await service.getRecentlyPlayedStations(
51
+ musicUserToken: musicUserToken,
52
+ limit: pagination.limit)
53
+ return BridgeResponses.stations(stations)
54
+ }
55
+ }
56
+
57
+ static func getRecentlyAdded(
58
+ service: HistoryService,
59
+ musicUserToken: String,
60
+ options: NSDictionary
61
+ ) async throws -> [String: Any] {
62
+ try await AppleMusicBridgeError.rethrow {
63
+ let pagination = BridgePagination(from: options)
64
+ let items = try await service.getRecentlyAdded(
65
+ musicUserToken: musicUserToken,
66
+ limit: pagination.limit,
67
+ offset: pagination.offset)
68
+ return BridgeResponses.recentItems(items)
69
+ }
70
+ }
71
+ }