@neteasecloudmusicapienhanced/api 4.29.11

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 (412) hide show
  1. package/LICENSE +21 -0
  2. package/README.MD +178 -0
  3. package/app.js +18 -0
  4. package/data/deviceid.txt +24641 -0
  5. package/generateConfig.js +24 -0
  6. package/interface.d.ts +1834 -0
  7. package/main.js +63 -0
  8. package/module/activate_init_profile.js +9 -0
  9. package/module/aidj_content_rcmd.js +28 -0
  10. package/module/album.js +6 -0
  11. package/module/album_detail.js +12 -0
  12. package/module/album_detail_dynamic.js +12 -0
  13. package/module/album_list.js +16 -0
  14. package/module/album_list_style.js +15 -0
  15. package/module/album_new.js +11 -0
  16. package/module/album_newest.js +6 -0
  17. package/module/album_privilege.js +9 -0
  18. package/module/album_songsaleboard.js +19 -0
  19. package/module/album_sub.js +10 -0
  20. package/module/album_sublist.js +11 -0
  21. package/module/api.js +21 -0
  22. package/module/artist_album.js +15 -0
  23. package/module/artist_desc.js +9 -0
  24. package/module/artist_detail.js +10 -0
  25. package/module/artist_detail_dynamic.js +9 -0
  26. package/module/artist_fans.js +11 -0
  27. package/module/artist_follow_count.js +13 -0
  28. package/module/artist_list.js +33 -0
  29. package/module/artist_mv.js +12 -0
  30. package/module/artist_new_mv.js +12 -0
  31. package/module/artist_new_song.js +12 -0
  32. package/module/artist_songs.js +12 -0
  33. package/module/artist_sub.js +11 -0
  34. package/module/artist_sublist.js +11 -0
  35. package/module/artist_top_song.js +8 -0
  36. package/module/artist_video.js +15 -0
  37. package/module/artists.js +6 -0
  38. package/module/audio_match.js +19 -0
  39. package/module/avatar_upload.js +22 -0
  40. package/module/banner.js +16 -0
  41. package/module/batch.js +12 -0
  42. package/module/broadcast_category_region_get.js +11 -0
  43. package/module/broadcast_channel_collect_list.js +12 -0
  44. package/module/broadcast_channel_currentinfo.js +13 -0
  45. package/module/broadcast_channel_list.js +13 -0
  46. package/module/broadcast_sub.js +12 -0
  47. package/module/calendar.js +8 -0
  48. package/module/captcha_sent.js +11 -0
  49. package/module/captcha_verify.js +11 -0
  50. package/module/cellphone_existence_check.js +10 -0
  51. package/module/check_music.js +30 -0
  52. package/module/cloud.js +146 -0
  53. package/module/cloud_import.js +39 -0
  54. package/module/cloud_match.js +13 -0
  55. package/module/cloudsearch.js +13 -0
  56. package/module/comment.js +30 -0
  57. package/module/comment_album.js +16 -0
  58. package/module/comment_dj.js +16 -0
  59. package/module/comment_event.js +15 -0
  60. package/module/comment_floor.js +16 -0
  61. package/module/comment_hot.js +18 -0
  62. package/module/comment_hug_list.js +20 -0
  63. package/module/comment_like.js +20 -0
  64. package/module/comment_music.js +16 -0
  65. package/module/comment_mv.js +16 -0
  66. package/module/comment_new.js +37 -0
  67. package/module/comment_playlist.js +16 -0
  68. package/module/comment_video.js +16 -0
  69. package/module/countries_code_list.js +6 -0
  70. package/module/creator_authinfo_get.js +6 -0
  71. package/module/daily_signin.js +16 -0
  72. package/module/digitalAlbum_detail.js +13 -0
  73. package/module/digitalAlbum_ordering.js +22 -0
  74. package/module/digitalAlbum_purchased.js +15 -0
  75. package/module/digitalAlbum_sales.js +13 -0
  76. package/module/djRadio_top.js +15 -0
  77. package/module/dj_banner.js +6 -0
  78. package/module/dj_category_excludehot.js +10 -0
  79. package/module/dj_category_recommend.js +10 -0
  80. package/module/dj_catelist.js +6 -0
  81. package/module/dj_detail.js +9 -0
  82. package/module/dj_hot.js +10 -0
  83. package/module/dj_paygift.js +15 -0
  84. package/module/dj_personalize_recommend.js +12 -0
  85. package/module/dj_program.js +12 -0
  86. package/module/dj_program_detail.js +9 -0
  87. package/module/dj_program_toplist.js +10 -0
  88. package/module/dj_program_toplist_hours.js +13 -0
  89. package/module/dj_radio_hot.js +11 -0
  90. package/module/dj_recommend.js +6 -0
  91. package/module/dj_recommend_type.js +32 -0
  92. package/module/dj_sub.js +10 -0
  93. package/module/dj_sublist.js +11 -0
  94. package/module/dj_subscriber.js +12 -0
  95. package/module/dj_today_perfered.js +13 -0
  96. package/module/dj_toplist.js +14 -0
  97. package/module/dj_toplist_hours.js +10 -0
  98. package/module/dj_toplist_newcomer.js +9 -0
  99. package/module/dj_toplist_pay.js +9 -0
  100. package/module/dj_toplist_popular.js +10 -0
  101. package/module/eapi_decrypt.js +27 -0
  102. package/module/event.js +10 -0
  103. package/module/event_del.js +9 -0
  104. package/module/event_forward.js +11 -0
  105. package/module/fanscenter_basicinfo_age_get.js +6 -0
  106. package/module/fanscenter_basicinfo_gender_get.js +10 -0
  107. package/module/fanscenter_basicinfo_province_get.js +10 -0
  108. package/module/fanscenter_overview_get.js +6 -0
  109. package/module/fanscenter_trend_list.js +10 -0
  110. package/module/fm_trash.js +11 -0
  111. package/module/follow.js +11 -0
  112. package/module/get_userids.js +7 -0
  113. package/module/history_recommend_songs.js +11 -0
  114. package/module/history_recommend_songs_detail.js +13 -0
  115. package/module/homepage_block_page.js +8 -0
  116. package/module/homepage_dragon_ball.js +10 -0
  117. package/module/hot_topic.js +10 -0
  118. package/module/hug_comment.js +16 -0
  119. package/module/inner_version.js +16 -0
  120. package/module/like.js +13 -0
  121. package/module/likelist.js +9 -0
  122. package/module/listen_data_realtime_report.js +11 -0
  123. package/module/listen_data_report.js +12 -0
  124. package/module/listen_data_today_song.js +9 -0
  125. package/module/listen_data_total.js +9 -0
  126. package/module/listen_data_year_report.js +9 -0
  127. package/module/listentogether_accept.js +13 -0
  128. package/module/listentogether_end.js +9 -0
  129. package/module/listentogether_heatbeat.js +12 -0
  130. package/module/listentogether_play_command.js +21 -0
  131. package/module/listentogether_room_check.js +9 -0
  132. package/module/listentogether_room_create.js +9 -0
  133. package/module/listentogether_status.js +10 -0
  134. package/module/listentogether_sync_list_command.js +26 -0
  135. package/module/listentogether_sync_playlist_get.js +13 -0
  136. package/module/login.js +41 -0
  137. package/module/login_cellphone.js +40 -0
  138. package/module/login_qr_check.js +29 -0
  139. package/module/login_qr_create.js +30 -0
  140. package/module/login_qr_key.js +19 -0
  141. package/module/login_refresh.js +21 -0
  142. package/module/login_status.js +21 -0
  143. package/module/logout.js +6 -0
  144. package/module/lyric.js +14 -0
  145. package/module/lyric_new.js +17 -0
  146. package/module/mlog_music_rcmd.js +13 -0
  147. package/module/mlog_to_video.js +13 -0
  148. package/module/mlog_url.js +11 -0
  149. package/module/msg_comments.js +17 -0
  150. package/module/msg_forwards.js +11 -0
  151. package/module/msg_notices.js +10 -0
  152. package/module/msg_private.js +11 -0
  153. package/module/msg_private_history.js +12 -0
  154. package/module/msg_recentcontact.js +11 -0
  155. package/module/music_first_listen_info.js +13 -0
  156. package/module/musician_cloudbean.js +7 -0
  157. package/module/musician_cloudbean_obtain.js +14 -0
  158. package/module/musician_data_overview.js +11 -0
  159. package/module/musician_play_trend.js +14 -0
  160. package/module/musician_sign.js +7 -0
  161. package/module/musician_tasks.js +11 -0
  162. package/module/musician_tasks_new.js +11 -0
  163. package/module/mv_all.js +16 -0
  164. package/module/mv_detail.js +9 -0
  165. package/module/mv_detail_info.js +14 -0
  166. package/module/mv_exclusive_rcmd.js +10 -0
  167. package/module/mv_first.js +12 -0
  168. package/module/mv_sub.js +11 -0
  169. package/module/mv_sublist.js +15 -0
  170. package/module/mv_url.js +14 -0
  171. package/module/nickname_check.js +7 -0
  172. package/module/personal_fm.js +6 -0
  173. package/module/personal_fm_mode.js +14 -0
  174. package/module/personalized.js +16 -0
  175. package/module/personalized_djprogram.js +10 -0
  176. package/module/personalized_mv.js +6 -0
  177. package/module/personalized_newsong.js +15 -0
  178. package/module/personalized_privatecontent.js +10 -0
  179. package/module/personalized_privatecontent_list.js +15 -0
  180. package/module/pl_count.js +6 -0
  181. package/module/playlist_category_list.js +11 -0
  182. package/module/playlist_catlist.js +6 -0
  183. package/module/playlist_cover_update.js +32 -0
  184. package/module/playlist_create.js +11 -0
  185. package/module/playlist_delete.js +9 -0
  186. package/module/playlist_desc_update.js +10 -0
  187. package/module/playlist_detail.js +11 -0
  188. package/module/playlist_detail_dynamic.js +11 -0
  189. package/module/playlist_detail_rcmd_get.js +11 -0
  190. package/module/playlist_highquality_tags.js +10 -0
  191. package/module/playlist_hot.js +6 -0
  192. package/module/playlist_import_name_task_create.js +62 -0
  193. package/module/playlist_import_task_status.js +11 -0
  194. package/module/playlist_mylike.js +12 -0
  195. package/module/playlist_name_update.js +10 -0
  196. package/module/playlist_order_update.js +13 -0
  197. package/module/playlist_privacy.js +10 -0
  198. package/module/playlist_subscribe.js +14 -0
  199. package/module/playlist_subscribers.js +11 -0
  200. package/module/playlist_tags_update.js +10 -0
  201. package/module/playlist_track_add.js +16 -0
  202. package/module/playlist_track_all.js +31 -0
  203. package/module/playlist_track_delete.js +20 -0
  204. package/module/playlist_tracks.js +45 -0
  205. package/module/playlist_update.js +13 -0
  206. package/module/playlist_update_playcount.js +9 -0
  207. package/module/playlist_video_recent.js +9 -0
  208. package/module/playmode_intelligence_list.js +13 -0
  209. package/module/playmode_song_vector.js +8 -0
  210. package/module/program_recommend.js +15 -0
  211. package/module/rebind.js +16 -0
  212. package/module/recent_listen_list.js +7 -0
  213. package/module/recommend_resource.js +10 -0
  214. package/module/recommend_songs.js +11 -0
  215. package/module/recommend_songs_dislike.js +14 -0
  216. package/module/record_recent_album.js +11 -0
  217. package/module/record_recent_dj.js +11 -0
  218. package/module/record_recent_playlist.js +11 -0
  219. package/module/record_recent_song.js +11 -0
  220. package/module/record_recent_video.js +11 -0
  221. package/module/record_recent_voice.js +11 -0
  222. package/module/register_anonimous.js +52 -0
  223. package/module/register_cellphone.js +15 -0
  224. package/module/related_allvideo.js +14 -0
  225. package/module/related_playlist.js +32 -0
  226. package/module/resource_like.js +14 -0
  227. package/module/scrobble.js +26 -0
  228. package/module/search.js +21 -0
  229. package/module/search_default.js +6 -0
  230. package/module/search_hot.js +9 -0
  231. package/module/search_hot_detail.js +6 -0
  232. package/module/search_match.js +18 -0
  233. package/module/search_multimatch.js +14 -0
  234. package/module/search_suggest.js +14 -0
  235. package/module/send_album.js +12 -0
  236. package/module/send_playlist.js +12 -0
  237. package/module/send_song.js +12 -0
  238. package/module/send_text.js +11 -0
  239. package/module/setting.js +7 -0
  240. package/module/share_resource.js +11 -0
  241. package/module/sheet_list.js +9 -0
  242. package/module/sheet_preview.js +8 -0
  243. package/module/sign_happy_info.js +5 -0
  244. package/module/signin_progress.js +13 -0
  245. package/module/simi_artist.js +12 -0
  246. package/module/simi_mv.js +9 -0
  247. package/module/simi_playlist.js +15 -0
  248. package/module/simi_song.js +15 -0
  249. package/module/simi_user.js +11 -0
  250. package/module/song_chorus.js +11 -0
  251. package/module/song_detail.js +11 -0
  252. package/module/song_downlist.js +11 -0
  253. package/module/song_download_url.js +10 -0
  254. package/module/song_download_url_v1.js +13 -0
  255. package/module/song_dynamic_cover.js +9 -0
  256. package/module/song_like_check.js +9 -0
  257. package/module/song_lyrics_mark.js +9 -0
  258. package/module/song_lyrics_mark_add.js +12 -0
  259. package/module/song_lyrics_mark_del.js +9 -0
  260. package/module/song_lyrics_mark_user_page.js +14 -0
  261. package/module/song_monthdownlist.js +11 -0
  262. package/module/song_music_detail.js +9 -0
  263. package/module/song_order_update.js +12 -0
  264. package/module/song_purchased.js +14 -0
  265. package/module/song_red_count.js +9 -0
  266. package/module/song_singledownlist.js +11 -0
  267. package/module/song_url.js +26 -0
  268. package/module/song_url_match.js +35 -0
  269. package/module/song_url_ncmget.js +75 -0
  270. package/module/song_url_unblock.js +35 -0
  271. package/module/song_url_v1.js +54 -0
  272. package/module/song_wiki_summary.js +8 -0
  273. package/module/starpick_comments_summary.js +12 -0
  274. package/module/style_album.js +16 -0
  275. package/module/style_artist.js +16 -0
  276. package/module/style_detail.js +9 -0
  277. package/module/style_list.js +7 -0
  278. package/module/style_playlist.js +16 -0
  279. package/module/style_preference.js +11 -0
  280. package/module/style_song.js +12 -0
  281. package/module/summary_annual.js +12 -0
  282. package/module/threshold_detail_get.js +10 -0
  283. package/module/top_album.js +22 -0
  284. package/module/top_artists.js +11 -0
  285. package/module/top_list.js +20 -0
  286. package/module/top_mv.js +12 -0
  287. package/module/top_playlist.js +22 -0
  288. package/module/top_playlist_highquality.js +16 -0
  289. package/module/top_song.js +16 -0
  290. package/module/topic_detail.js +7 -0
  291. package/module/topic_detail_event_hot.js +7 -0
  292. package/module/topic_sublist.js +11 -0
  293. package/module/toplist.js +6 -0
  294. package/module/toplist_artist.js +12 -0
  295. package/module/toplist_detail.js +6 -0
  296. package/module/toplist_detail_v2.js +6 -0
  297. package/module/ugc_album_get.js +8 -0
  298. package/module/ugc_artist_get.js +8 -0
  299. package/module/ugc_artist_search.js +10 -0
  300. package/module/ugc_detail.js +17 -0
  301. package/module/ugc_mv_get.js +8 -0
  302. package/module/ugc_song_get.js +8 -0
  303. package/module/ugc_user_devote.js +6 -0
  304. package/module/user_account.js +5 -0
  305. package/module/user_audio.js +9 -0
  306. package/module/user_binding.js +9 -0
  307. package/module/user_bindingcellphone.js +15 -0
  308. package/module/user_cloud.js +10 -0
  309. package/module/user_cloud_del.js +9 -0
  310. package/module/user_cloud_detail.js +10 -0
  311. package/module/user_comment_history.js +15 -0
  312. package/module/user_detail.js +15 -0
  313. package/module/user_detail_new.js +20 -0
  314. package/module/user_dj.js +14 -0
  315. package/module/user_event.js +12 -0
  316. package/module/user_follow_mixed.js +23 -0
  317. package/module/user_followeds.js +17 -0
  318. package/module/user_follows.js +15 -0
  319. package/module/user_level.js +7 -0
  320. package/module/user_medal.js +11 -0
  321. package/module/user_mutualfollow_get.js +9 -0
  322. package/module/user_playlist.js +12 -0
  323. package/module/user_record.js +10 -0
  324. package/module/user_replacephone.js +14 -0
  325. package/module/user_social_status.js +11 -0
  326. package/module/user_social_status_edit.js +16 -0
  327. package/module/user_social_status_rcmd.js +5 -0
  328. package/module/user_social_status_support.js +5 -0
  329. package/module/user_subcount.js +6 -0
  330. package/module/user_update.js +15 -0
  331. package/module/verify_getQr.js +39 -0
  332. package/module/verify_qrcodestatus.js +12 -0
  333. package/module/video_category_list.js +15 -0
  334. package/module/video_detail.js +13 -0
  335. package/module/video_detail_info.js +14 -0
  336. package/module/video_group.js +16 -0
  337. package/module/video_group_list.js +11 -0
  338. package/module/video_sub.js +14 -0
  339. package/module/video_timeline_all.js +17 -0
  340. package/module/video_timeline_recommend.js +13 -0
  341. package/module/video_url.js +10 -0
  342. package/module/vip_growthpoint.js +11 -0
  343. package/module/vip_growthpoint_details.js +14 -0
  344. package/module/vip_growthpoint_get.js +13 -0
  345. package/module/vip_info.js +12 -0
  346. package/module/vip_info_v2.js +12 -0
  347. package/module/vip_tasks.js +11 -0
  348. package/module/vip_timemachine.js +17 -0
  349. package/module/voice_delete.js +7 -0
  350. package/module/voice_detail.js +7 -0
  351. package/module/voice_lyric.js +7 -0
  352. package/module/voice_upload.js +186 -0
  353. package/module/voicelist_detail.js +11 -0
  354. package/module/voicelist_list.js +13 -0
  355. package/module/voicelist_list_search.js +14 -0
  356. package/module/voicelist_search.js +14 -0
  357. package/module/voicelist_trans.js +15 -0
  358. package/module/weblog.js +10 -0
  359. package/module/yunbei.js +6 -0
  360. package/module/yunbei_expense.js +8 -0
  361. package/module/yunbei_info.js +5 -0
  362. package/module/yunbei_rcmd_song.js +17 -0
  363. package/module/yunbei_rcmd_song_history.js +16 -0
  364. package/module/yunbei_receipt.js +8 -0
  365. package/module/yunbei_sign.js +5 -0
  366. package/module/yunbei_task_finish.js +12 -0
  367. package/module/yunbei_tasks.js +9 -0
  368. package/module/yunbei_tasks_todo.js +9 -0
  369. package/module/yunbei_today.js +5 -0
  370. package/package.json +104 -0
  371. package/plugins/songUpload.js +61 -0
  372. package/plugins/upload.js +37 -0
  373. package/public/api.html +127 -0
  374. package/public/audio_match_demo/afp.js +1627 -0
  375. package/public/audio_match_demo/afp.wasm.js +5 -0
  376. package/public/audio_match_demo/index.html +199 -0
  377. package/public/audio_match_demo/rec.js +49 -0
  378. package/public/avatar_update.html +73 -0
  379. package/public/cloud.html +104 -0
  380. package/public/docs/.nojekyll +0 -0
  381. package/public/docs/_coverpage.md +13 -0
  382. package/public/docs/favicon.ico +0 -0
  383. package/public/docs/home.md +4857 -0
  384. package/public/docs/icon.png +0 -0
  385. package/public/docs/index.html +47 -0
  386. package/public/docs/ncmapireborn.png +0 -0
  387. package/public/docs/sw.js +90 -0
  388. package/public/eapi_decrypt.html +77 -0
  389. package/public/home.html +41 -0
  390. package/public/index.html +313 -0
  391. package/public/listen_together_host.html +324 -0
  392. package/public/login.html +47 -0
  393. package/public/playlist_cover_update.html +77 -0
  394. package/public/playlist_import.html +262 -0
  395. package/public/qrlogin-nocookie.html +72 -0
  396. package/public/qrlogin.html +71 -0
  397. package/public/static/docs.png +0 -0
  398. package/public/static/eapi_params.png +0 -0
  399. package/public/static/eapi_response.png +0 -0
  400. package/public/static/screenshot1.png +0 -0
  401. package/public/unblock_test.html +123 -0
  402. package/public/voice_upload.html +128 -0
  403. package/server.js +366 -0
  404. package/util/apicache.js +833 -0
  405. package/util/client-sign.js +169 -0
  406. package/util/config.json +20 -0
  407. package/util/crypto.js +135 -0
  408. package/util/index.js +159 -0
  409. package/util/logger.js +29 -0
  410. package/util/memory-cache.js +71 -0
  411. package/util/option.js +14 -0
  412. package/util/request.js +364 -0
