api.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import secrets from 'secrets';
  2. import browser from 'webextension-polyfill';
  3. import { parseJSON } from './helper';
  4. function queryBuilder(obj) {
  5. let str = '';
  6. Object.entries(obj).forEach(([key, value], index) => {
  7. if (index !== 0) str += `&`;
  8. str += `${key}=${value}`;
  9. });
  10. return str;
  11. }
  12. export async function fetchApi(path, options) {
  13. const urlPath = path.startsWith('/') ? path : `/${path}`;
  14. const headers = {};
  15. if (urlPath.startsWith('/me')) {
  16. let { session } = await browser.storage.local.get('session');
  17. if (!session) throw new Error('Unauthorized');
  18. const isExpired = Date.now() > session.expires_at * 1000 - 64000;
  19. if (isExpired) {
  20. const response = await fetchApi(
  21. `/session/refresh?token=${session.refresh_token}`
  22. );
  23. const result = await response.json();
  24. if (!response.ok) throw new Error(result.message);
  25. session = result;
  26. await browser.storage.local.set({ session });
  27. }
  28. headers.Authorization = `Bearer ${session.access_token}`;
  29. }
  30. return fetch(`${secrets.baseApiUrl}${urlPath}`, {
  31. headers: {
  32. 'Content-Type': 'application/json',
  33. ...headers,
  34. },
  35. ...options,
  36. });
  37. }
  38. export const googleSheets = {
  39. getUrl(spreadsheetId, range) {
  40. return `/services/google-sheets?spreadsheetId=${spreadsheetId}&range=${range}`;
  41. },
  42. getValues({ spreadsheetId, range }) {
  43. const url = this.getUrl(spreadsheetId, range);
  44. return fetchApi(url);
  45. },
  46. getRange({ spreadsheetId, range }) {
  47. return googleSheets.updateValues({
  48. range,
  49. append: true,
  50. spreadsheetId,
  51. options: {
  52. body: JSON.stringify({ values: [] }),
  53. queries: {
  54. valueInputOption: 'RAW',
  55. includeValuesInResponse: false,
  56. insertDataOption: 'INSERT_ROWS',
  57. },
  58. },
  59. });
  60. },
  61. updateValues({ spreadsheetId, range, options = {}, append }) {
  62. const url = `${this.getUrl(spreadsheetId, range)}&${queryBuilder(
  63. options?.queries || {}
  64. )}`;
  65. return fetchApi(url, {
  66. ...options,
  67. method: append ? 'POST' : 'PUT',
  68. });
  69. },
  70. };
  71. export async function cacheApi(key, callback, useCache = true) {
  72. const tenMinutes = 1000 * 10;
  73. const tenMinutesAgo = Date.now() - tenMinutes;
  74. const timerKey = `cache-time:${key}`;
  75. const cacheResult = parseJSON(sessionStorage.getItem(key), null);
  76. const cacheTime = +sessionStorage.getItem(timerKey) || Date.now();
  77. if (useCache && cacheResult && tenMinutesAgo < cacheTime) {
  78. return cacheResult;
  79. }
  80. const result = await callback();
  81. let cacheData = result;
  82. if (result?.cacheData) {
  83. cacheData = result?.cacheData;
  84. }
  85. sessionStorage.setItem(timerKey, Date.now());
  86. sessionStorage.setItem(key, JSON.stringify(cacheData));
  87. return result;
  88. }
  89. export async function getSharedWorkflows(useCache = true) {
  90. return cacheApi(
  91. 'shared-workflows',
  92. async () => {
  93. try {
  94. const response = await fetchApi('/me/workflows/shared?data=all');
  95. if (response.status !== 200) throw new Error(response.statusText);
  96. const result = await response.json();
  97. const sharedWorkflows = result.reduce((acc, item) => {
  98. item.drawflow = JSON.stringify(item.drawflow);
  99. item.table = item.table || item.dataColumns || [];
  100. item.createdAt = new Date(item.createdAt || Date.now()).getTime();
  101. acc[item.id] = item;
  102. return acc;
  103. }, {});
  104. return sharedWorkflows;
  105. } catch (error) {
  106. console.error(error);
  107. return {};
  108. }
  109. },
  110. useCache
  111. );
  112. }
  113. export async function getUserWorkflows(useCache = true) {
  114. return cacheApi(
  115. 'user-workflows',
  116. async () => {
  117. try {
  118. const { lastBackup } = await browser.storage.local.get('lastBackup');
  119. const response = await fetchApi(
  120. `/me/workflows?lastBackup=${(useCache && lastBackup) || null}`
  121. );
  122. if (!response.ok) throw new Error(response.statusText);
  123. const result = await response.json();
  124. const workflows = result.reduce(
  125. (acc, workflow) => {
  126. if (workflow.isHost) {
  127. acc.hosted[workflow.id] = {
  128. id: workflow.id,
  129. hostId: workflow.hostId,
  130. };
  131. }
  132. acc.backup.push(workflow);
  133. return acc;
  134. },
  135. { hosted: {}, backup: [] }
  136. );
  137. workflows.cacheData = {
  138. backup: [],
  139. hosted: workflows.hosted,
  140. };
  141. return workflows;
  142. } catch (error) {
  143. console.error(error);
  144. return {};
  145. }
  146. },
  147. useCache
  148. );
  149. }