streaming.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. // @ts-check
  2. import { connectStream } from '../stream';
  3. import {
  4. updateTimeline,
  5. deleteFromTimelines,
  6. expandHomeTimeline,
  7. connectTimeline,
  8. disconnectTimeline,
  9. fillHomeTimelineGaps,
  10. fillPublicTimelineGaps,
  11. fillCommunityTimelineGaps,
  12. fillListTimelineGaps,
  13. } from './timelines';
  14. import { updateNotifications, expandNotifications } from './notifications';
  15. import { updateConversations } from './conversations';
  16. import { updateStatus } from './statuses';
  17. import {
  18. fetchAnnouncements,
  19. updateAnnouncements,
  20. updateReaction as updateAnnouncementsReaction,
  21. deleteAnnouncement,
  22. } from './announcements';
  23. import { getLocale } from '../locales';
  24. const { messages } = getLocale();
  25. /**
  26. * @param {number} max
  27. * @return {number}
  28. */
  29. const randomUpTo = max =>
  30. Math.floor(Math.random() * Math.floor(max));
  31. /**
  32. * @param {string} timelineId
  33. * @param {string} channelName
  34. * @param {Object.<string, string>} params
  35. * @param {Object} options
  36. * @param {function(Function, Function): void} [options.fallback]
  37. * @param {function(): void} [options.fillGaps]
  38. * @param {function(object): boolean} [options.accept]
  39. * @return {function(): void}
  40. */
  41. export const connectTimelineStream = (timelineId, channelName, params = {}, options = {}) =>
  42. connectStream(channelName, params, (dispatch, getState) => {
  43. const locale = getState().getIn(['meta', 'locale']);
  44. let pollingId;
  45. /**
  46. * @param {function(Function, Function): void} fallback
  47. */
  48. const useFallback = fallback => {
  49. fallback(dispatch, () => {
  50. pollingId = setTimeout(() => useFallback(fallback), 20000 + randomUpTo(20000));
  51. });
  52. };
  53. return {
  54. onConnect() {
  55. dispatch(connectTimeline(timelineId));
  56. if (pollingId) {
  57. clearTimeout(pollingId);
  58. pollingId = null;
  59. }
  60. if (options.fillGaps) {
  61. dispatch(options.fillGaps());
  62. }
  63. },
  64. onDisconnect() {
  65. dispatch(disconnectTimeline(timelineId));
  66. if (options.fallback) {
  67. pollingId = setTimeout(() => useFallback(options.fallback), randomUpTo(40000));
  68. }
  69. },
  70. onReceive (data) {
  71. switch(data.event) {
  72. case 'update':
  73. dispatch(updateTimeline(timelineId, JSON.parse(data.payload), options.accept));
  74. break;
  75. case 'status.update':
  76. dispatch(updateStatus(JSON.parse(data.payload)));
  77. break;
  78. case 'delete':
  79. dispatch(deleteFromTimelines(data.payload));
  80. break;
  81. case 'notification':
  82. dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
  83. break;
  84. case 'conversation':
  85. dispatch(updateConversations(JSON.parse(data.payload)));
  86. break;
  87. case 'announcement':
  88. dispatch(updateAnnouncements(JSON.parse(data.payload)));
  89. break;
  90. case 'announcement.reaction':
  91. dispatch(updateAnnouncementsReaction(JSON.parse(data.payload)));
  92. break;
  93. case 'announcement.delete':
  94. dispatch(deleteAnnouncement(data.payload));
  95. break;
  96. }
  97. },
  98. };
  99. });
  100. /**
  101. * @param {Function} dispatch
  102. * @param {function(): void} done
  103. */
  104. const refreshHomeTimelineAndNotification = (dispatch, done) => {
  105. dispatch(expandHomeTimeline({}, () =>
  106. dispatch(expandNotifications({}, () =>
  107. dispatch(fetchAnnouncements(done))))));
  108. };
  109. /**
  110. * @return {function(): void}
  111. */
  112. export const connectUserStream = () =>
  113. connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification, fillGaps: fillHomeTimelineGaps });
  114. /**
  115. * @param {Object} options
  116. * @param {boolean} [options.onlyMedia]
  117. * @return {function(): void}
  118. */
  119. export const connectCommunityStream = ({ onlyMedia } = {}) =>
  120. connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => (fillCommunityTimelineGaps({ onlyMedia })) });
  121. /**
  122. * @param {Object} options
  123. * @param {boolean} [options.onlyMedia]
  124. * @param {boolean} [options.onlyRemote]
  125. * @return {function(): void}
  126. */
  127. export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
  128. connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, {}, { fillGaps: () => fillPublicTimelineGaps({ onlyMedia, onlyRemote }) });
  129. /**
  130. * @param {string} columnId
  131. * @param {string} tagName
  132. * @param {boolean} onlyLocal
  133. * @param {function(object): boolean} accept
  134. * @return {function(): void}
  135. */
  136. export const connectHashtagStream = (columnId, tagName, onlyLocal, accept) =>
  137. connectTimelineStream(`hashtag:${columnId}${onlyLocal ? ':local' : ''}`, `hashtag${onlyLocal ? ':local' : ''}`, { tag: tagName }, { accept });
  138. /**
  139. * @return {function(): void}
  140. */
  141. export const connectDirectStream = () =>
  142. connectTimelineStream('direct', 'direct');
  143. /**
  144. * @param {string} listId
  145. * @return {function(): void}
  146. */
  147. export const connectListStream = listId =>
  148. connectTimelineStream(`list:${listId}`, 'list', { list: listId }, { fillGaps: () => fillListTimelineGaps(listId) });