@@ -0,0 +1,833 @@
1
+ var url = require('url')
2
+ var MemoryCache = require('./memory-cache')
3
+ const logger = require('./logger.js')
4
+
5
+ var t = {
6
+ ms: 1,
7
+ second: 1000,
8
+ minute: 60000,
9
+ hour: 3600000,
10
+ day: 3600000 * 24,
11
+ week: 3600000 * 24 * 7,
12
+ month: 3600000 * 24 * 30,
13
+ }
14
+
15
+ var instances = []
16
+
17
+ var matches = function (a) {
18
+ return function (b) {
19
+ return a === b
20
+ }
21
+ }
22
+
23
+ var doesntMatch = function (a) {
24
+ return function (b) {
25
+ return !matches(a)(b)
26
+ }
27
+ }
28
+
29
+ var logDuration = function (d, prefix) {
30
+ var str = d > 1000 ? (d / 1000).toFixed(2) + 'sec' : d + 'ms'
31
+ return '\x1b[33m- ' + (prefix ? prefix + ' ' : '') + str + '\x1b[0m'
32
+ }
33
+
34
+ function getSafeHeaders(res) {
35
+ return res.getHeaders ? res.getHeaders() : res._headers
36
+ }
37
+
38
+ function ApiCache() {
39
+ var memCache = new MemoryCache()
40
+
41
+ var globalOptions = {
42
+ debug: false,
43
+ defaultDuration: 3600000,
44
+ enabled: true,
45
+ appendKey: [],
46
+ jsonp: false,
47
+ redisClient: false,
48
+ headerBlacklist: [],
49
+ statusCodes: {
50
+ include: [],
51
+ exclude: [],
52
+ },
53
+ events: {
54
+ expire: undefined,
55
+ },
56
+ headers: {
57
+ // 'cache-control': 'no-cache' // example of header overwrite
58
+ },
59
+ trackPerformance: false,
60
+ }
61
+
62
+ var middlewareOptions = []
63
+ var instance = this
64
+ var index = null
65
+ var timers = {}
66
+ var performanceArray = [] // for tracking cache hit rate
67
+
68
+ instances.push(this)
69
+ this.id = instances.length
70
+
71
+ function debug(a, b, c, d) {
72
+ var arr = ['\x1b[36m[apicache]\x1b[0m', a, b, c, d].filter(function (arg) {
73
+ return arg !== undefined
74
+ })
75
+ var debugEnv =
76
+ process.env.DEBUG &&
77
+ process.env.DEBUG.split(',').indexOf('apicache') !== -1
78
+
79
+ return (globalOptions.debug || debugEnv) && console.log.apply(null, arr)
80
+ }
81
+
82
+ function shouldCacheResponse(request, response, toggle) {
83
+ var opt = globalOptions
84
+ var codes = opt.statusCodes
85
+
86
+ if (!response) return false
87
+
88
+ if (toggle && !toggle(request, response)) {
89
+ return false
90
+ }
91
+
92
+ if (
93
+ codes.exclude &&
94
+ codes.exclude.length &&
95
+ codes.exclude.indexOf(response.statusCode) !== -1
96
+ )
97
+ return false
98
+ if (
99
+ codes.include &&
100
+ codes.include.length &&
101
+ codes.include.indexOf(response.statusCode) === -1
102
+ )
103
+ return false
104
+
105
+ return true
106
+ }
107
+
108
+ function addIndexEntries(key, req) {
109
+ var groupName = req.apicacheGroup
110
+
111
+ if (groupName) {
112
+ debug('group detected "' + groupName + '"')
113
+ var group = (index.groups[groupName] = index.groups[groupName] || [])
114
+ group.unshift(key)
115
+ }
116
+
117
+ index.all.unshift(key)
118
+ }
119
+
120
+ function filterBlacklistedHeaders(headers) {
121
+ return Object.keys(headers)
122
+ .filter(function (key) {
123
+ return globalOptions.headerBlacklist.indexOf(key) === -1
124
+ })
125
+ .reduce(function (acc, header) {
126
+ acc[header] = headers[header]
127
+ return acc
128
+ }, {})
129
+ }
130
+
131
+ function createCacheObject(status, headers, data, encoding) {
132
+ return {
133
+ status: status,
134
+ headers: filterBlacklistedHeaders(headers),
135
+ data: data,
136
+ encoding: encoding,
137
+ timestamp: new Date().getTime() / 1000, // seconds since epoch. This is used to properly decrement max-age headers in cached responses.
138
+ }
139
+ }
140
+
141
+ function cacheResponse(key, value, duration) {
142
+ var redis = globalOptions.redisClient
143
+ var expireCallback = globalOptions.events.expire
144
+
145
+ if (redis && redis.connected) {
146
+ try {
147
+ redis.hset(key, 'response', JSON.stringify(value))
148
+ redis.hset(key, 'duration', duration)
149
+ redis.expire(key, duration / 1000, expireCallback || function () {})
150
+ } catch (err) {
151
+ debug('[apicache] error in redis.hset()')
152
+ }
153
+ } else {
154
+ memCache.add(key, value, duration, expireCallback)
155
+ }
156
+
157
+ // add automatic cache clearing from duration, includes max limit on setTimeout
158
+ timers[key] = setTimeout(function () {
159
+ instance.clear(key, true)
160
+ }, Math.min(duration, 2147483647))
161
+ }
162
+
163
+ function accumulateContent(res, content) {
164
+ if (content) {
165
+ if (typeof content == 'string') {
166
+ res._apicache.content = (res._apicache.content || '') + content
167
+ } else if (Buffer.isBuffer(content)) {
168
+ var oldContent = res._apicache.content
169
+
170
+ if (typeof oldContent === 'string') {
171
+ oldContent = !Buffer.from
172
+ ? new Buffer(oldContent)
173
+ : Buffer.from(oldContent)
174
+ }
175
+
176
+ if (!oldContent) {
177
+ oldContent = !Buffer.alloc ? new Buffer(0) : Buffer.alloc(0)
178
+ }
179
+
180
+ res._apicache.content = Buffer.concat(
181
+ [oldContent, content],
182
+ oldContent.length + content.length,
183
+ )
184
+ } else {
185
+ res._apicache.content = content
186
+ }
187
+ }
188
+ }
189
+
190
+ function makeResponseCacheable(
191
+ req,
192
+ res,
193
+ next,
194
+ key,
195
+ duration,
196
+ strDuration,
197
+ toggle,
198
+ ) {
199
+ // monkeypatch res.end to create cache object
200
+ res._apicache = {
201
+ write: res.write,
202
+ writeHead: res.writeHead,
203
+ end: res.end,
204
+ cacheable: true,
205
+ content: undefined,
206
+ }
207
+
208
+ // append header overwrites if applicable
209
+ Object.keys(globalOptions.headers).forEach(function (name) {
210
+ res.setHeader(name, globalOptions.headers[name])
211
+ })
212
+
213
+ res.writeHead = function () {
214
+ // add cache control headers
215
+ if (!globalOptions.headers['cache-control']) {
216
+ if (shouldCacheResponse(req, res, toggle)) {
217
+ res.setHeader(
218
+ 'cache-control',
219
+ 'max-age=' + (duration / 1000).toFixed(0),
220
+ )
221
+ } else {
222
+ res.setHeader('cache-control', 'no-cache, no-store, must-revalidate')
223
+ }
224
+ }
225
+
226
+ res._apicache.headers = Object.assign({}, getSafeHeaders(res))
227
+ return res._apicache.writeHead.apply(this, arguments)
228
+ }
229
+
230
+ // patch res.write
231
+ res.write = function (content) {
232
+ accumulateContent(res, content)
233
+ return res._apicache.write.apply(this, arguments)
234
+ }
235
+
236
+ // patch res.end
237
+ res.end = function (content, encoding) {
238
+ if (shouldCacheResponse(req, res, toggle)) {
239
+ accumulateContent(res, content)
240
+
241
+ if (res._apicache.cacheable && res._apicache.content) {
242
+ addIndexEntries(key, req)
243
+ var headers = res._apicache.headers || getSafeHeaders(res)
244
+ var cacheObject = createCacheObject(
245
+ res.statusCode,
246
+ headers,
247
+ res._apicache.content,
248
+ encoding,
249
+ )
250
+ cacheResponse(key, cacheObject, duration)
251
+
252
+ // display log entry
253
+ var elapsed = new Date() - req.apicacheTimer
254
+ debug(
255
+ 'adding cache entry for "' + key + '" @ ' + strDuration,
256
+ logDuration(elapsed),
257
+ )
258
+ debug('_apicache.headers: ', res._apicache.headers)
259
+ debug('res.getHeaders(): ', getSafeHeaders(res))
260
+ debug('cacheObject: ', cacheObject)
261
+ }
262
+ }
263
+
264
+ return res._apicache.end.apply(this, arguments)
265
+ }
266
+
267
+ next()
268
+ }
269
+
270
+ function sendCachedResponse(
271
+ request,
272
+ response,
273
+ cacheObject,
274
+ toggle,
275
+ next,
276
+ duration,
277
+ ) {
278
+ if (toggle && !toggle(request, response)) {
279
+ return next()
280
+ }
281
+
282
+ var headers = getSafeHeaders(response)
283
+
284
+ Object.assign(
285
+ headers,
286
+ filterBlacklistedHeaders(cacheObject.headers || {}),
287
+ {
288
+ // set properly-decremented max-age header. This ensures that max-age is in sync with the cache expiration.
289
+ 'cache-control':
290
+ 'max-age=' +
291
+ Math.max(
292
+ 0,
293
+ (
294
+ duration / 1000 -
295
+ (new Date().getTime() / 1000 - cacheObject.timestamp)
296
+ ).toFixed(0),
297
+ ),
298
+ },
299
+ )
300
+
301
+ // only embed apicache headers when not in production environment
302
+
303
+ // unstringify buffers
304
+ var data = cacheObject.data
305
+ if (data && data.type === 'Buffer') {
306
+ data =
307
+ typeof data.data === 'number'
308
+ ? new Buffer.alloc(data.data)
309
+ : new Buffer.from(data.data)
310
+ }
311
+
312
+ // test Etag against If-None-Match for 304
313
+ var cachedEtag = cacheObject.headers.etag
314
+ var requestEtag = request.headers['if-none-match']
315
+
316
+ if (requestEtag && cachedEtag === requestEtag) {
317
+ response.writeHead(304, headers)
318
+ return response.end()
319
+ }
320
+
321
+ response.writeHead(cacheObject.status || 200, headers)
322
+
323
+ return response.end(data, cacheObject.encoding)
324
+ }
325
+
326
+ function syncOptions() {
327
+ for (var i in middlewareOptions) {
328
+ Object.assign(
329
+ middlewareOptions[i].options,
330
+ globalOptions,
331
+ middlewareOptions[i].localOptions,
332
+ )
333
+ }
334
+ }
335
+
336
+ this.clear = function (target, isAutomatic) {
337
+ var group = index.groups[target]
338
+ var redis = globalOptions.redisClient
339
+
340
+ if (group) {
341
+ debug('clearing group "' + target + '"')
342
+
343
+ group.forEach(function (key) {
344
+ debug('clearing cached entry for "' + key + '"')
345
+ clearTimeout(timers[key])
346
+ delete timers[key]
347
+ if (!globalOptions.redisClient) {
348
+ memCache.delete(key)
349
+ } else {
350
+ try {
351
+ redis.del(key)
352
+ } catch (err) {
353
+ logger.info('[apicache] error in redis.del("' + key + '")')
354
+ }
355
+ }
356
+ index.all = index.all.filter(doesntMatch(key))
357
+ })
358
+
359
+ delete index.groups[target]
360
+ } else if (target) {
361
+ debug(
362
+ 'clearing ' +
363
+ (isAutomatic ? 'expired' : 'cached') +
364
+ ' entry for "' +
365
+ target +
366
+ '"',
367
+ )
368
+ clearTimeout(timers[target])
369
+ delete timers[target]
370
+ // clear actual cached entry
371
+ if (!redis) {
372
+ memCache.delete(target)
373
+ } else {
374
+ try {
375
+ redis.del(target)
376
+ } catch (err) {
377
+ logger.info('[apicache] error in redis.del("' + target + '")')
378
+ }
379
+ }
380
+
381
+ // remove from global index
382
+ index.all = index.all.filter(doesntMatch(target))
383
+
384
+ // remove target from each group that it may exist in
385
+ Object.keys(index.groups).forEach(function (groupName) {
386
+ index.groups[groupName] = index.groups[groupName].filter(
387
+ doesntMatch(target),
388
+ )
389
+
390
+ // delete group if now empty
391
+ if (!index.groups[groupName].length) {
392
+ delete index.groups[groupName]
393
+ }
394
+ })
395
+ } else {
396
+ debug('clearing entire index')
397
+
398
+ if (!redis) {
399
+ memCache.clear()
400
+ } else {
401
+ // clear redis keys one by one from internal index to prevent clearing non-apicache entries
402
+ index.all.forEach(function (key) {
403
+ clearTimeout(timers[key])
404
+ delete timers[key]
405
+ try {
406
+ redis.del(key)
407
+ } catch (err) {
408
+ logger.info('[apicache] error in redis.del("' + key + '")')
409
+ }
410
+ })
411
+ }
412
+ this.resetIndex()
413
+ }
414
+
415
+ return this.getIndex()
416
+ }
417
+
418
+ function parseDuration(duration, defaultDuration) {
419
+ if (typeof duration === 'number') return duration
420
+
421
+ if (typeof duration === 'string') {
422
+ var split = duration.match(/^([\d\.,]+)\s?(\w+)$/)
423
+
424
+ if (split.length === 3) {
425
+ var len = parseFloat(split[1])
426
+ var unit = split[2].replace(/s$/i, '').toLowerCase()
427
+ if (unit === 'm') {
428
+ unit = 'ms'
429
+ }
430
+
431
+ return (len || 1) * (t[unit] || 0)
432
+ }
433
+ }
434
+
435
+ return defaultDuration
436
+ }
437
+
438
+ this.getDuration = function (duration) {
439
+ return parseDuration(duration, globalOptions.defaultDuration)
440
+ }
441
+
442
+ /**
443
+ * Return cache performance statistics (hit rate). Suitable for putting into a route:
444
+ * <code>
445
+ * app.get('/api/cache/performance', (req, res) => {
446
+ * res.json(apicache.getPerformance())
447
+ * })
448
+ * </code>
449
+ */
450
+ this.getPerformance = function () {
451
+ return performanceArray.map(function (p) {
452
+ return p.report()
453
+ })
454
+ }
455
+
456
+ this.getIndex = function (group) {
457
+ if (group) {
458
+ return index.groups[group]
459
+ } else {
460
+ return index
461
+ }
462
+ }
463
+
464
+ this.middleware = function cache(
465
+ strDuration,
466
+ middlewareToggle,
467
+ localOptions,
468
+ ) {
469
+ var duration = instance.getDuration(strDuration)
470
+ var opt = {}
471
+
472
+ middlewareOptions.push({
473
+ options: opt,
474
+ })
475
+
476
+ var options = function (localOptions) {
477
+ if (localOptions) {
478
+ middlewareOptions.find(function (middleware) {
479
+ return middleware.options === opt
480
+ }).localOptions = localOptions
481
+ }
482
+
483
+ syncOptions()
484
+
485
+ return opt
486
+ }
487
+
488
+ options(localOptions)
489
+
490
+ /**
491
+ * A Function for non tracking performance
492
+ */
493
+ function NOOPCachePerformance() {
494
+ this.report = this.hit = this.miss = function () {} // noop;
495
+ }
496
+
497
+ /**
498
+ * A function for tracking and reporting hit rate. These statistics are returned by the getPerformance() call above.
499
+ */
500
+ function CachePerformance() {
501
+ /**
502
+ * Tracks the hit rate for the last 100 requests.
503
+ * If there have been fewer than 100 requests, the hit rate just considers the requests that have happened.
504
+ */
505
+ this.hitsLast100 = new Uint8Array(100 / 4) // each hit is 2 bits
506
+
507
+ /**
508
+ * Tracks the hit rate for the last 1000 requests.
509
+ * If there have been fewer than 1000 requests, the hit rate just considers the requests that have happened.
510
+ */
511
+ this.hitsLast1000 = new Uint8Array(1000 / 4) // each hit is 2 bits
512
+
513
+ /**
514
+ * Tracks the hit rate for the last 10000 requests.
515
+ * If there have been fewer than 10000 requests, the hit rate just considers the requests that have happened.
516
+ */
517
+ this.hitsLast10000 = new Uint8Array(10000 / 4) // each hit is 2 bits
518
+
519
+ /**
520
+ * Tracks the hit rate for the last 100000 requests.
521
+ * If there have been fewer than 100000 requests, the hit rate just considers the requests that have happened.
522
+ */
523
+ this.hitsLast100000 = new Uint8Array(100000 / 4) // each hit is 2 bits
524
+
525
+ /**
526
+ * The number of calls that have passed through the middleware since the server started.
527
+ */
528
+ this.callCount = 0
529
+
530
+ /**
531
+ * The total number of hits since the server started
532
+ */
533
+ this.hitCount = 0
534
+
535
+ /**
536
+ * The key from the last cache hit. This is useful in identifying which route these statistics apply to.
537
+ */
538
+ this.lastCacheHit = null
539
+
540
+ /**
541
+ * The key from the last cache miss. This is useful in identifying which route these statistics apply to.
542
+ */
543
+ this.lastCacheMiss = null
544
+
545
+ /**
546
+ * Return performance statistics
547
+ */
548
+ this.report = function () {
549
+ return {
550
+ lastCacheHit: this.lastCacheHit,
551
+ lastCacheMiss: this.lastCacheMiss,
552
+ callCount: this.callCount,
553
+ hitCount: this.hitCount,
554
+ missCount: this.callCount - this.hitCount,
555
+ hitRate: this.callCount == 0 ? null : this.hitCount / this.callCount,
556
+ hitRateLast100: this.hitRate(this.hitsLast100),
557
+ hitRateLast1000: this.hitRate(this.hitsLast1000),
558
+ hitRateLast10000: this.hitRate(this.hitsLast10000),
559
+ hitRateLast100000: this.hitRate(this.hitsLast100000),
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Computes a cache hit rate from an array of hits and misses.
565
+ * @param {Uint8Array} array An array representing hits and misses.
566
+ * @returns a number between 0 and 1, or null if the array has no hits or misses
567
+ */
568
+ this.hitRate = function (array) {
569
+ var hits = 0
570
+ var misses = 0
571
+ for (var i = 0; i < array.length; i++) {
572
+ var n8 = array[i]
573
+ for (j = 0; j < 4; j++) {
574
+ switch (n8 & 3) {
575
+ case 1:
576
+ hits++
577
+ break
578
+ case 2:
579
+ misses++
580
+ break
581
+ }
582
+ n8 >>= 2
583
+ }
584
+ }
585
+ var total = hits + misses
586
+ if (total == 0) return null
587
+ return hits / total
588
+ }
589
+
590
+ /**
591
+ * Record a hit or miss in the given array. It will be recorded at a position determined
592
+ * by the current value of the callCount variable.
593
+ * @param {Uint8Array} array An array representing hits and misses.
594
+ * @param {boolean} hit true for a hit, false for a miss
595
+ * Each element in the array is 8 bits, and encodes 4 hit/miss records.
596
+ * Each hit or miss is encoded as to bits as follows:
597
+ * 00 means no hit or miss has been recorded in these bits
598
+ * 01 encodes a hit
599
+ * 10 encodes a miss
600
+ */
601
+ this.recordHitInArray = function (array, hit) {
602
+ var arrayIndex = ~~(this.callCount / 4) % array.length
603
+ var bitOffset = (this.callCount % 4) * 2 // 2 bits per record, 4 records per uint8 array element
604
+ var clearMask = ~(3 << bitOffset)
605
+ var record = (hit ? 1 : 2) << bitOffset
606
+ array[arrayIndex] = (array[arrayIndex] & clearMask) | record
607
+ }
608
+
609
+ /**
610
+ * Records the hit or miss in the tracking arrays and increments the call count.
611
+ * @param {boolean} hit true records a hit, false records a miss
612
+ */
613
+ this.recordHit = function (hit) {
614
+ this.recordHitInArray(this.hitsLast100, hit)
615
+ this.recordHitInArray(this.hitsLast1000, hit)
616
+ this.recordHitInArray(this.hitsLast10000, hit)
617
+ this.recordHitInArray(this.hitsLast100000, hit)
618
+ if (hit) this.hitCount++
619
+ this.callCount++
620
+ }
621
+
622
+ /**
623
+ * Records a hit event, setting lastCacheMiss to the given key
624
+ * @param {string} key The key that had the cache hit
625
+ */
626
+ this.hit = function (key) {
627
+ this.recordHit(true)
628
+ this.lastCacheHit = key
629
+ }
630
+
631
+ /**
632
+ * Records a miss event, setting lastCacheMiss to the given key
633
+ * @param {string} key The key that had the cache miss
634
+ */
635
+ this.miss = function (key) {
636
+ this.recordHit(false)
637
+ this.lastCacheMiss = key
638
+ }
639
+ }
640
+
641
+ var perf = globalOptions.trackPerformance
642
+ ? new CachePerformance()
643
+ : new NOOPCachePerformance()
644
+
645
+ performanceArray.push(perf)
646
+
647
+ var cache = function (req, res, next) {
648
+ function bypass() {
649
+ debug('bypass detected, skipping cache.')
650
+ return next()
651
+ }
652
+
653
+ // initial bypass chances
654
+ if (!opt.enabled) return bypass()
655
+ if (
656
+ req.headers['x-apicache-bypass'] ||
657
+ req.headers['x-apicache-force-fetch']
658
+ )
659
+ return bypass()
660
+
661
+ // REMOVED IN 0.11.1 TO CORRECT MIDDLEWARE TOGGLE EXECUTE ORDER
662
+ // if (typeof middlewareToggle === 'function') {
663
+ // if (!middlewareToggle(req, res)) return bypass()
664
+ // } else if (middlewareToggle !== undefined && !middlewareToggle) {
665
+ // return bypass()
666
+ // }
667
+
668
+ // embed timer
669
+ req.apicacheTimer = new Date()
670
+
671
+ // In Express 4.x the url is ambigious based on where a router is mounted. originalUrl will give the full Url
672
+ var key =
673
+ req.hostname +
674
+ (req.originalUrl || req.url) +
675
+ JSON.stringify(req.cookies)
676
+ // Remove querystring from key if jsonp option is enabled
677
+ if (opt.jsonp) {
678
+ key = url.parse(key).pathname
679
+ }
680
+
681
+ // add appendKey (either custom function or response path)
682
+ if (typeof opt.appendKey === 'function') {
683
+ key += '$$appendKey=' + opt.appendKey(req, res)
684
+ } else if (opt.appendKey.length > 0) {
685
+ var appendKey = req
686
+
687
+ for (var i = 0; i < opt.appendKey.length; i++) {
688
+ appendKey = appendKey[opt.appendKey[i]]
689
+ }
690
+ key += '$$appendKey=' + appendKey
691
+ }
692
+
693
+ // attempt cache hit
694
+ var redis = opt.redisClient
695
+ var cached = !redis ? memCache.getValue(key) : null
696
+
697
+ // send if cache hit from memory-cache
698
+ if (cached) {
699
+ var elapsed = new Date() - req.apicacheTimer
700
+ debug(
701
+ 'sending cached (memory-cache) version of',
702
+ key,
703
+ logDuration(elapsed),
704
+ )
705
+
706
+ perf.hit(key)
707
+ return sendCachedResponse(
708
+ req,
709
+ res,
710
+ cached,
711
+ middlewareToggle,
712
+ next,
713
+ duration,
714
+ )
715
+ }
716
+
717
+ // send if cache hit from redis
718
+ if (redis && redis.connected) {
719
+ try {
720
+ redis.hgetall(key, function (err, obj) {
721
+ if (!err && obj && obj.response) {
722
+ var elapsed = new Date() - req.apicacheTimer
723
+ debug(
724
+ 'sending cached (redis) version of',
725
+ key,
726
+ logDuration(elapsed),
727
+ )
728
+
729
+ perf.hit(key)
730
+ return sendCachedResponse(
731
+ req,
732
+ res,
733
+ JSON.parse(obj.response),
734
+ middlewareToggle,
735
+ next,
736
+ duration,
737
+ )
738
+ } else {
739
+ perf.miss(key)
740
+ return makeResponseCacheable(
741
+ req,
742
+ res,
743
+ next,
744
+ key,
745
+ duration,
746
+ strDuration,
747
+ middlewareToggle,
748
+ )
749
+ }
750
+ })
751
+ } catch (err) {
752
+ // bypass redis on error
753
+ perf.miss(key)
754
+ return makeResponseCacheable(
755
+ req,
756
+ res,
757
+ next,
758
+ key,
759
+ duration,
760
+ strDuration,
761
+ middlewareToggle,
762
+ )
763
+ }
764
+ } else {
765
+ perf.miss(key)
766
+ return makeResponseCacheable(
767
+ req,
768
+ res,
769
+ next,
770
+ key,
771
+ duration,
772
+ strDuration,
773
+ middlewareToggle,
774
+ )
775
+ }
776
+ }
777
+
778
+ cache.options = options
779
+
780
+ return cache
781
+ }
782
+
783
+ this.options = function (options) {
784
+ if (options) {
785
+ Object.assign(globalOptions, options)
786
+ syncOptions()
787
+
788
+ if ('defaultDuration' in options) {
789
+ // Convert the default duration to a number in milliseconds (if needed)
790
+ globalOptions.defaultDuration = parseDuration(
791
+ globalOptions.defaultDuration,
792
+ 3600000,
793
+ )
794
+ }
795
+
796
+ if (globalOptions.trackPerformance) {
797
+ debug(
798
+ 'WARNING: using trackPerformance flag can cause high memory usage!',
799
+ )
800
+ }
801
+
802
+ return this
803
+ } else {
804
+ return globalOptions
805
+ }
806
+ }
807
+
808
+ this.resetIndex = function () {
809
+ index = {
810
+ all: [],
811
+ groups: {},
812
+ }
813
+ }
814
+
815
+ this.newInstance = function (config) {
816
+ var instance = new ApiCache()
817
+
818
+ if (config) {
819
+ instance.options(config)
820
+ }
821
+
822
+ return instance
823
+ }
824
+
825
+ this.clone = function () {
826
+ return this.newInstance(this.options())
827
+ }
828
+
829
+ // initialize index
830
+ this.resetIndex()
831
+ }
832
+
833
+ module.exports = new ApiCache()