@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,19 @@
1
+ {
2
+ "platforms": ["apple", "android", "web"],
3
+ "apple": {
4
+ "modules": ["ExpoAppleMusicModule"]
5
+ },
6
+ "android": {
7
+ "modules": ["expo.modules.applemusic.ExpoAppleMusicModule"],
8
+ "gradleAarProjects": [
9
+ {
10
+ "name": "musickit-auth",
11
+ "aarFilePath": "android/libs/musickitauth-release-1.1.2.aar"
12
+ },
13
+ {
14
+ "name": "musickit-playback",
15
+ "aarFilePath": "android/libs/mediaplayback-release-1.1.1.aar"
16
+ }
17
+ ]
18
+ }
19
+ }
@@ -0,0 +1,60 @@
1
+ // AppleMusicBridgeError.swift
2
+ // Map native errors to Expo bridge Exception codes.
3
+
4
+ import ExpoModulesCore
5
+
6
+ @available(iOS 16.0, *)
7
+ enum AppleMusicBridgeError {
8
+ /// Maps thrown errors to Expo `Exception` codes for async bridge methods.
9
+ static func rethrow<T>(_ operation: () async throws -> T) async throws -> T {
10
+ do {
11
+ return try await operation()
12
+ } catch let exception as Exception {
13
+ throw exception
14
+ } catch {
15
+ throw exception(from: error)
16
+ }
17
+ }
18
+
19
+ static func exception(from error: Error) -> Exception {
20
+ if let rest = error as? AppleMusicRestClient.RestError {
21
+ let code = rest.bridgeCode
22
+ return Exception(
23
+ name: code,
24
+ description: rest.errorDescription ?? "Apple Music error",
25
+ code: code
26
+ )
27
+ }
28
+
29
+ if let catalog = error as? CatalogService.CatalogServiceError {
30
+ switch catalog {
31
+ case .configurationRequired(let message):
32
+ return Exception(
33
+ name: AppleMusicErrorCodes.permissionDenied,
34
+ description: message,
35
+ code: AppleMusicErrorCodes.permissionDenied
36
+ )
37
+ case .notFound, .unknownResourceType:
38
+ let message = catalog.errorDescription ?? "Apple Music error"
39
+ return Exception(name: AppleMusicErrorCodes.error, description: message, code: AppleMusicErrorCodes.error)
40
+ }
41
+ }
42
+
43
+ if let subscription = error as? SubscriptionService.SubscriptionError {
44
+ return Exception(
45
+ name: subscription.code,
46
+ description: subscription.errorDescription ?? "Apple Music error",
47
+ code: subscription.code
48
+ )
49
+ }
50
+
51
+ let message = error.localizedDescription
52
+ let code =
53
+ message.localizedCaseInsensitiveContains("403")
54
+ || message.localizedCaseInsensitiveContains("authorization required")
55
+ || message.localizedCaseInsensitiveContains("subscription needed")
56
+ ? AppleMusicErrorCodes.permissionDenied
57
+ : AppleMusicErrorCodes.error
58
+ return Exception(name: code, description: message, code: code)
59
+ }
60
+ }
@@ -0,0 +1,11 @@
1
+ // AppleMusicErrorCodes.swift
2
+ // Bridge rejection codes — keep in sync with src/constants/apple-music-error-codes.ts and docs/ERROR_CODES.md.
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ enum AppleMusicErrorCodes {
8
+ static let error = "ERROR"
9
+ static let permissionDenied = "permissionDenied"
10
+ static let playbackError = "PLAYBACK_ERROR"
11
+ }
@@ -0,0 +1,213 @@
1
+ // AppleMusicRestClient.swift
2
+ // Authenticated requests to api.music.apple.com (URLSession + stored tokens, or MusicDataRequest).
3
+
4
+ import Foundation
5
+ import MusicKit
6
+
7
+ @available(iOS 16.0, *)
8
+ enum AppleMusicHttpMethod: String {
9
+ case get = "GET"
10
+ case post = "POST"
11
+ case put = "PUT"
12
+ case delete = "DELETE"
13
+ }
14
+
15
+ @available(iOS 16.0, *)
16
+ enum AppleMusicRestClient {
17
+
18
+ enum RestError: LocalizedError {
19
+ case invalidURL
20
+ case invalidResponse
21
+ case permissionDenied(String)
22
+ case apiError(String)
23
+
24
+ var errorDescription: String? {
25
+ switch self {
26
+ case .invalidURL: return "Invalid Apple Music API URL"
27
+ case .invalidResponse: return "Invalid Apple Music API response"
28
+ case .permissionDenied(let message): return message
29
+ case .apiError(let message): return message
30
+ }
31
+ }
32
+
33
+ var bridgeCode: String {
34
+ switch self {
35
+ case .permissionDenied:
36
+ return AppleMusicErrorCodes.permissionDenied
37
+ case .invalidURL, .invalidResponse, .apiError:
38
+ return AppleMusicErrorCodes.error
39
+ }
40
+ }
41
+ }
42
+
43
+ static func request(
44
+ method: AppleMusicHttpMethod,
45
+ path: String,
46
+ musicUserToken: String? = nil,
47
+ query: [String: String] = [:],
48
+ body: [String: Any]? = nil
49
+ ) async throws -> [String: Any] {
50
+ let session = AuthenticatedSession.current
51
+ if path.hasPrefix("/v1/me/") {
52
+ guard let musicUserToken, !musicUserToken.isEmpty else {
53
+ throw RestError.apiError("Apple Music REST requires a music user token")
54
+ }
55
+ return try await requestViaUrlSession(
56
+ session: session,
57
+ method: method,
58
+ path: path,
59
+ musicUserToken: musicUserToken,
60
+ query: query,
61
+ body: body)
62
+ }
63
+ if method == .get, session.prefersStoredDeveloperTokenForGet {
64
+ return try await requestViaUrlSession(
65
+ session: session,
66
+ method: method,
67
+ path: path,
68
+ musicUserToken: nil,
69
+ query: query,
70
+ body: body)
71
+ }
72
+ if method == .get, session.canUseMusicKitAutoTokenForGet {
73
+ return try await getViaMusicDataRequest(path: path, query: query)
74
+ }
75
+ return try await requestViaUrlSession(
76
+ session: session,
77
+ method: method,
78
+ path: path,
79
+ musicUserToken: musicUserToken,
80
+ query: query,
81
+ body: body)
82
+ }
83
+
84
+ static func get(
85
+ path: String,
86
+ musicUserToken: String? = nil,
87
+ query: [String: String] = [:]
88
+ ) async throws -> [String: Any] {
89
+ try await request(method: .get, path: path, musicUserToken: musicUserToken, query: query)
90
+ }
91
+
92
+ static func getDataArray(
93
+ path: String,
94
+ musicUserToken: String? = nil,
95
+ query: [String: String] = [:]
96
+ ) async throws -> [[String: Any]] {
97
+ let json = try await get(path: path, musicUserToken: musicUserToken, query: query)
98
+ return try parseDataArray(from: json)
99
+ }
100
+
101
+ /// Top-level list responses must include a `data` array (may be empty).
102
+ static func parseDataArray(from json: [String: Any], key: String = "data") throws -> [[String: Any]] {
103
+ guard json.keys.contains(key) else {
104
+ throw RestError.apiError("Apple Music API response missing \"\(key)\"")
105
+ }
106
+ if json[key] is NSNull {
107
+ return []
108
+ }
109
+ guard let data = json[key] as? [[String: Any]] else {
110
+ throw RestError.apiError("Apple Music API response \"\(key)\" is not an array")
111
+ }
112
+ return data
113
+ }
114
+
115
+ private static func getViaMusicDataRequest(
116
+ path: String,
117
+ query: [String: String]
118
+ ) async throws -> [String: Any] {
119
+ guard var components = URLComponents(string: "https://api.music.apple.com\(path)") else {
120
+ throw RestError.invalidURL
121
+ }
122
+ if !query.isEmpty {
123
+ components.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) }
124
+ }
125
+ guard let url = components.url else {
126
+ throw RestError.invalidURL
127
+ }
128
+
129
+ let urlRequest = URLRequest(url: url)
130
+ let dataRequest = MusicDataRequest(urlRequest: urlRequest)
131
+ let response = try await dataRequest.response()
132
+ return try parseResponseData(response.data)
133
+ }
134
+
135
+ private static func requestViaUrlSession(
136
+ session: AuthenticatedSession,
137
+ method: AppleMusicHttpMethod,
138
+ path: String,
139
+ musicUserToken: String?,
140
+ query: [String: String],
141
+ body: [String: Any]?
142
+ ) async throws -> [String: Any] {
143
+ guard let developerToken = session.developerToken,
144
+ !developerToken.isEmpty
145
+ else {
146
+ if method == .get {
147
+ return try await getViaMusicDataRequest(path: path, query: query)
148
+ }
149
+ throw RestError.apiError("Apple Music REST requires a stored developer token")
150
+ }
151
+
152
+ let userToken = musicUserToken ?? session.musicUserToken
153
+ let requiresUserToken = session.pathRequiresMusicUserToken(path)
154
+
155
+ if requiresUserToken, userToken == nil || userToken?.isEmpty == true {
156
+ throw RestError.apiError("Apple Music REST requires a music user token")
157
+ }
158
+
159
+ guard var components = URLComponents(string: "https://api.music.apple.com\(path)") else {
160
+ throw RestError.invalidURL
161
+ }
162
+ if !query.isEmpty {
163
+ components.queryItems = query.map { URLQueryItem(name: $0.key, value: $0.value) }
164
+ }
165
+ guard let url = components.url else {
166
+ throw RestError.invalidURL
167
+ }
168
+
169
+ var urlRequest = URLRequest(url: url)
170
+ urlRequest.httpMethod = method.rawValue
171
+ urlRequest.setValue("Bearer \(developerToken)", forHTTPHeaderField: "Authorization")
172
+ if let userToken, !userToken.isEmpty {
173
+ urlRequest.setValue(userToken, forHTTPHeaderField: "Music-User-Token")
174
+ }
175
+ if let body {
176
+ urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
177
+ urlRequest.httpBody = try JSONSerialization.data(withJSONObject: body)
178
+ }
179
+
180
+ let (data, response) = try await URLSession.shared.data(for: urlRequest)
181
+ if let http = response as? HTTPURLResponse, !(200...299).contains(http.statusCode) {
182
+ if http.statusCode == 403 {
183
+ throw RestError.permissionDenied(
184
+ "Apple Music authorization required or subscription needed (403)"
185
+ )
186
+ }
187
+ if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
188
+ let errors = json["errors"] as? [[String: Any]],
189
+ let detail = errors.first?["detail"] as? String
190
+ {
191
+ throw RestError.apiError(detail)
192
+ }
193
+ throw RestError.apiError("Apple Music API error (\(http.statusCode))")
194
+ }
195
+ return try parseResponseData(data)
196
+ }
197
+
198
+ private static func parseResponseData(_ data: Data) throws -> [String: Any] {
199
+ if data.isEmpty {
200
+ return [:]
201
+ }
202
+ guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
203
+ throw RestError.invalidResponse
204
+ }
205
+ if let errors = json["errors"] as? [[String: Any]],
206
+ let first = errors.first,
207
+ let detail = first["detail"] as? String
208
+ {
209
+ throw RestError.apiError(detail)
210
+ }
211
+ return json
212
+ }
213
+ }
@@ -0,0 +1,64 @@
1
+ // AuthenticatedSession.swift
2
+ // Single view of stored credentials and REST/catalog transport preferences (see docs/AUTH.md).
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ struct AuthenticatedSession {
8
+ let developerToken: String?
9
+ let musicUserToken: String?
10
+
11
+ static var current: AuthenticatedSession {
12
+ AuthenticatedSession(
13
+ developerToken: MusicKitAuthStorage.getDeveloperToken(),
14
+ musicUserToken: MusicKitAuthStorage.getMusicUserToken()
15
+ )
16
+ }
17
+
18
+ var hasDeveloperToken: Bool {
19
+ guard let developerToken, !developerToken.isEmpty else { return false }
20
+ return true
21
+ }
22
+
23
+ var hasMusicUserToken: Bool {
24
+ guard let musicUserToken, !musicUserToken.isEmpty else { return false }
25
+ return true
26
+ }
27
+
28
+ /// Developer JWT and music user token — required for `/v1/me/` REST and full URLSession auth.
29
+ var hasRestTokens: Bool {
30
+ hasDeveloperToken && hasMusicUserToken
31
+ }
32
+
33
+ /// GET may use MusicKit `MusicDataRequest` when stored REST tokens are incomplete.
34
+ var canUseMusicKitAutoTokenForGet: Bool {
35
+ !hasRestTokens
36
+ }
37
+
38
+ /// GET with a stored developer JWT uses URLSession + Bearer (see `AppleMusicRestClient`).
39
+ var prefersStoredDeveloperTokenForGet: Bool {
40
+ hasDeveloperToken
41
+ }
42
+
43
+ func pathRequiresMusicUserToken(_ path: String) -> Bool {
44
+ path.hasPrefix("/v1/me/")
45
+ }
46
+ }
47
+
48
+ /// In-memory storefront resolved after auth; cleared when tokens change.
49
+ @available(iOS 16.0, *)
50
+ enum AuthenticatedSessionCache {
51
+ private static var storefrontId: String?
52
+
53
+ static func cachedStorefrontId() -> String? {
54
+ storefrontId
55
+ }
56
+
57
+ static func setStorefrontId(_ id: String) {
58
+ storefrontId = id
59
+ }
60
+
61
+ static func invalidate() {
62
+ storefrontId = nil
63
+ }
64
+ }
@@ -0,0 +1,17 @@
1
+ import Foundation
2
+
3
+ /// Shared bridge pagination defaults (limit 25, offset 0).
4
+ struct BridgePagination {
5
+ let limit: Int
6
+ let offset: Int
7
+
8
+ init(from dictionary: NSDictionary) {
9
+ limit = dictionary["limit"] as? Int ?? 25
10
+ offset = dictionary["offset"] as? Int ?? 0
11
+ }
12
+
13
+ init(limit: Int, offset: Int) {
14
+ self.limit = limit
15
+ self.offset = offset
16
+ }
17
+ }
@@ -0,0 +1,82 @@
1
+ import Foundation
2
+
3
+ /// Standard bridge response envelopes (keys match Android and TypeScript).
4
+ enum BridgeResponses {
5
+ static func storefront(id: String) -> [String: Any] {
6
+ ["id": id]
7
+ }
8
+
9
+ static func catalogSearch(_ result: CatalogService.SearchResult) -> [String: Any] {
10
+ [
11
+ "songs": result.songs,
12
+ "albums": result.albums,
13
+ "artists": result.artists,
14
+ "playlists": result.playlists,
15
+ "stations": result.stations,
16
+ "musicVideos": result.musicVideos,
17
+ ]
18
+ }
19
+
20
+ static func catalogCharts(_ result: CatalogService.ChartsResult) -> [String: Any] {
21
+ [
22
+ "songs": result.songs,
23
+ "albums": result.albums,
24
+ "playlists": result.playlists,
25
+ "musicVideos": result.musicVideos,
26
+ ]
27
+ }
28
+
29
+ static func songs(_ items: [[String: Any]]) -> [String: Any] {
30
+ ["songs": items]
31
+ }
32
+
33
+ static func albums(_ items: [[String: Any]]) -> [String: Any] {
34
+ ["albums": items]
35
+ }
36
+
37
+ static func artists(_ items: [[String: Any]]) -> [String: Any] {
38
+ ["artists": items]
39
+ }
40
+
41
+ static func playlists(_ items: [[String: Any]]) -> [String: Any] {
42
+ ["playlists": items]
43
+ }
44
+
45
+ static func musicVideos(_ items: [[String: Any]]) -> [String: Any] {
46
+ ["musicVideos": items]
47
+ }
48
+
49
+ static func librarySearch(_ result: LibraryService.LibrarySearchResult) -> [String: Any] {
50
+ [
51
+ "songs": result.songs,
52
+ "albums": result.albums,
53
+ "artists": result.artists,
54
+ "playlists": result.playlists,
55
+ "musicVideos": result.musicVideos,
56
+ ]
57
+ }
58
+
59
+ static func stations(_ items: [[String: Any]]) -> [String: Any] {
60
+ ["stations": items]
61
+ }
62
+
63
+ static func recentlyPlayedResources(_ items: [[String: Any]]) -> [String: Any] {
64
+ ["recentlyPlayedItems": items]
65
+ }
66
+
67
+ static func recentItems(_ items: [[String: Any]]) -> [String: Any] {
68
+ ["items": items]
69
+ }
70
+
71
+ static func recommendations(_ items: [[String: Any]]) -> [String: Any] {
72
+ ["recommendations": items]
73
+ }
74
+
75
+ static func replaySummaries(_ items: [[String: Any]]) -> [String: Any] {
76
+ ["summaries": items]
77
+ }
78
+
79
+ static func configurePlayer(mixWithOthers: Bool) -> [String: Any] {
80
+ ["mixWithOthers": mixWithOthers]
81
+ }
82
+ }
@@ -0,0 +1,13 @@
1
+ // CatalogSearchStore.swift
2
+ // Catalog search transport seam — native MusicKit vs REST (developer JWT).
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ protocol CatalogSearchStore {
8
+ func search(
9
+ term: String,
10
+ types: [String],
11
+ options: CatalogService.SearchOptions
12
+ ) async throws -> CatalogService.SearchResult
13
+ }
@@ -0,0 +1,31 @@
1
+ // CatalogSearchStoreFactory.swift
2
+ // Native MusicKit search first; REST fallback only when auto-token fails and a developer JWT is stored.
3
+
4
+ import Foundation
5
+
6
+ @available(iOS 16.0, *)
7
+ enum CatalogSearchStoreFactory {
8
+
9
+ static func search(
10
+ term: String,
11
+ types: [String],
12
+ options: CatalogService.SearchOptions
13
+ ) async throws -> CatalogService.SearchResult {
14
+ do {
15
+ return try await MusicKitCatalogSearchStore().search(
16
+ term: term, types: types, options: options)
17
+ } catch {
18
+ if MusicKitCatalogSearchStore.isClientNotRegistered(error),
19
+ AuthenticatedSession.current.hasDeveloperToken
20
+ {
21
+ return try await RestCatalogSearchStore().search(
22
+ term: term, types: types, options: options)
23
+ }
24
+ if MusicKitCatalogSearchStore.isClientNotRegistered(error) {
25
+ throw CatalogService.CatalogServiceError.configurationRequired(
26
+ MusicKitCatalogSearchStore.missingDeveloperTokenMessage)
27
+ }
28
+ throw error
29
+ }
30
+ }
31
+ }