feeds-fun 1.21.5 → 1.22.0

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.
package/src/logic/api.ts CHANGED
@@ -4,92 +4,112 @@ import * as t from "@/logic/types";
4
4
  import type * as e from "@/logic/enums";
5
5
  import * as settings from "@/logic/settings";
6
6
  import * as cookieConsent from "@/plugins/CookieConsent";
7
+ import {useGlobalState} from "@/stores/globalState";
7
8
 
8
- const ENTRY_POINT = "/api";
9
-
10
- const API_GET_FEEDS = `${ENTRY_POINT}/get-feeds`;
11
- const API_GET_LAST_ENTRIES = `${ENTRY_POINT}/get-last-entries`;
12
- const API_GET_LAST_COLLECTION_ENTRIES = `${ENTRY_POINT}/get-last-collection-entries`;
13
- const API_GET_ENTRIES_BY_IDS = `${ENTRY_POINT}/get-entries-by-ids`;
14
- const API_CREATE_OR_UPDATE_RULE = `${ENTRY_POINT}/create-or-update-rule`;
15
-
16
- const API_DELETE_RULE = `${ENTRY_POINT}/delete-rule`;
17
- const API_UPDATE_RULE = `${ENTRY_POINT}/update-rule`;
18
- const API_GET_RULES = `${ENTRY_POINT}/get-rules`;
19
- const API_GET_SCORE_DETAILS = `${ENTRY_POINT}/get-score-details`;
20
- const API_SET_MARKER = `${ENTRY_POINT}/set-marker`;
21
- const API_REMOVE_MARKER = `${ENTRY_POINT}/remove-marker`;
22
- const API_DISCOVER_FEEDS = `${ENTRY_POINT}/discover-feeds`;
23
- const API_ADD_FEED = `${ENTRY_POINT}/add-feed`;
24
- const API_ADD_OPML = `${ENTRY_POINT}/add-opml`;
25
- const API_UNSUBSCRIBE = `${ENTRY_POINT}/unsubscribe`;
26
- const API_GET_COLLECTIONS = `${ENTRY_POINT}/get-collections`;
27
- const API_GET_COLLECTION_FEEDS = `${ENTRY_POINT}/get-collection-feeds`;
28
- const API_SUBSCRIBE_TO_COLLECTIONS = `${ENTRY_POINT}/subscribe-to-collections`;
29
- const API_GET_TAGS_INFO = `${ENTRY_POINT}/get-tags-info`;
30
- const API_GET_USER_SETTINGS = `${ENTRY_POINT}/get-user-settings`;
31
- const API_SET_USER_SETTING = `${ENTRY_POINT}/set-user-setting`;
32
- const API_GET_RESOURCE_HISTORY = `${ENTRY_POINT}/get-resource-history`;
33
- const API_GET_INFO = `${ENTRY_POINT}/get-info`;
34
- const API_TRACK_EVENT = `${ENTRY_POINT}/track-event`;
35
- const API_REMOVE_USER = `${ENTRY_POINT}/remove-user`;
36
-
37
- let _onSessionLost: () => void = () => {};
38
-
39
- export function init({onSessionLost}: {onSessionLost: () => void}) {
40
- _onSessionLost = onSessionLost;
41
- }
42
-
43
- async function post({url, data}: {url: string; data: any}) {
44
- try {
45
- const response = await axios.post(url, data);
46
- return response.data;
47
- } catch (error) {
48
- console.log(error);
49
-
50
- if (error instanceof Error && "response" in error) {
51
- const axiosError = error as AxiosError;
52
- if (axiosError.response && axiosError.response.status === 401) {
53
- await _onSessionLost();
54
- }
55
- }
9
+ ///////////////
10
+ // API handlers
11
+ ///////////////
12
+
13
+ const publicEntryPoint = "/spa/api/public";
14
+ const privateEntryPoint = "/spa/api/private";
56
15
 
57
- throw error;
16
+ const apiPublic = axios.create({baseURL: publicEntryPoint, withCredentials: true});
17
+ const apiPrivate = axios.create({baseURL: privateEntryPoint, withCredentials: true});
18
+
19
+ // It is an open question what should we do in case of session expiration:
20
+ // - redirect to login page
21
+ // - redirect to home page & show notification
22
+ // Currently the easiest way to handle it is to always redirect to login page.
23
+ export function redirectToLogin(returnTo?: string) {
24
+ if (!returnTo) {
25
+ returnTo = window.location.pathname + window.location.search;
58
26
  }
27
+ window.location.assign(`/spa/auth/login?return_to=${encodeURIComponent(returnTo)}`);
59
28
  }
60
29
 
61
- export async function getFeeds() {
62
- const response = await post({url: API_GET_FEEDS, data: {}});
30
+ export function redirectToJoin(returnTo?: string) {
31
+ if (!returnTo) {
32
+ returnTo = window.location.pathname + window.location.search;
33
+ }
34
+ window.location.assign(`/spa/auth/join?return_to=${encodeURIComponent(returnTo)}`);
35
+ }
63
36
 
64
- const feeds = [];
37
+ export function logoutRedirect() {
38
+ window.location.assign("/spa/auth/logout");
39
+ }
65
40
 
66
- for (let rawFeed of response.feeds) {
67
- const feed = t.feedFromJSON(rawFeed);
68
- feeds.push(feed);
69
- }
41
+ let _refreshingAuth: Promise<void> | null = null;
70
42
 
71
- return feeds;
43
+ enum Ffun401Behaviour {
44
+ RedirectToLogin = "redirectToLogin",
45
+ DoNotRetry = "doNotRetry",
46
+ ReturnNull = "returnNull"
72
47
  }
73
48
 
74
- export async function getLastEntries({period, minTagCount}: {period: number; minTagCount: number}) {
75
- const response = await post({
76
- url: API_GET_LAST_ENTRIES,
77
- data: {
78
- period: period,
79
- minTagCount: minTagCount
49
+ // We try to refresh auth on 401 responses for private API.
50
+ // For the public API we do nothing, because it most likely means infrastructure issue.
51
+ apiPrivate.interceptors.response.use(
52
+ (r) => r,
53
+ async (error) => {
54
+ const {config, response} = error;
55
+
56
+ if (!response) {
57
+ throw error;
80
58
  }
81
- });
82
59
 
83
- const entries = [];
60
+ if (response.status !== 401) {
61
+ throw error;
62
+ }
84
63
 
85
- for (let rawEntry of response.entries) {
86
- const entry = t.entryFromJSON(rawEntry, response.tagsMapping);
87
- entries.push(entry);
64
+ if (config?.ffunRequestRetried) {
65
+ throw error;
66
+ }
67
+
68
+ if (config?.ffun401Behaviour === Ffun401Behaviour.RedirectToLogin) {
69
+ redirectToLogin();
70
+ return; // never reached
71
+ }
72
+
73
+ if (config?.ffun401Behaviour === Ffun401Behaviour.DoNotRetry) {
74
+ throw error;
75
+ }
76
+
77
+ if (config?.ffun401Behaviour === Ffun401Behaviour.ReturnNull) {
78
+ return {data: null};
79
+ }
80
+
81
+ (config as any).ffunRequestRetried = true;
82
+
83
+ if (!_refreshingAuth) {
84
+ _refreshingAuth = apiPrivate
85
+ // @ts-ignore
86
+ .post("/refresh-auth", undefined, {ffun401Behaviour: Ffun401Behaviour.RedirectToLogin})
87
+ .then(() => {})
88
+ .finally(() => {
89
+ _refreshingAuth = null;
90
+ });
91
+ }
92
+
93
+ await _refreshingAuth; // all 401s await the same refresh
94
+
95
+ return await apiPrivate(config); // retry the original request generically
88
96
  }
97
+ );
89
98
 
90
- return entries;
99
+ async function postPublic({url, data}: {url: string; data: any}) {
100
+ const response = await apiPublic.post(url, data);
101
+ return response.data;
91
102
  }
92
103
 
104
+ async function postPrivate({url, data, config}: {url: string; data: any; config?: any}) {
105
+ const response = await apiPrivate.post(url, data, config);
106
+ return response.data;
107
+ }
108
+
109
+ /////////////
110
+ // Public API
111
+ /////////////
112
+
93
113
  export async function getLastCollectionEntries({
94
114
  period,
95
115
  collectionSlug,
@@ -99,8 +119,8 @@ export async function getLastCollectionEntries({
99
119
  collectionSlug: t.CollectionSlug | null;
100
120
  minTagCount: number;
101
121
  }) {
102
- const response = await post({
103
- url: API_GET_LAST_COLLECTION_ENTRIES,
122
+ const response = await postPublic({
123
+ url: "/get-last-collection-entries",
104
124
  data: {period: period, collectionSlug: collectionSlug, minTagCount: minTagCount}
105
125
  });
106
126
 
@@ -115,9 +135,157 @@ export async function getLastCollectionEntries({
115
135
  }
116
136
 
117
137
  export async function getEntriesByIds({ids}: {ids: t.EntryId[]}) {
118
- const response = await post({
119
- url: API_GET_ENTRIES_BY_IDS,
120
- data: {ids: ids}
138
+ const globalState = useGlobalState();
139
+
140
+ let response = null;
141
+
142
+ if (globalState.loginConfirmed) {
143
+ response = await postPrivate({
144
+ url: "/get-entries-by-ids",
145
+ data: {ids: ids},
146
+ config: {ffun401Behaviour: Ffun401Behaviour.ReturnNull}
147
+ });
148
+ }
149
+
150
+ if (!response) {
151
+ response = await postPublic({
152
+ url: "/get-entries-by-ids",
153
+ data: {ids: ids}
154
+ });
155
+ }
156
+
157
+ const entries = [];
158
+
159
+ for (let rawEntry of response.entries) {
160
+ const entry = t.entryFromJSON(rawEntry, response.tagsMapping);
161
+ entries.push(entry);
162
+ }
163
+
164
+ return entries;
165
+ }
166
+
167
+ export async function getCollections() {
168
+ const response = await postPublic({url: "/get-collections", data: {}});
169
+
170
+ const collections = [];
171
+
172
+ for (let rawCollection of response.collections) {
173
+ const collection = t.collectionFromJSON(rawCollection);
174
+ collections.push(collection);
175
+ }
176
+
177
+ return collections;
178
+ }
179
+
180
+ export async function getCollectionFeeds({collectionId}: {collectionId: t.CollectionId}) {
181
+ const response = await postPublic({
182
+ url: "/get-collection-feeds",
183
+ data: {collectionId: collectionId}
184
+ });
185
+
186
+ const feeds = [];
187
+
188
+ for (let rawFeed of response.feeds) {
189
+ const feed = t.collectionFeedInfoFromJSON(rawFeed);
190
+ feeds.push(feed);
191
+ }
192
+
193
+ return feeds;
194
+ }
195
+
196
+ export async function getTagsInfo({uids}: {uids: string[]}) {
197
+ const response = await postPublic({url: "/get-tags-info", data: {uids: uids}});
198
+
199
+ const tags: {[key: string]: t.TagInfo} = {};
200
+
201
+ for (let uid in response.tags) {
202
+ const rawTag = response.tags[uid];
203
+ const tag = t.tagInfoFromJSON(rawTag);
204
+ tags[uid] = tag;
205
+ }
206
+
207
+ return tags;
208
+ }
209
+
210
+ export async function getInfo() {
211
+ const response = await postPublic({url: "/get-info", data: {}});
212
+
213
+ return t.stateInfoFromJSON(response);
214
+ }
215
+
216
+ export async function getUser() {
217
+ const response = await postPrivate({
218
+ url: "/get-user",
219
+ data: {},
220
+ config: {ffun401Behaviour: Ffun401Behaviour.ReturnNull}
221
+ });
222
+
223
+ if (!response) {
224
+ return null;
225
+ }
226
+
227
+ return t.userInfoFromJSON(response);
228
+ }
229
+
230
+ export function trackEvent(data: {[key: string]: string | number | null}) {
231
+ if (!settings.trackEvents) {
232
+ return;
233
+ }
234
+
235
+ if (!cookieConsent.isAnalyticsAllowed()) {
236
+ return;
237
+ }
238
+
239
+ const globalState = useGlobalState();
240
+
241
+ let url: string;
242
+
243
+ if (globalState.loginConfirmed) {
244
+ url = privateEntryPoint + "/track-event";
245
+ } else {
246
+ url = publicEntryPoint + "/track-event";
247
+ }
248
+
249
+ let payload = JSON.stringify({event: data});
250
+
251
+ if ("sendBeacon" in navigator) {
252
+ return navigator.sendBeacon(url, payload);
253
+ }
254
+
255
+ // Fallback: fire-and-forget; avoid preflight by using text/plain + no-cors
256
+ fetch(url, {
257
+ method: "POST",
258
+ keepalive: true,
259
+ mode: "no-cors",
260
+ headers: {"Content-Type": "text/plain;charset=UTF-8"},
261
+ body: payload
262
+ }).catch(() => {});
263
+ }
264
+
265
+ //////////////
266
+ // Private API
267
+ //////////////
268
+
269
+ export async function getFeeds() {
270
+ const response = await postPrivate({url: "/get-feeds", data: {}});
271
+
272
+ const feeds = [];
273
+
274
+ for (let rawFeed of response.feeds) {
275
+ const feed = t.feedFromJSON(rawFeed);
276
+ feeds.push(feed);
277
+ }
278
+
279
+ return feeds;
280
+ }
281
+
282
+ export async function getLastEntries({period, minTagCount}: {period: number; minTagCount: number}) {
283
+ const response = await postPrivate({
284
+ url: "/get-last-entries",
285
+ data: {
286
+ period: period,
287
+ minTagCount: minTagCount
288
+ }
121
289
  });
122
290
 
123
291
  const entries = [];
@@ -139,8 +307,8 @@ export async function createOrUpdateRule({
139
307
  excludedTags: string[];
140
308
  score: number;
141
309
  }) {
142
- const response = await post({
143
- url: API_CREATE_OR_UPDATE_RULE,
310
+ const response = await postPrivate({
311
+ url: "/create-or-update-rule",
144
312
  data: {
145
313
  requiredTags: requiredTags,
146
314
  excludedTags: excludedTags,
@@ -151,7 +319,7 @@ export async function createOrUpdateRule({
151
319
  }
152
320
 
153
321
  export async function deleteRule({id}: {id: t.RuleId}) {
154
- const response = await post({url: API_DELETE_RULE, data: {id: id}});
322
+ const response = await postPrivate({url: "/delete-rule", data: {id: id}});
155
323
  return response;
156
324
  }
157
325
 
@@ -166,15 +334,15 @@ export async function updateRule({
166
334
  excludedTags: string[];
167
335
  score: number;
168
336
  }) {
169
- const response = await post({
170
- url: API_UPDATE_RULE,
337
+ const response = await postPrivate({
338
+ url: "/update-rule",
171
339
  data: {id: id, score: score, requiredTags: requiredTags, excludedTags: excludedTags}
172
340
  });
173
341
  return response;
174
342
  }
175
343
 
176
344
  export async function getRules() {
177
- const response = await post({url: API_GET_RULES, data: {}});
345
+ const response = await postPrivate({url: "/get-rules", data: {}});
178
346
 
179
347
  const rules = [];
180
348
 
@@ -187,8 +355,8 @@ export async function getRules() {
187
355
  }
188
356
 
189
357
  export async function getScoreDetails({entryId}: {entryId: t.EntryId}) {
190
- const response = await post({
191
- url: API_GET_SCORE_DETAILS,
358
+ const response = await postPrivate({
359
+ url: "/get-score-details",
192
360
  data: {entryId: entryId}
193
361
  });
194
362
 
@@ -203,21 +371,21 @@ export async function getScoreDetails({entryId}: {entryId: t.EntryId}) {
203
371
  }
204
372
 
205
373
  export async function setMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
206
- await post({
207
- url: API_SET_MARKER,
374
+ await postPrivate({
375
+ url: "/set-marker",
208
376
  data: {entryId: entryId, marker: marker}
209
377
  });
210
378
  }
211
379
 
212
380
  export async function removeMarker({entryId, marker}: {entryId: t.EntryId; marker: e.Marker}) {
213
- await post({
214
- url: API_REMOVE_MARKER,
381
+ await postPrivate({
382
+ url: "/remove-marker",
215
383
  data: {entryId: entryId, marker: marker}
216
384
  });
217
385
  }
218
386
 
219
387
  export async function discoverFeeds({url}: {url: string}) {
220
- const response = await post({url: API_DISCOVER_FEEDS, data: {url: url}});
388
+ const response = await postPrivate({url: "/discover-feeds", data: {url: url}});
221
389
 
222
390
  const feeds = [];
223
391
  const messages = [];
@@ -236,71 +404,28 @@ export async function discoverFeeds({url}: {url: string}) {
236
404
  }
237
405
 
238
406
  export async function addFeed({url}: {url: string}) {
239
- const response = await post({url: API_ADD_FEED, data: {url: url}});
407
+ const response = await postPrivate({url: "/add-feed", data: {url: url}});
240
408
 
241
409
  return t.feedFromJSON(response.feed);
242
410
  }
243
411
 
244
412
  export async function addOPML({content}: {content: string}) {
245
- await post({url: API_ADD_OPML, data: {content: content}});
413
+ await postPrivate({url: "/add-opml", data: {content: content}});
246
414
  }
247
415
 
248
416
  export async function unsubscribe({feedId}: {feedId: t.FeedId}) {
249
- await post({url: API_UNSUBSCRIBE, data: {feedId: feedId}});
250
- }
251
-
252
- export async function getCollections() {
253
- const response = await post({url: API_GET_COLLECTIONS, data: {}});
254
-
255
- const collections = [];
256
-
257
- for (let rawCollection of response.collections) {
258
- const collection = t.collectionFromJSON(rawCollection);
259
- collections.push(collection);
260
- }
261
-
262
- return collections;
263
- }
264
-
265
- export async function getCollectionFeeds({collectionId}: {collectionId: t.CollectionId}) {
266
- const response = await post({
267
- url: API_GET_COLLECTION_FEEDS,
268
- data: {collectionId: collectionId}
269
- });
270
-
271
- const feeds = [];
272
-
273
- for (let rawFeed of response.feeds) {
274
- const feed = t.collectionFeedInfoFromJSON(rawFeed);
275
- feeds.push(feed);
276
- }
277
-
278
- return feeds;
417
+ await postPrivate({url: "/unsubscribe", data: {feedId: feedId}});
279
418
  }
280
419
 
281
420
  export async function subscribeToCollections({collectionsIds}: {collectionsIds: t.CollectionId[]}) {
282
- await post({
283
- url: API_SUBSCRIBE_TO_COLLECTIONS,
421
+ await postPrivate({
422
+ url: "/subscribe-to-collections",
284
423
  data: {collections: collectionsIds}
285
424
  });
286
425
  }
287
426
 
288
- export async function getTagsInfo({uids}: {uids: string[]}) {
289
- const response = await post({url: API_GET_TAGS_INFO, data: {uids: uids}});
290
-
291
- const tags: {[key: string]: t.TagInfo} = {};
292
-
293
- for (let uid in response.tags) {
294
- const rawTag = response.tags[uid];
295
- const tag = t.tagInfoFromJSON(rawTag);
296
- tags[uid] = tag;
297
- }
298
-
299
- return tags;
300
- }
301
-
302
427
  export async function getUserSettings() {
303
- const response = await post({url: API_GET_USER_SETTINGS, data: {}});
428
+ const response = await postPrivate({url: "/get-user-settings", data: {}});
304
429
 
305
430
  const settings: {[key: string]: t.UserSetting} = {};
306
431
 
@@ -313,12 +438,12 @@ export async function getUserSettings() {
313
438
  }
314
439
 
315
440
  export async function setUserSetting({kind, value}: {kind: string; value: string | number | boolean}) {
316
- await post({url: API_SET_USER_SETTING, data: {kind: kind, value: value}});
441
+ await postPrivate({url: "/set-user-setting", data: {kind: kind, value: value}});
317
442
  }
318
443
 
319
444
  export async function getResourceHistory({kind}: {kind: string}) {
320
- const response = await post({
321
- url: API_GET_RESOURCE_HISTORY,
445
+ const response = await postPrivate({
446
+ url: "/get-resource-history",
322
447
  data: {kind: kind}
323
448
  });
324
449
 
@@ -332,24 +457,6 @@ export async function getResourceHistory({kind}: {kind: string}) {
332
457
  return history;
333
458
  }
334
459
 
335
- export async function getInfo() {
336
- const response = await post({url: API_GET_INFO, data: {}});
337
-
338
- return response;
339
- }
340
-
341
- export async function trackEvent(data: {[key: string]: string | number | null}) {
342
- if (!settings.trackEvents) {
343
- return;
344
- }
345
-
346
- if (!cookieConsent.isAnalyticsAllowed()) {
347
- return;
348
- }
349
-
350
- await post({url: API_TRACK_EVENT, data: {event: data}});
351
- }
352
-
353
460
  export async function removeUser() {
354
- await post({url: API_REMOVE_USER, data: {}});
461
+ await postPrivate({url: "/remove-user", data: {}});
355
462
  }
@@ -5,6 +5,16 @@ export type AnyEnum = {
5
5
  [key in keyof any]: string | number;
6
6
  };
7
7
 
8
+ //////////////
9
+ // Login state
10
+ //////////////
11
+
12
+ export enum LoginState {
13
+ Unknown = "unknown",
14
+ LoggedOut = "logged-out",
15
+ LoggedIn = "logged-in"
16
+ }
17
+
8
18
  ///////////////////
9
19
  // Main panel modes
10
20
  ///////////////////
@@ -18,31 +18,39 @@ export type TagChangeSource = "tags_filter" | "entry_record" | "rule_record";
18
18
  export type SidebarVisibilityChangeEvent = "hide" | "show";
19
19
  export type SidebarVisibilityChangeSource = "top_sidebar_button";
20
20
 
21
- export async function newsLinkOpened({entryId, view}: {entryId: t.EntryId; view: EventsViewName}) {
22
- await api.trackEvent({
21
+ export function newsLinkOpened({entryId, view}: {entryId: t.EntryId; view: EventsViewName}) {
22
+ api.trackEvent({
23
23
  name: "news_link_opened",
24
24
  view: view,
25
25
  entry_id: entryId
26
26
  });
27
27
  }
28
28
 
29
- export async function newsBodyOpened({entryId, view}: {entryId: t.EntryId; view: EventsViewName}) {
30
- await api.trackEvent({
29
+ export function newsBodyOpened({entryId, view}: {entryId: t.EntryId; view: EventsViewName}) {
30
+ api.trackEvent({
31
31
  name: "news_body_opened",
32
32
  view: view,
33
33
  entry_id: entryId
34
34
  });
35
35
  }
36
36
 
37
- export async function socialLinkClicked({linkType, view}: {linkType: string; view: EventsViewName}) {
38
- await api.trackEvent({
37
+ export function socialLinkClicked({linkType, view}: {linkType: string; view: EventsViewName}) {
38
+ api.trackEvent({
39
39
  name: "social_link_clicked",
40
40
  view: view,
41
41
  link_type: linkType
42
42
  });
43
43
  }
44
44
 
45
- export async function sidebarStateChanged({
45
+ export function authButtonClicked({buttonType, view}: {buttonType: string; view: EventsViewName}) {
46
+ api.trackEvent({
47
+ name: "auth_button_clicked",
48
+ view: view,
49
+ button_type: buttonType
50
+ });
51
+ }
52
+
53
+ export function sidebarStateChanged({
46
54
  subEvent,
47
55
  view,
48
56
  source
@@ -51,7 +59,7 @@ export async function sidebarStateChanged({
51
59
  view: EventsViewName;
52
60
  source: SidebarVisibilityChangeSource;
53
61
  }) {
54
- await api.trackEvent({
62
+ api.trackEvent({
55
63
  name: "sidebar_state_changed",
56
64
  view: view,
57
65
  sub_event: subEvent,
@@ -59,7 +67,7 @@ export async function sidebarStateChanged({
59
67
  });
60
68
  }
61
69
 
62
- export async function tagStateChanged({
70
+ export function tagStateChanged({
63
71
  tag,
64
72
  fromState,
65
73
  toState,
@@ -74,7 +82,7 @@ export async function tagStateChanged({
74
82
  }) {
75
83
  // const eventsView = inject<events.EventViewName>("eventsViewName");
76
84
 
77
- await api.trackEvent({
85
+ api.trackEvent({
78
86
  name: "tag_filter_state_changed",
79
87
  tag: tag,
80
88
  from_state: fromState,
@@ -84,7 +92,7 @@ export async function tagStateChanged({
84
92
  });
85
93
  }
86
94
 
87
- export async function trackUtm({
95
+ export function trackUtm({
88
96
  utm_source,
89
97
  utm_medium,
90
98
  utm_campaign
@@ -93,7 +101,7 @@ export async function trackUtm({
93
101
  utm_medium: string;
94
102
  utm_campaign: string;
95
103
  }) {
96
- await api.trackEvent({
104
+ api.trackEvent({
97
105
  name: "user_utm",
98
106
  utm_source: utm_source,
99
107
  utm_medium: utm_medium,