@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,313 @@
1
+ // LibraryService.swift
2
+ // Handles user's Apple Music library operations.
3
+
4
+ import Foundation
5
+ import MusicKit
6
+
7
+ @available(iOS 16.0, *)
8
+ final class LibraryService {
9
+
10
+ // MARK: - Options
11
+
12
+ func getArtists(musicUserToken: String, options: BridgePagination) async throws -> [[String: Any]] {
13
+ let data = try await AppleMusicRestClient.getDataArray(
14
+ path: "/v1/me/library/artists",
15
+ musicUserToken: musicUserToken,
16
+ query: ["limit": "\(options.limit)", "offset": "\(options.offset)"]
17
+ )
18
+ return data.map(RestJsonMapper.mapArtist)
19
+ }
20
+
21
+ private func getArtistsNative(options: BridgePagination) async throws -> [[String: Any]] {
22
+ var request = MusicLibraryRequest<Artist>()
23
+ request.limit = options.limit
24
+ request.offset = options.offset
25
+ let response = try await request.response()
26
+ return response.items.map(MusicItemMapper.map)
27
+ }
28
+
29
+ func getAlbums(musicUserToken: String, options: BridgePagination) async throws -> [[String: Any]] {
30
+ let data = try await AppleMusicRestClient.getDataArray(
31
+ path: "/v1/me/library/albums",
32
+ musicUserToken: musicUserToken,
33
+ query: ["limit": "\(options.limit)", "offset": "\(options.offset)"]
34
+ )
35
+ return data.map(RestJsonMapper.mapAlbum)
36
+ }
37
+
38
+ private func getAlbumsNative(options: BridgePagination) async throws -> [[String: Any]] {
39
+ var request = MusicLibraryRequest<Album>()
40
+ request.limit = options.limit
41
+ request.offset = options.offset
42
+ let response = try await request.response()
43
+ return response.items.map(MusicItemMapper.map)
44
+ }
45
+
46
+ // MARK: - Playlists
47
+
48
+ func getPlaylists(musicUserToken: String, options: BridgePagination) async throws -> [[String: Any]] {
49
+ let data = try await AppleMusicRestClient.getDataArray(
50
+ path: "/v1/me/library/playlists",
51
+ musicUserToken: musicUserToken,
52
+ query: ["limit": "\(options.limit)", "offset": "\(options.offset)"]
53
+ )
54
+ return data.map(RestJsonMapper.mapPlaylist)
55
+ }
56
+
57
+ private func getPlaylistsNative(options: BridgePagination) async throws -> [[String: Any]] {
58
+ var request = MusicLibraryRequest<Playlist>()
59
+ request.limit = options.limit
60
+ request.offset = options.offset
61
+
62
+ let response = try await request.response()
63
+
64
+ // Load tracks for accurate count
65
+ var playlists: [[String: Any]] = []
66
+ for playlist in response.items {
67
+ let detailed = try await playlist.with([.tracks])
68
+ playlists.append(MusicItemMapper.map(detailed))
69
+ }
70
+ return playlists
71
+ }
72
+
73
+ func getPlaylistSongs(musicUserToken: String, playlistId: String) async throws -> [[String: Any]] {
74
+ let json = try await AppleMusicRestClient.get(
75
+ path: "/v1/me/library/playlists/\(playlistId)/tracks",
76
+ musicUserToken: musicUserToken
77
+ )
78
+ guard let data = json["data"] as? [[String: Any]] else {
79
+ throw LibraryServiceError.playlistNotFound
80
+ }
81
+ return data.compactMap { row -> [String: Any]? in
82
+ guard let type = row["type"] as? String, type.contains("song") else { return nil }
83
+ return RestJsonMapper.mapSong(row)
84
+ }
85
+ }
86
+
87
+ private func getPlaylistSongsNative(playlistId: String) async throws -> [[String: Any]] {
88
+ let musicItemId = MusicItemID(playlistId)
89
+
90
+ var request = MusicLibraryRequest<Playlist>()
91
+ request.filter(matching: \.id, equalTo: musicItemId)
92
+
93
+ let response = try await request.response()
94
+ guard let playlist = response.items.first else {
95
+ throw LibraryServiceError.playlistNotFound
96
+ }
97
+
98
+ let detailed = try await playlist.with([.tracks])
99
+ var songs: [[String: Any]] = []
100
+
101
+ if let tracks = detailed.tracks {
102
+ for track in tracks {
103
+ if case .song(let song) = track {
104
+ songs.append(MusicItemMapper.map(song))
105
+ }
106
+ }
107
+ }
108
+ return songs
109
+ }
110
+
111
+ // MARK: - Songs
112
+
113
+ func getSongs(musicUserToken: String, options: BridgePagination) async throws -> [[String: Any]] {
114
+ let data = try await AppleMusicRestClient.getDataArray(
115
+ path: "/v1/me/library/songs",
116
+ musicUserToken: musicUserToken,
117
+ query: ["limit": "\(options.limit)", "offset": "\(options.offset)"]
118
+ )
119
+ return data.map(RestJsonMapper.mapSong)
120
+ }
121
+
122
+ private func getSongsNative(options: BridgePagination) async throws -> [[String: Any]] {
123
+ var request = MusicLibraryRequest<Song>()
124
+ request.limit = options.limit
125
+ request.offset = options.offset
126
+
127
+ let response = try await request.response()
128
+ return response.items.map(MusicItemMapper.map)
129
+ }
130
+
131
+ func getMusicVideos(musicUserToken: String, options: BridgePagination) async throws -> [[String: Any]] {
132
+ let data = try await AppleMusicRestClient.getDataArray(
133
+ path: "/v1/me/library/music-videos",
134
+ musicUserToken: musicUserToken,
135
+ query: ["limit": "\(options.limit)", "offset": "\(options.offset)"]
136
+ )
137
+ return data.map(RestJsonMapper.mapMusicVideo)
138
+ }
139
+
140
+ private func getMusicVideosNative(options: BridgePagination) async throws -> [[String: Any]] {
141
+ var request = MusicLibraryRequest<MusicVideo>()
142
+ request.limit = options.limit
143
+ request.offset = options.offset
144
+ let response = try await request.response()
145
+ return response.items.map(MusicItemMapper.map)
146
+ }
147
+
148
+ struct LibrarySearchResult {
149
+ let songs: [[String: Any]]
150
+ let albums: [[String: Any]]
151
+ let artists: [[String: Any]]
152
+ let playlists: [[String: Any]]
153
+ let musicVideos: [[String: Any]]
154
+ }
155
+
156
+ func search(musicUserToken: String, term: String, types: [String], options: BridgePagination) async throws -> LibrarySearchResult {
157
+ return try await searchViaRest(musicUserToken: musicUserToken, term: term, types: types, options: options)
158
+ }
159
+
160
+ private func searchNative(term: String, types: [String], options: BridgePagination) async throws -> LibrarySearchResult {
161
+ if options.offset > 0 {
162
+ return try await searchViaRest(musicUserToken: "", term: term, types: types, options: options)
163
+ }
164
+
165
+ let searchTypes = types.compactMap { Self.librarySearchableType($0) }
166
+ let resolvedTypes: [any MusicLibrarySearchable.Type]
167
+ if searchTypes.isEmpty {
168
+ resolvedTypes = [Song.self, Album.self, Artist.self, Playlist.self, MusicVideo.self]
169
+ } else {
170
+ resolvedTypes = searchTypes
171
+ }
172
+
173
+ var request = MusicLibrarySearchRequest(term: term, types: resolvedTypes)
174
+ request.limit = options.limit
175
+ let response = try await request.response()
176
+
177
+ return LibrarySearchResult(
178
+ songs: response.songs.map(MusicItemMapper.map),
179
+ albums: response.albums.map(MusicItemMapper.map),
180
+ artists: response.artists.map(MusicItemMapper.map),
181
+ playlists: response.playlists.map(MusicItemMapper.map),
182
+ musicVideos: response.musicVideos.map(MusicItemMapper.map)
183
+ )
184
+ }
185
+
186
+ /// MusicKit `MusicLibrarySearchRequest` has no `offset`; REST matches Android pagination.
187
+ private func searchViaRest(
188
+ musicUserToken: String,
189
+ term: String,
190
+ types: [String],
191
+ options: BridgePagination
192
+ ) async throws -> LibrarySearchResult {
193
+ let typeParam = Array(Set(types.compactMap { Self.librarySearchRestTypeParam($0) })).sorted().joined(
194
+ separator: ",")
195
+ let typesQuery = typeParam.isEmpty ? "library-songs,library-albums" : typeParam
196
+
197
+ let json = try await AppleMusicRestClient.get(
198
+ path: "/v1/me/library/search",
199
+ musicUserToken: musicUserToken,
200
+ query: [
201
+ "term": term,
202
+ "types": typesQuery,
203
+ "limit": "\(options.limit)",
204
+ "offset": "\(options.offset)",
205
+ ]
206
+ )
207
+
208
+ let results = json["results"] as? [String: Any] ?? [:]
209
+ return LibrarySearchResult(
210
+ songs: parseLibrarySearchBucket(results: results, key: "library-songs", mapper: RestJsonMapper.mapSong),
211
+ albums: parseLibrarySearchBucket(results: results, key: "library-albums", mapper: RestJsonMapper.mapAlbum),
212
+ artists: parseLibrarySearchBucket(results: results, key: "library-artists", mapper: RestJsonMapper.mapArtist),
213
+ playlists: parseLibrarySearchBucket(
214
+ results: results, key: "library-playlists", mapper: RestJsonMapper.mapPlaylist),
215
+ musicVideos: parseLibrarySearchBucket(
216
+ results: results, key: "library-music-videos", mapper: RestJsonMapper.mapMusicVideo)
217
+ )
218
+ }
219
+
220
+ private func parseLibrarySearchBucket(
221
+ results: [String: Any],
222
+ key: String,
223
+ mapper: ([String: Any]) -> [String: Any]
224
+ ) -> [[String: Any]] {
225
+ guard let bucket = results[key] as? [String: Any],
226
+ let data = bucket["data"] as? [[String: Any]]
227
+ else {
228
+ return []
229
+ }
230
+ return data.map(mapper)
231
+ }
232
+
233
+ private static func librarySearchRestTypeParam(_ type: String) -> String? {
234
+ switch type {
235
+ case "library-songs", "songs": return "library-songs"
236
+ case "library-albums", "albums": return "library-albums"
237
+ case "library-artists", "artists": return "library-artists"
238
+ case "library-playlists", "playlists": return "library-playlists"
239
+ case "library-music-videos", "music-videos", "musicVideos": return "library-music-videos"
240
+ default: return nil
241
+ }
242
+ }
243
+
244
+ private static func librarySearchableType(_ type: String) -> (any MusicLibrarySearchable.Type)? {
245
+ switch type {
246
+ case "library-songs", "songs": return Song.self
247
+ case "library-albums", "albums": return Album.self
248
+ case "library-artists", "artists": return Artist.self
249
+ case "library-playlists", "playlists": return Playlist.self
250
+ case "library-music-videos", "music-videos", "musicVideos": return MusicVideo.self
251
+ default: return nil
252
+ }
253
+ }
254
+
255
+ // MARK: - Fetch Items by ID
256
+
257
+ func fetchSong(id: MusicItemID) async throws -> Song? {
258
+ var request = MusicLibraryRequest<Song>()
259
+ request.filter(matching: \.id, equalTo: id)
260
+ let response = try await request.response()
261
+ return response.items.first
262
+ }
263
+
264
+ func fetchAlbum(id: MusicItemID) async throws -> Album? {
265
+ var request = MusicLibraryRequest<Album>()
266
+ request.filter(matching: \.id, equalTo: id)
267
+ let response = try await request.response()
268
+ return response.items.first
269
+ }
270
+
271
+ func fetchPlaylist(id: MusicItemID) async throws -> Playlist? {
272
+ var request = MusicLibraryRequest<Playlist>()
273
+ request.filter(matching: \.id, equalTo: id)
274
+ let response = try await request.response()
275
+ guard let playlist = response.items.first else { return nil }
276
+ return try await playlist.with([.tracks])
277
+ }
278
+
279
+ // MARK: - Extract Songs from Playlist
280
+
281
+ func extractSongs(from playlist: Playlist) async throws -> [Song] {
282
+ let detailed = try await playlist.with([.tracks])
283
+ guard let tracks = detailed.tracks else { return [] }
284
+
285
+ var songs: [Song] = []
286
+ for track in tracks {
287
+ if case .song(let song) = track {
288
+ songs.append(song)
289
+ }
290
+ }
291
+ return songs
292
+ }
293
+ }
294
+
295
+ // MARK: - Errors
296
+
297
+ enum LibraryServiceError: LocalizedError {
298
+ case playlistNotFound
299
+ case songNotFound
300
+ case albumNotFound
301
+ case noTracksInPlaylist
302
+ case noSongsInPlaylist
303
+
304
+ var errorDescription: String? {
305
+ switch self {
306
+ case .playlistNotFound: return "Playlist not found in library"
307
+ case .songNotFound: return "Song not found in library"
308
+ case .albumNotFound: return "Album not found in library"
309
+ case .noTracksInPlaylist: return "No tracks in playlist"
310
+ case .noSongsInPlaylist: return "No songs in playlist"
311
+ }
312
+ }
313
+ }
@@ -0,0 +1,171 @@
1
+ // MusicItemMapper.swift
2
+ // Maps MusicKit types to bridge dictionaries (must match RestJsonMapper / AppleMusicJsonMapper).
3
+
4
+ import Foundation
5
+ import MusicKit
6
+
7
+ @available(iOS 16.0, *)
8
+ enum MusicItemMapper {
9
+
10
+ // MARK: - Song
11
+
12
+ static func map(_ song: Song) -> [String: Any] {
13
+ [
14
+ "id": catalogPlaybackId(from: song),
15
+ "title": song.title,
16
+ "artistName": song.artistName,
17
+ "artworkUrl": extractArtworkURL(song.artwork),
18
+ "duration": durationMillis(song.duration),
19
+ ]
20
+ }
21
+
22
+ // MARK: - Album
23
+
24
+ static func map(_ album: Album) -> [String: Any] {
25
+ [
26
+ "id": musicItemId(album.id),
27
+ "title": album.title,
28
+ "artistName": album.artistName,
29
+ "artworkUrl": extractArtworkURL(album.artwork),
30
+ "trackCount": album.trackCount,
31
+ ]
32
+ }
33
+
34
+ // MARK: - Artist
35
+
36
+ static func map(_ artist: Artist) -> [String: Any] {
37
+ [
38
+ "id": musicItemId(artist.id),
39
+ "name": artist.name,
40
+ "artworkUrl": extractArtworkURL(artist.artwork),
41
+ ]
42
+ }
43
+
44
+ // MARK: - Playlist
45
+
46
+ static func map(_ playlist: Playlist) -> [String: Any] {
47
+ [
48
+ "id": musicItemId(playlist.id),
49
+ "name": playlist.name,
50
+ "description": playlist.standardDescription ?? "",
51
+ "artworkUrl": extractArtworkURL(playlist.artwork),
52
+ "trackCount": playlistTrackCount(playlist),
53
+ ]
54
+ }
55
+
56
+ // MARK: - Station
57
+
58
+ static func map(_ station: Station) -> [String: Any] {
59
+ [
60
+ "id": musicItemId(station.id),
61
+ "name": station.name,
62
+ "artworkUrl": extractArtworkURL(station.artwork),
63
+ ]
64
+ }
65
+
66
+ // MARK: - Music Video
67
+
68
+ @available(iOS 16.0, *)
69
+ static func map(_ musicVideo: MusicVideo) -> [String: Any] {
70
+ [
71
+ "id": catalogPlaybackId(from: musicVideo),
72
+ "title": musicVideo.title,
73
+ "artistName": musicVideo.artistName,
74
+ "artworkUrl": extractArtworkURL(musicVideo.artwork),
75
+ "duration": durationMillis(musicVideo.duration),
76
+ ]
77
+ }
78
+
79
+ // MARK: - Recently Played Items
80
+
81
+ @available(iOS 16.0, *)
82
+ static func map(_ item: RecentlyPlayedMusicItem) -> [String: Any] {
83
+ var result: [String: Any] = [
84
+ "id": musicItemId(item.id),
85
+ "title": item.title,
86
+ "subtitle": String(describing: item.subtitle ?? ""),
87
+ ]
88
+
89
+ switch item {
90
+ case .album:
91
+ result["type"] = "album"
92
+ case .playlist:
93
+ result["type"] = "playlist"
94
+ case .station:
95
+ result["type"] = "station"
96
+ default:
97
+ result["type"] = "unknown"
98
+ }
99
+
100
+ return result
101
+ }
102
+
103
+ // MARK: - Playback Status
104
+
105
+ static func describePlaybackStatus(_ status: MusicPlayer.PlaybackStatus) -> String {
106
+ switch status {
107
+ case .playing: return "playing"
108
+ case .paused: return "paused"
109
+ case .stopped: return "stopped"
110
+ case .interrupted: return "interrupted"
111
+ case .seekingForward: return "seekingForward"
112
+ case .seekingBackward: return "seekingBackward"
113
+ @unknown default: return "unknown"
114
+ }
115
+ }
116
+
117
+ // MARK: - ID & duration helpers (parity with RestJsonMapper / Android)
118
+
119
+ static func musicItemId(_ id: MusicItemID) -> String {
120
+ id.rawValue
121
+ }
122
+
123
+ static func catalogPlaybackId(from song: Song) -> String {
124
+ if let playId = catalogPlaybackId(from: song.playParameters) {
125
+ return playId
126
+ }
127
+ return musicItemId(song.id)
128
+ }
129
+
130
+ static func catalogPlaybackId(from musicVideo: MusicVideo) -> String {
131
+ if let playId = catalogPlaybackId(from: musicVideo.playParameters) {
132
+ return playId
133
+ }
134
+ return musicItemId(musicVideo.id)
135
+ }
136
+
137
+ /// MusicKit no longer exposes `PlayParameters.id`; decode Codable payload (parity with RestJsonMapper).
138
+ private static func catalogPlaybackId(from playParameters: PlayParameters?) -> String? {
139
+ guard let playParameters,
140
+ let data = try? JSONEncoder().encode(playParameters),
141
+ let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
142
+ else { return nil }
143
+ if let id = json["id"] as? String, !id.isEmpty { return id }
144
+ if let catalogId = json["catalogId"] as? String, !catalogId.isEmpty { return catalogId }
145
+ return nil
146
+ }
147
+
148
+ /// MusicKit `duration` is seconds; bridge uses milliseconds (matches `durationInMillis` from REST).
149
+ static func durationMillis(_ seconds: TimeInterval?) -> Int {
150
+ Int((seconds ?? 0) * 1000)
151
+ }
152
+
153
+ private static func playlistTrackCount(_ playlist: Playlist) -> Int {
154
+ if let count = playlist.tracks?.count, count > 0 {
155
+ return count
156
+ }
157
+ return playlist.entries?.count ?? 0
158
+ }
159
+
160
+ // MARK: - Private Helpers
161
+
162
+ private static func extractArtworkURL(_ artwork: Artwork?, width: Int = 200, height: Int = 200) -> String {
163
+ guard let artwork = artwork,
164
+ let url = artwork.url(width: width, height: height),
165
+ url.scheme == "https" || url.scheme == "http"
166
+ else {
167
+ return ""
168
+ }
169
+ return url.absoluteString
170
+ }
171
+ }
@@ -0,0 +1,38 @@
1
+ // MusicKitAuthStorage.swift
2
+ // Persists developer and music user tokens for Apple Music REST (parity with Android).
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ enum MusicKitAuthStorage {
8
+ private static let developerTokenKey = "expo.modules.applemusic.developerToken"
9
+ private static let musicUserTokenKey = "expo.modules.applemusic.musicUserToken"
10
+
11
+ static func saveDeveloperToken(_ token: String) {
12
+ UserDefaults.standard.set(token, forKey: developerTokenKey)
13
+ AuthenticatedSessionCache.invalidate()
14
+ }
15
+
16
+ static func getDeveloperToken() -> String? {
17
+ UserDefaults.standard.string(forKey: developerTokenKey)
18
+ }
19
+
20
+ static func saveMusicUserToken(_ token: String) {
21
+ UserDefaults.standard.set(token, forKey: musicUserTokenKey)
22
+ AuthenticatedSessionCache.invalidate()
23
+ }
24
+
25
+ static func getMusicUserToken() -> String? {
26
+ UserDefaults.standard.string(forKey: musicUserTokenKey)
27
+ }
28
+
29
+ static func clearMusicUserToken() {
30
+ UserDefaults.standard.removeObject(forKey: musicUserTokenKey)
31
+ AuthenticatedSessionCache.invalidate()
32
+ }
33
+
34
+ static func clearDeveloperToken() {
35
+ UserDefaults.standard.removeObject(forKey: developerTokenKey)
36
+ AuthenticatedSessionCache.invalidate()
37
+ }
38
+ }
@@ -0,0 +1,62 @@
1
+ // MusicKitCatalogSearchStore.swift
2
+ // Catalog search via native MusicCatalogSearchRequest.
3
+
4
+ import Foundation
5
+ import MusicKit
6
+
7
+ @available(iOS 16.0, *)
8
+ struct MusicKitCatalogSearchStore: CatalogSearchStore {
9
+
10
+ static let missingDeveloperTokenMessage =
11
+ "Apple Music catalog search needs a developer JWT on this device. "
12
+ + "From the repo root: npm run dev-token -- --write-env example/.env.local "
13
+ + "then restart Metro (npx expo start --clear), rebuild, and tap Authorize. "
14
+ + "See docs/CLI.md. (Native MusicKit auto-token returned 404 for this bundle ID.)"
15
+
16
+ func search(
17
+ term: String,
18
+ types: [String],
19
+ options: CatalogService.SearchOptions
20
+ ) async throws -> CatalogService.SearchResult {
21
+ let searchTypes = types.compactMap { typeString -> MusicCatalogSearchable.Type? in
22
+ switch typeString {
23
+ case "songs": return Song.self
24
+ case "albums": return Album.self
25
+ case "artists": return Artist.self
26
+ case "playlists": return Playlist.self
27
+ case "stations": return Station.self
28
+ case "music-videos": return MusicVideo.self
29
+ default: return nil
30
+ }
31
+ }
32
+
33
+ var request = MusicCatalogSearchRequest(term: term, types: searchTypes)
34
+ request.limit = options.limit
35
+ request.offset = options.offset
36
+
37
+ let response = try await request.response()
38
+
39
+ return CatalogService.SearchResult(
40
+ songs: response.songs.map(MusicItemMapper.map),
41
+ albums: response.albums.map(MusicItemMapper.map),
42
+ artists: response.artists.map(MusicItemMapper.map),
43
+ playlists: response.playlists.map(MusicItemMapper.map),
44
+ stations: response.stations.map(MusicItemMapper.map),
45
+ musicVideos: response.musicVideos.map(MusicItemMapper.map)
46
+ )
47
+ }
48
+
49
+ static func isClientNotRegistered(_ error: Error) -> Bool {
50
+ let ns = error as NSError
51
+ if ns.domain == "ICError", ns.code == -8200 { return true }
52
+ if ns.localizedDescription.contains("Client not found") { return true }
53
+ if ns.localizedDescription.contains("developerTokenRequestFailed") { return true }
54
+ if let underlying = ns.userInfo[NSUnderlyingErrorKey] as? NSError,
55
+ underlying.domain == "AMSErrorDomain",
56
+ underlying.code == 301
57
+ {
58
+ return true
59
+ }
60
+ return false
61
+ }
62
+ }