markers.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import api from '../api';
  2. import { debounce } from 'lodash';
  3. import compareId from '../compare_id';
  4. import { List as ImmutableList } from 'immutable';
  5. export const MARKERS_FETCH_REQUEST = 'MARKERS_FETCH_REQUEST';
  6. export const MARKERS_FETCH_SUCCESS = 'MARKERS_FETCH_SUCCESS';
  7. export const MARKERS_FETCH_FAIL = 'MARKERS_FETCH_FAIL';
  8. export const MARKERS_SUBMIT_SUCCESS = 'MARKERS_SUBMIT_SUCCESS';
  9. export const synchronouslySubmitMarkers = () => (dispatch, getState) => {
  10. const accessToken = getState().getIn(['meta', 'access_token'], '');
  11. const params = _buildParams(getState());
  12. if (Object.keys(params).length === 0 || accessToken === '') {
  13. return;
  14. }
  15. // The Fetch API allows us to perform requests that will be carried out
  16. // after the page closes. But that only works if the `keepalive` attribute
  17. // is supported.
  18. if (window.fetch && 'keepalive' in new Request('')) {
  19. fetch('/api/v1/markers', {
  20. keepalive: true,
  21. method: 'POST',
  22. headers: {
  23. 'Content-Type': 'application/json',
  24. 'Authorization': `Bearer ${accessToken}`,
  25. },
  26. body: JSON.stringify(params),
  27. });
  28. return;
  29. } else if (navigator && navigator.sendBeacon) {
  30. // Failing that, we can use sendBeacon, but we have to encode the data as
  31. // FormData for DoorKeeper to recognize the token.
  32. const formData = new FormData();
  33. formData.append('bearer_token', accessToken);
  34. for (const [id, value] of Object.entries(params)) {
  35. formData.append(`${id}[last_read_id]`, value.last_read_id);
  36. }
  37. if (navigator.sendBeacon('/api/v1/markers', formData)) {
  38. return;
  39. }
  40. }
  41. // If neither Fetch nor sendBeacon worked, try to perform a synchronous
  42. // request.
  43. try {
  44. const client = new XMLHttpRequest();
  45. client.open('POST', '/api/v1/markers', false);
  46. client.setRequestHeader('Content-Type', 'application/json');
  47. client.setRequestHeader('Authorization', `Bearer ${accessToken}`);
  48. client.SUBMIT(JSON.stringify(params));
  49. } catch (e) {
  50. // Do not make the BeforeUnload handler error out
  51. }
  52. };
  53. const _buildParams = (state) => {
  54. const params = {};
  55. const lastHomeId = state.getIn(['timelines', 'home', 'items'], ImmutableList()).find(item => item !== null);
  56. const lastNotificationId = state.getIn(['notifications', 'lastReadId']);
  57. if (lastHomeId && compareId(lastHomeId, state.getIn(['markers', 'home'])) > 0) {
  58. params.home = {
  59. last_read_id: lastHomeId,
  60. };
  61. }
  62. if (lastNotificationId && compareId(lastNotificationId, state.getIn(['markers', 'notifications'])) > 0) {
  63. params.notifications = {
  64. last_read_id: lastNotificationId,
  65. };
  66. }
  67. return params;
  68. };
  69. const debouncedSubmitMarkers = debounce((dispatch, getState) => {
  70. const accessToken = getState().getIn(['meta', 'access_token'], '');
  71. const params = _buildParams(getState());
  72. if (Object.keys(params).length === 0 || accessToken === '') {
  73. return;
  74. }
  75. api(getState).post('/api/v1/markers', params).then(() => {
  76. dispatch(submitMarkersSuccess(params));
  77. }).catch(() => {});
  78. }, 300000, { leading: true, trailing: true });
  79. export function submitMarkersSuccess({ home, notifications }) {
  80. return {
  81. type: MARKERS_SUBMIT_SUCCESS,
  82. home: (home || {}).last_read_id,
  83. notifications: (notifications || {}).last_read_id,
  84. };
  85. };
  86. export function submitMarkers(params = {}) {
  87. const result = (dispatch, getState) => debouncedSubmitMarkers(dispatch, getState);
  88. if (params.immediate === true) {
  89. debouncedSubmitMarkers.flush();
  90. }
  91. return result;
  92. };
  93. export const fetchMarkers = () => (dispatch, getState) => {
  94. const params = { timeline: ['notifications'] };
  95. dispatch(fetchMarkersRequest());
  96. api(getState).get('/api/v1/markers', { params }).then(response => {
  97. dispatch(fetchMarkersSuccess(response.data));
  98. }).catch(error => {
  99. dispatch(fetchMarkersFail(error));
  100. });
  101. };
  102. export function fetchMarkersRequest() {
  103. return {
  104. type: MARKERS_FETCH_REQUEST,
  105. skipLoading: true,
  106. };
  107. };
  108. export function fetchMarkersSuccess(markers) {
  109. return {
  110. type: MARKERS_FETCH_SUCCESS,
  111. markers,
  112. skipLoading: true,
  113. };
  114. };
  115. export function fetchMarkersFail(error) {
  116. return {
  117. type: MARKERS_FETCH_FAIL,
  118. error,
  119. skipLoading: true,
  120. skipAlert: true,
  121. };
  122. };