musora-content-services 1.3.21 → 2.0.5
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/.editorconfig +16 -0
- package/.github/workflows/node.js.yml +0 -0
- package/.prettierignore +0 -0
- package/.prettierrc +0 -0
- package/CHANGELOG.md +4 -4
- package/README.md +0 -0
- package/babel.config.cjs +0 -0
- package/docs/Content-Organization.html +245 -0
- package/docs/Playlists.html +192 -0
- package/docs/config.js.html +14 -5
- package/docs/content-org_playlists-types.js.html +109 -0
- package/docs/content-org_playlists.js.html +194 -0
- package/docs/content-org_types.js.html +112 -0
- package/docs/content.js.html +443 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/global.html +3878 -0
- package/docs/index.html +2 -2
- package/docs/module-Config.html +60 -7
- package/docs/module-Content-Organization-Playlists.html +194 -0
- package/docs/module-Content-Organization.html +976 -0
- package/docs/module-Content-Services-V2.html +2433 -0
- package/docs/module-Playlists.html +969 -0
- package/docs/module-Railcontent-Services.html +3052 -1991
- package/docs/module-Sanity-Services.html +57 -43
- package/docs/module-Session-Management.html +575 -0
- package/docs/module-User-Permissions.html +406 -0
- package/docs/module-playlists.html +1878 -0
- package/docs/module-playlists_.html +108 -0
- package/docs/railcontent.js.html +149 -112
- package/docs/sanity.js.html +297 -109
- package/docs/scripts/collapse.js +0 -0
- package/docs/scripts/commonNav.js +0 -0
- package/docs/scripts/linenumber.js +0 -0
- package/docs/scripts/nav.js +0 -0
- package/docs/scripts/polyfill.js +0 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -0
- package/docs/scripts/prettify/lang-css.js +0 -0
- package/docs/scripts/prettify/prettify.js +0 -0
- package/docs/scripts/search.js +0 -0
- package/docs/styles/jsdoc.css +0 -0
- package/docs/styles/prettify.css +0 -0
- package/docs/user_permissions.js.html +110 -0
- package/docs/user_sessions.js.html +139 -0
- package/docs/user_types.js.html +188 -0
- package/jest.config.js +0 -0
- package/jsdoc.json +3 -0
- package/package.json +1 -1
- package/publish.sh +2 -2
- package/src/contentMetaData.js +307 -1088
- package/src/contentTypeConfig.js +108 -4
- package/src/filterBuilder.js +6 -6
- package/src/index.d.ts +67 -9
- package/src/index.js +67 -9
- package/src/{services → lib}/lastUpdated.js +17 -1
- package/src/services/content-org/playlists-types.js +37 -0
- package/src/services/content-org/playlists.js +122 -0
- package/src/services/content.js +371 -0
- package/src/services/contentLikes.js +0 -0
- package/src/services/contentProgress.js +0 -0
- package/src/services/forum.js +57 -0
- package/src/services/railcontent.js +122 -122
- package/src/services/recommendations.js +19 -0
- package/src/services/sanity.js +278 -104
- package/src/services/{userPermissions.js → user/permissions.js} +16 -2
- package/src/services/user/sessions.js +67 -0
- package/src/services/user/types.js +116 -0
- package/src/services/userActivity.js +32 -0
- package/test/content.test.js +116 -0
- package/test/contentProgress.test.js +83 -5
- package/test/forum.test.js +18 -0
- package/test/initializeTests.js +6 -1
- package/test/{lastUpdated.test.js → lib/lastUpdated.test.js} +2 -5
- package/test/live/contentProgressLive.test.js +0 -0
- package/test/live/railcontentLive.test.js +0 -0
- package/test/localStorageMock.js +0 -0
- package/test/log.js +0 -0
- package/test/sanityQueryService.test.js +66 -18
- package/test/{userPermissions.test.js → user/permissions.test.js} +3 -3
- package/tools/generate-index.cjs +16 -3
|
@@ -290,6 +290,11 @@ async function patchDataHandler(url, data) {
|
|
|
290
290
|
return fetchHandler(url, 'patch', null, data)
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
async function deleteDataHandler(url, data) {
|
|
294
|
+
return fetchHandler(url, 'delete')
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// TODO: this should be extracted to a utility file
|
|
293
298
|
export async function fetchHandler(url, method = 'get', dataVersion = null, body = null) {
|
|
294
299
|
let headers = {
|
|
295
300
|
'Content-Type': 'application/json',
|
|
@@ -492,10 +497,8 @@ export async function fetchChallengeUserActiveChallenges(brand = null) {
|
|
|
492
497
|
* @returns {Promise<any|null>}
|
|
493
498
|
*/
|
|
494
499
|
export async function fetchCarouselCardData(brand = null) {
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
if (globalConfig.sanityConfig.perspective === 'previewDrafts') urlParams.push(`sanityPreview`)
|
|
498
|
-
const url = `/api/v2/content/carousel${urlParams.length ? `?${urlParams.join('&')}` : ''}`
|
|
500
|
+
const brandParam = brand ? `?brand=${brand}` : ''
|
|
501
|
+
let url = `/api/v2/content/carousel${brandParam}`
|
|
499
502
|
return await fetchHandler(url, 'get')
|
|
500
503
|
}
|
|
501
504
|
|
|
@@ -616,41 +619,6 @@ export async function postChallengesHideCompletedBanner(contentId) {
|
|
|
616
619
|
return await fetchHandler(url, 'post')
|
|
617
620
|
}
|
|
618
621
|
|
|
619
|
-
/**
|
|
620
|
-
* Fetches user playlists for a specific brand.
|
|
621
|
-
*
|
|
622
|
-
* Allows optional pagination, sorting, and search parameters to control the result set.
|
|
623
|
-
*
|
|
624
|
-
* @param {string} brand - The brand identifier for which playlists are being fetched.
|
|
625
|
-
* @param {number} [params.limit=10] - The maximum number of playlists to return per page (default is 10).
|
|
626
|
-
* @param {number} [params.page=1] - The page number for pagination.
|
|
627
|
-
* @param {string} [params.sort='-created_at'] - The sorting order for the playlists (default is by created_at in descending order).
|
|
628
|
-
* @param {string} [params.searchTerm] - A search term to filter playlists by name.
|
|
629
|
-
* @param {int|string} [params.content_id] - If content_id exists, the endpoint checks in each playlist if we have the content in the items.
|
|
630
|
-
*
|
|
631
|
-
* @returns {Promise<Object|null>} - A promise that resolves to the response from the API, containing the user playlists data.
|
|
632
|
-
*
|
|
633
|
-
* @example
|
|
634
|
-
* fetchUserPlaylists('drumeo', { page: 1, sort: 'name', searchTerm: 'rock' })
|
|
635
|
-
* .then(playlists => console.log(playlists))
|
|
636
|
-
* .catch(error => console.error(error));
|
|
637
|
-
*/
|
|
638
|
-
export async function fetchUserPlaylists(
|
|
639
|
-
brand,
|
|
640
|
-
{ page, limit, sort, searchTerm, content_id, categories } = {}
|
|
641
|
-
) {
|
|
642
|
-
let url
|
|
643
|
-
const limitString = limit ? `&limit=${limit}` : ''
|
|
644
|
-
const pageString = page ? `&page=${page}` : ''
|
|
645
|
-
const sortString = sort ? `&sort=${sort}` : ''
|
|
646
|
-
const searchFilter = searchTerm ? `&term=${searchTerm}` : ''
|
|
647
|
-
const content = content_id ? `&content_id=${content_id}` : ''
|
|
648
|
-
const categoryString =
|
|
649
|
-
categories && categories.length ? categories.map((cat) => `categories[]=${cat}`).join('&') : ''
|
|
650
|
-
url = `/playlists/all?brand=${brand}${limitString}${pageString}${sortString}${searchFilter}${content}${categoryString ? `&${categoryString}` : ''}`
|
|
651
|
-
return await fetchHandler(url)
|
|
652
|
-
}
|
|
653
|
-
|
|
654
622
|
/**
|
|
655
623
|
* Duplicates an existing playlist by sending a POST request to the API.
|
|
656
624
|
*
|
|
@@ -747,36 +715,6 @@ export async function updatePlaylist(playlistId, updatedData) {
|
|
|
747
715
|
return await fetchHandler(url, 'PUT', null, updatedData)
|
|
748
716
|
}
|
|
749
717
|
|
|
750
|
-
/**
|
|
751
|
-
* Creates a new user playlist by sending a POST request with playlist data to the API.
|
|
752
|
-
*
|
|
753
|
-
* This function calls the `/playlists/playlist` endpoint, where the server validates the incoming data and associates
|
|
754
|
-
* the new playlist with the authenticated user. The `name` field is required, while other fields are optional.
|
|
755
|
-
*
|
|
756
|
-
* @param {Object} playlistData - An object containing data to create the playlist. The fields include:
|
|
757
|
-
* - `name` (string): The name of the new playlist (required, max 255 characters).
|
|
758
|
-
* - `description` (string): A description of the playlist (optional, max 1000 characters).
|
|
759
|
-
* - `category` (string): The category of the playlist.
|
|
760
|
-
* - `thumbnail_url` (string): The URL of the playlist thumbnail (optional, must be a valid URL).
|
|
761
|
-
* - `private` (boolean): Whether the playlist is private (optional, defaults to true).
|
|
762
|
-
* - `brand` (string): Brand identifier for the playlist.
|
|
763
|
-
*
|
|
764
|
-
* @returns {Promise<Object>} - A promise that resolves to the created playlist data if successful, or an error response if validation fails.
|
|
765
|
-
*
|
|
766
|
-
* The server response includes:
|
|
767
|
-
* - `message`: Success message indicating playlist creation (e.g., "Playlist created successfully").
|
|
768
|
-
* - `playlist`: The data for the created playlist, including the `user_id` of the authenticated user.
|
|
769
|
-
*
|
|
770
|
-
* @example
|
|
771
|
-
* createPlaylist({ name: "My Playlist", description: "A cool playlist", private: true })
|
|
772
|
-
* .then(response => console.log(response.message))
|
|
773
|
-
* .catch(error => console.error('Error creating playlist:', error));
|
|
774
|
-
*/
|
|
775
|
-
export async function createPlaylist(playlistData) {
|
|
776
|
-
const url = `/playlists/playlist`
|
|
777
|
-
return await fetchHandler(url, 'POST', null, playlistData)
|
|
778
|
-
}
|
|
779
|
-
|
|
780
718
|
/**
|
|
781
719
|
* Sends a request to "like" a playlist on behalf of the authenticated user.
|
|
782
720
|
*
|
|
@@ -998,51 +936,6 @@ export async function postContentReset(contentId) {
|
|
|
998
936
|
return postDataHandler(url, { contentId: contentId })
|
|
999
937
|
}
|
|
1000
938
|
|
|
1001
|
-
/**
|
|
1002
|
-
* Adds an item to one or more playlists by making a POST request to the `/playlists/add-item` endpoint.
|
|
1003
|
-
*
|
|
1004
|
-
* @param {Object} payload - The request payload containing necessary parameters.
|
|
1005
|
-
* @param {number} payload.content_id - The ID of the content to add to the playlist(s).
|
|
1006
|
-
* @param {Array<number>} payload.playlist_id - An array of playlist IDs where the content should be added.
|
|
1007
|
-
* @param {boolean} [payload.import_full_soundslice_assignment=false] - Flag to include full Soundslice assignments.
|
|
1008
|
-
* @param {boolean} [payload.import_instrumentless_soundslice_assignment=false] - Flag to include instrumentless Soundslice assignments.
|
|
1009
|
-
* @param {boolean} [payload.import_high_routine=false] - Flag to include high routine content.
|
|
1010
|
-
* @param {boolean} [payload.import_low_routine=false] - Flag to include low routine content.
|
|
1011
|
-
* @param {boolean} [payload.import_all_assignments=false] - Flag to include all Soundslice assignments if true.
|
|
1012
|
-
*
|
|
1013
|
-
* @returns {Promise<Object|null>} - A promise that resolves to an object with the response data, including:
|
|
1014
|
-
* - `success` (boolean): Indicates if the items were added successfully (`true` on success).
|
|
1015
|
-
* - `limit_excedeed` (Array): A list of playlists where the item limit was exceeded, if any.
|
|
1016
|
-
* - `successful` (Array): A list of successfully added items (empty if none).
|
|
1017
|
-
*
|
|
1018
|
-
* Resolves to `null` if the request fails.
|
|
1019
|
-
* @throws {Error} - Throws an error if the request encounters issues during the operation.
|
|
1020
|
-
*
|
|
1021
|
-
* @example
|
|
1022
|
-
* const payload = {
|
|
1023
|
-
* content_id: 123,
|
|
1024
|
-
* playlist_id: [1, 2, 3],
|
|
1025
|
-
* import_all_assignments: true
|
|
1026
|
-
* };
|
|
1027
|
-
*
|
|
1028
|
-
* addItemToPlaylist(payload)
|
|
1029
|
-
* .then(response => {
|
|
1030
|
-
* if (response?.success) {
|
|
1031
|
-
* console.log("Item(s) added to playlist successfully");
|
|
1032
|
-
* }
|
|
1033
|
-
* if (response?.limit_excedeed) {
|
|
1034
|
-
* console.warn("Some playlists exceeded the item limit:", response.limit_excedeed);
|
|
1035
|
-
* }
|
|
1036
|
-
* })
|
|
1037
|
-
* .catch(error => {
|
|
1038
|
-
* console.error("Error adding item to playlist:", error);
|
|
1039
|
-
* });
|
|
1040
|
-
*/
|
|
1041
|
-
export async function addItemToPlaylist(payload) {
|
|
1042
|
-
const url = `/playlists/add-item`
|
|
1043
|
-
return await fetchHandler(url, 'POST', null, payload)
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
939
|
/**
|
|
1047
940
|
* Retrieves the count of lessons and assignments associated with a specific content ID.
|
|
1048
941
|
*
|
|
@@ -1207,27 +1100,27 @@ export async function setStudentViewForUser(userId, enable) {
|
|
|
1207
1100
|
* @returns {Promise<Object|null>} - A promise that resolves to an comment object
|
|
1208
1101
|
*/
|
|
1209
1102
|
export async function fetchTopComment(railcontentId) {
|
|
1210
|
-
const url = `/api/content/v1/comments/${railcontentId}/top`
|
|
1103
|
+
const url = `/api/content/v1/comments/content/${railcontentId}/top`
|
|
1211
1104
|
return await fetchHandler(url)
|
|
1212
1105
|
}
|
|
1213
1106
|
|
|
1214
1107
|
/**
|
|
1215
1108
|
*
|
|
1216
|
-
* @param railcontentId
|
|
1217
|
-
* @param page
|
|
1218
|
-
* @param limit
|
|
1109
|
+
* @param {int} railcontentId
|
|
1110
|
+
* @param {int} page
|
|
1111
|
+
* @param {int} limit
|
|
1219
1112
|
* @returns {Promise<*|null>}
|
|
1220
1113
|
*/
|
|
1221
1114
|
export async function fetchComments(railcontentId, page = 1, limit = 20) {
|
|
1222
|
-
const url = `/api/content/v1/comments/${railcontentId}/all?page=${page}&limit=${limit}`
|
|
1115
|
+
const url = `/api/content/v1/comments/content/${railcontentId}/all?page=${page}&limit=${limit}`
|
|
1223
1116
|
return await fetchHandler(url)
|
|
1224
1117
|
}
|
|
1225
1118
|
|
|
1226
1119
|
/**
|
|
1227
1120
|
*
|
|
1228
|
-
* @param commentId
|
|
1229
|
-
* @param page
|
|
1230
|
-
* @param limit
|
|
1121
|
+
* @param {int} commentId
|
|
1122
|
+
* @param {int} page
|
|
1123
|
+
* @param {int} limit
|
|
1231
1124
|
* @returns {Promise<*|null>}
|
|
1232
1125
|
*/
|
|
1233
1126
|
export async function fetchCommentRelies(commentId, page = 1, limit = 20) {
|
|
@@ -1235,6 +1128,113 @@ export async function fetchCommentRelies(commentId, page = 1, limit = 20) {
|
|
|
1235
1128
|
return await fetchHandler(url)
|
|
1236
1129
|
}
|
|
1237
1130
|
|
|
1131
|
+
/**
|
|
1132
|
+
* @param {int} commentId
|
|
1133
|
+
* @returns {Promise<*|null>}
|
|
1134
|
+
*/
|
|
1135
|
+
export async function deleteComment(commentId) {
|
|
1136
|
+
const url = `/api/content/v1/comments/${commentId}`
|
|
1137
|
+
return await fetchHandler(url, 'DELETE')
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
/**
|
|
1141
|
+
* @param {int} commentId
|
|
1142
|
+
* @param {string} comment
|
|
1143
|
+
* @returns {Promise<*|null>}
|
|
1144
|
+
*/
|
|
1145
|
+
export async function replyToComment(commentId, comment) {
|
|
1146
|
+
const data = { comment: comment }
|
|
1147
|
+
const url = `/api/content/v1/comments/${commentId}/reply`
|
|
1148
|
+
return await postDataHandler(url, data)
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
/**
|
|
1152
|
+
* @param {int} railcontentId
|
|
1153
|
+
* @param {string} comment
|
|
1154
|
+
* @returns {Promise<*|null>}
|
|
1155
|
+
*/
|
|
1156
|
+
export async function createComment(railcontentId, comment) {
|
|
1157
|
+
const data = {
|
|
1158
|
+
comment: comment,
|
|
1159
|
+
content_id: railcontentId,
|
|
1160
|
+
}
|
|
1161
|
+
const url = `/api/content/v1/comments/store`
|
|
1162
|
+
return await postDataHandler(url, data)
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* @param {int} commentId
|
|
1167
|
+
* @returns {Promise<*|null>}
|
|
1168
|
+
*/
|
|
1169
|
+
export async function assignModeratorToComment(commentId) {
|
|
1170
|
+
const url = `/api/content/v1/comments/${commentId}/assign_moderator`
|
|
1171
|
+
return await postDataHandler(url)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/**
|
|
1175
|
+
* @param {int} commentId
|
|
1176
|
+
* @returns {Promise<*|null>}
|
|
1177
|
+
*/
|
|
1178
|
+
export async function unassignModeratorToComment(commentId) {
|
|
1179
|
+
const url = `/api/content/v1/comments/${commentId}/unassign_moderator`
|
|
1180
|
+
return await postDataHandler(url)
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* @param {int} commentId
|
|
1185
|
+
* @returns {Promise<*|null>}
|
|
1186
|
+
*/
|
|
1187
|
+
export async function likeComment(commentId) {
|
|
1188
|
+
const url = `/api/content/v1/comments/${commentId}/like`
|
|
1189
|
+
return await postDataHandler(url)
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/**
|
|
1193
|
+
* @param {int} commentId
|
|
1194
|
+
* @returns {Promise<*|null>}
|
|
1195
|
+
*/
|
|
1196
|
+
export async function unlikeComment(commentId) {
|
|
1197
|
+
const url = `/api/content/v1/comments/${commentId}/like`
|
|
1198
|
+
return await deleteDataHandler(url)
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* @param {int} commentId
|
|
1203
|
+
* @returns {Promise<*|null>}
|
|
1204
|
+
*/
|
|
1205
|
+
export async function closeComment(commentId) {
|
|
1206
|
+
const url = `/api/content/v1/comments/${commentId}`
|
|
1207
|
+
const data = {
|
|
1208
|
+
conversation_status: 'closed',
|
|
1209
|
+
}
|
|
1210
|
+
return await patchDataHandler(url, data)
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* @param {int} commentId
|
|
1215
|
+
* @returns {Promise<*|null>}
|
|
1216
|
+
*/
|
|
1217
|
+
export async function openComment(commentId) {
|
|
1218
|
+
const url = `/api/content/v1/comments/${commentId}`
|
|
1219
|
+
const data = {
|
|
1220
|
+
conversation_status: 'open',
|
|
1221
|
+
}
|
|
1222
|
+
return await patchDataHandler(url, data)
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* @param {int} commentId
|
|
1227
|
+
* @param {string} comment
|
|
1228
|
+
* @returns {Promise<*|null>}
|
|
1229
|
+
*/
|
|
1230
|
+
export async function editComment(commentId, comment) {
|
|
1231
|
+
const url = `/api/content/v1/comments/${commentId}`
|
|
1232
|
+
const data = {
|
|
1233
|
+
comment: comment,
|
|
1234
|
+
}
|
|
1235
|
+
return await patchDataHandler(url, data)
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
1238
|
function fetchAbsolute(url, params) {
|
|
1239
1239
|
if (globalConfig.railcontentConfig.authToken) {
|
|
1240
1240
|
params.headers['Authorization'] = `Bearer ${globalConfig.railcontentConfig.authToken}`
|
|
@@ -109,6 +109,25 @@ export async function rankItems(brand, content_ids) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
export async function recommendations(brand, {
|
|
113
|
+
page = 1,
|
|
114
|
+
limit = 10,
|
|
115
|
+
} = {}) {
|
|
116
|
+
let data = {
|
|
117
|
+
'brand': brand,
|
|
118
|
+
'user_id': globalConfig.railcontentConfig.userId,
|
|
119
|
+
'num_recommendations': limit
|
|
120
|
+
}
|
|
121
|
+
const url = `/recommendations/`
|
|
122
|
+
try {
|
|
123
|
+
const response = await fetchHandler(url, 'POST', data)
|
|
124
|
+
return response['recommendations']
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Fetch error:', error)
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
async function fetchHandler(url, method = 'get', body = null) {
|
|
113
132
|
|
|
114
133
|
let headers = {
|