streaming.js 5.5 KB

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