419 lines
14 KiB
JavaScript
419 lines
14 KiB
JavaScript
|
|
const baseUrl = "https://emiapi.reynafamily.com";
|
|
//const baseUrl = "http://localhost:3000";
|
|
const requestErrorCooldownMs = 30000;
|
|
const profileFailureCooldownMs = 60000;
|
|
const recentRequestErrors = {};
|
|
const failedProfileCache = {};
|
|
|
|
const normalizePath = (path = "") => {
|
|
return path.replace(/[a-f0-9]{24}/gi, ":id");
|
|
};
|
|
|
|
const logRequestErrorOnce = (key, message) => {
|
|
const now = Date.now();
|
|
if (recentRequestErrors[key] && now - recentRequestErrors[key] < requestErrorCooldownMs) return;
|
|
recentRequestErrors[key] = now;
|
|
console.error(message);
|
|
};
|
|
|
|
const getFallbackForPath = (path = "") => {
|
|
if (path.startsWith("/post/") || path === "/post/") return [];
|
|
return {};
|
|
};
|
|
|
|
const parseJsonResponse = async (response, path = "", fallback) => {
|
|
const text = await response.text();
|
|
if (!text) return fallback;
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (error) {
|
|
const normalizedPath = normalizePath(path);
|
|
logRequestErrorOnce(`invalid-json:${normalizedPath}`, `Invalid JSON from ${normalizedPath}: ${text.slice(0, 200)}`);
|
|
return fallback;
|
|
}
|
|
};
|
|
|
|
let getCall = async (path = "", params = {}) => {
|
|
let queryParams = "?";
|
|
Object.keys(params).forEach(p => {
|
|
queryParams += p + "=" + params[p] + "&"
|
|
});
|
|
let localBaseUrl = global.baseUrl ?? baseUrl;
|
|
const fallback = getFallbackForPath(path);
|
|
return fetch(localBaseUrl + path + queryParams, {
|
|
method: 'GET',
|
|
mode: 'cors',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
}).then(async (response) => {
|
|
const normalizedPath = normalizePath(path);
|
|
if (!response.ok) {
|
|
logRequestErrorOnce(`GET:${normalizedPath}:${response.status}`, `GET ${normalizedPath} failed: ${response.status}`);
|
|
return fallback;
|
|
}
|
|
return parseJsonResponse(response, path, fallback);
|
|
}).catch((error) => {
|
|
const normalizedPath = normalizePath(path);
|
|
logRequestErrorOnce(`GET:${normalizedPath}:network`, `GET ${normalizedPath} network error: ${error?.message || error}`);
|
|
return fallback;
|
|
})
|
|
}
|
|
|
|
let deleteCall = async (path = "", params = {}) => {
|
|
let queryParams = "?";
|
|
Object.keys(params).forEach(p => {
|
|
queryParams += p + "=" + params[p] + "&"
|
|
});
|
|
let localBaseUrl = global.baseUrl ?? baseUrl;
|
|
const fallback = {};
|
|
return fetch(localBaseUrl + path + queryParams, {
|
|
method: 'DELETE',
|
|
mode: 'cors',
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
}).then(async (response) => {
|
|
const normalizedPath = normalizePath(path);
|
|
if (!response.ok) {
|
|
logRequestErrorOnce(`DELETE:${normalizedPath}:${response.status}`, `DELETE ${normalizedPath} failed: ${response.status}`);
|
|
return fallback;
|
|
}
|
|
return parseJsonResponse(response, path, fallback);
|
|
}).catch((error) => {
|
|
const normalizedPath = normalizePath(path);
|
|
logRequestErrorOnce(`DELETE:${normalizedPath}:network`, `DELETE ${normalizedPath} network error: ${error?.message || error}`);
|
|
return fallback;
|
|
})
|
|
}
|
|
|
|
let postCall = async (path, params) => {
|
|
let localBaseUrl = global.baseUrl ?? baseUrl;
|
|
const fallback = {};
|
|
return fetch(localBaseUrl + path, {
|
|
method: 'POST',
|
|
mode: 'cors',
|
|
credentials: 'include',
|
|
body: JSON.stringify(params),
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
}).then(async (response) => {
|
|
const normalizedPath = normalizePath(path);
|
|
if (!response.ok) {
|
|
logRequestErrorOnce(`POST:${normalizedPath}:${response.status}`, `POST ${normalizedPath} failed: ${response.status}`);
|
|
return fallback;
|
|
}
|
|
return parseJsonResponse(response, path, fallback);
|
|
}).catch((error) => {
|
|
const normalizedPath = normalizePath(path);
|
|
logRequestErrorOnce(`POST:${normalizedPath}:network`, `POST ${normalizedPath} network error: ${error?.message || error}`);
|
|
return fallback;
|
|
})
|
|
}
|
|
|
|
let CurrentUserId;
|
|
let CurrentProfile;
|
|
let CurrentProfileData;
|
|
let userNameCache = {}; //save this on localstorage
|
|
let working_on = {}; //Promises
|
|
const emptyProfileData = (id = "") => ({
|
|
_id: id || "",
|
|
profile: {
|
|
firstName: "",
|
|
lastName: "",
|
|
photo: "",
|
|
description: "",
|
|
language: "en",
|
|
},
|
|
data: {},
|
|
following: [],
|
|
});
|
|
let getProfileFromCache = async (id, refresh=false) => {
|
|
if(!id) console.trace();
|
|
const lastFailure = failedProfileCache[id] || 0;
|
|
if (!refresh && lastFailure && Date.now() - lastFailure < profileFailureCooldownMs) {
|
|
return emptyProfileData(id);
|
|
}
|
|
if (userNameCache[id] && !refresh)
|
|
return userNameCache[id];
|
|
if (working_on[id] && !refresh)
|
|
return working_on[id];
|
|
working_on[id] = getCall("/user/" + id).then((profile) => {
|
|
const hasRealProfile = !!(profile && profile._id);
|
|
const safeProfile = hasRealProfile ? { ...emptyProfileData(id), ...profile, profile: { ...emptyProfileData(id).profile, ...(profile.profile || {}) } } : emptyProfileData(id);
|
|
if (hasRealProfile) {
|
|
userNameCache[id] = safeProfile;
|
|
delete failedProfileCache[id];
|
|
} else {
|
|
failedProfileCache[id] = Date.now();
|
|
}
|
|
delete working_on[id];
|
|
return safeProfile;
|
|
}).catch(() => {
|
|
failedProfileCache[id] = Date.now();
|
|
delete working_on[id];
|
|
return emptyProfileData(id);
|
|
});
|
|
return working_on[id];
|
|
}
|
|
|
|
const API = {
|
|
isLoggedIn: async () => {
|
|
return getCall().then((data) => {
|
|
//console.log("isLoggedIn", data)
|
|
if (data && data.status && data.status === 'ok') {
|
|
CurrentUserId = data.userInfo._id;
|
|
CurrentProfile = data.profileInfo._id;
|
|
return true;
|
|
}
|
|
CurrentUserId = '';
|
|
CurrentProfile = '';
|
|
CurrentProfileData = undefined;
|
|
return false;
|
|
})
|
|
},
|
|
logIn: async (username, password) => {
|
|
return postCall("/login", {
|
|
username,
|
|
password
|
|
}).then((data) => {
|
|
if (data && data.status === "ok") {
|
|
CurrentUserId = data.user_sid;
|
|
CurrentProfile = data.profile_id;
|
|
}
|
|
return data;
|
|
})
|
|
},
|
|
async logout(){
|
|
console.log("Logging out...")
|
|
return getCall("/logout").then(()=>{
|
|
CurrentUserId = '';
|
|
CurrentProfile = '';
|
|
CurrentProfileData = undefined;
|
|
});
|
|
},
|
|
async signup(username, password, email, profile){
|
|
return postCall("/signup", {
|
|
username,
|
|
password,
|
|
email,
|
|
profile
|
|
}).then((data) => {
|
|
//console.log(data)
|
|
if (data && data.status === "ok") {
|
|
CurrentUserId = data.user_sid;
|
|
CurrentProfile = data.profile_id;
|
|
}
|
|
return data;
|
|
});
|
|
},
|
|
async subscribe(subscription){
|
|
return postCall("/subscribe", subscription);
|
|
},
|
|
resetPassword(email){
|
|
return postCall("/resetPassword", {username: email});
|
|
},
|
|
changePassword(newPassword){
|
|
return postCall("/resetPassword", {password: newPassword});
|
|
},
|
|
currentUserId() {
|
|
if (!CurrentUserId) this.isLoggedIn(); //replace with cookie
|
|
return CurrentUserId;
|
|
},
|
|
addPaymentCard(cardInfo){
|
|
return postCall("/payment/card", {cardInfo});
|
|
},
|
|
//Posts
|
|
getPosts(userid) {
|
|
if (userid) return getCall("/post/usr/" + userid).then((data) => Array.isArray(data) ? data : []);
|
|
return getCall("/post/").then((data) => Array.isArray(data) ? data : []);
|
|
},
|
|
getPostsByTag(tag) {
|
|
return getCall("/post/tag/" + tag);
|
|
},
|
|
getPostsWithTag(userid, tag = "images") {
|
|
if (userid) return getCall("/post/usr/" + userid + "/" + tag);
|
|
return getCall("/post/" + tag);
|
|
},
|
|
getPost(postid) {
|
|
return getCall("/post/" + postid);
|
|
},
|
|
getVideo(videoId) {
|
|
return getCall("/post/video/" + videoId);
|
|
},
|
|
registerToken(token) {
|
|
return postCall("/token/", {token});
|
|
},
|
|
deletePost(postid){
|
|
return deleteCall("/post/" + postid);
|
|
},
|
|
updatePost(post){
|
|
return postCall("/post/" + post._id, {content: post.content});
|
|
},
|
|
newPost(content, toProfile, isNews) {
|
|
//Content is expected to be a string.
|
|
let params = { content };
|
|
if(isNews) params.nonOrganicType = "News"
|
|
if (toProfile && CurrentUserId !== toProfile)
|
|
params.toProfile = toProfile;
|
|
return postCall("/post/", params);
|
|
},
|
|
newPostReaction(postid) {
|
|
return postCall("/post/react", { postid });
|
|
},
|
|
removePostReaction(postid) {
|
|
return postCall("/post/unreact", { postid });
|
|
},
|
|
newPostBookmark(postid) {
|
|
return postCall("/post/bookmark", { postid });
|
|
},
|
|
removePostBookmark(postid) {
|
|
return postCall("/post/unbookmark", { postid });
|
|
},
|
|
newPostComment(postid, content) {
|
|
return postCall("/post/comment/", { postid, content });
|
|
},
|
|
newCommentReaction(postid, commentDate) {
|
|
return postCall("/post/comment/react", { postid, commentDate });
|
|
},
|
|
removeCommentReaction(postid, commentDate) {
|
|
return postCall("/post/comment/unreact", { postid, commentDate });
|
|
},
|
|
//Invitations
|
|
newInvitation(name, email){
|
|
return postCall("/user/invite", { name, email });
|
|
},
|
|
getInvitation(email){
|
|
return getCall("/invite/" + email );
|
|
},
|
|
//Profiles
|
|
async getMe() {
|
|
if (!CurrentProfileData)
|
|
if(!CurrentProfile) await this.isLoggedIn();
|
|
CurrentProfileData = await this.getUserProfile(CurrentProfile, true);
|
|
return CurrentProfileData;
|
|
},
|
|
getMyProfiles() {
|
|
return getCall("/user/mine");
|
|
},
|
|
getMyProfile() {
|
|
return getCall("/user/" + CurrentProfile);
|
|
},
|
|
updateMyProfile(profile, data) {
|
|
return postCall("/user/myProfile", {profile, data});
|
|
},
|
|
searchProfiles(query){
|
|
return getCall("/user/search", query ? {query} : {}).then((data)=>{
|
|
if(data.status == "ok"){
|
|
data.profiles.forEach((p)=>{
|
|
userNameCache[p._id] = p;
|
|
});
|
|
return data
|
|
}
|
|
});
|
|
},
|
|
changeProfile(profileid){
|
|
return postCall("/changeProfile", {profileid}).then((profile) =>{
|
|
CurrentProfile = profileid;
|
|
CurrentProfileData = profile;
|
|
return profile;
|
|
});
|
|
},
|
|
getUserProfile(profileid, refresh=false) {
|
|
return getProfileFromCache(profileid, refresh).then((profile) => profile || emptyProfileData(profileid));
|
|
},
|
|
deleteProfile(profileid){
|
|
return deleteCall("/user/" + profileid);
|
|
},
|
|
currentProfileId() {
|
|
if (!CurrentProfile) this.isLoggedIn(); //replace with cookie
|
|
return CurrentProfile;
|
|
},
|
|
followProfile(profileid){
|
|
return getCall("/user/" + profileid + "/follow");
|
|
},
|
|
unfollowProfile(profileid){
|
|
return getCall("/user/" + profileid + "/unfollow");
|
|
},
|
|
setDataValue(key, value){
|
|
return postCall("/user/setData", {key, value});
|
|
},
|
|
//Groups
|
|
newGroup(title, subtitle, description, isPrivate=false, isCourse=false, photo='') {
|
|
return postCall("/user/groups", {
|
|
profile: {
|
|
firstName: title,
|
|
lastName: subtitle,
|
|
description,
|
|
photo
|
|
},
|
|
isPrivate,
|
|
isCourse
|
|
});
|
|
},
|
|
getRecentGroups() {
|
|
return getCall("/user/groups");
|
|
},
|
|
getFollowingGroups() {
|
|
return getCall("/user/groups/following");
|
|
},
|
|
searchGroups(query){
|
|
return getCall("/user/groups/search", query ? {query} : {}).then((data)=>{
|
|
if(data.status == "ok"){
|
|
data.groups.forEach((p)=>{
|
|
userNameCache[p._id] = p;
|
|
});
|
|
return data
|
|
}
|
|
});
|
|
},
|
|
getCourses() {
|
|
return getCall("/user/courses");
|
|
},
|
|
getRecentCourses() {
|
|
return getCall("/posts/course/recent");
|
|
},
|
|
searchCourses(query){
|
|
return getCall("/user/groups/search", query ? {query, courses: true} : {courses: true}).then((data)=>{
|
|
if(data.status == "ok"){
|
|
data.groups.forEach((p)=>{
|
|
userNameCache[p._id] = p;
|
|
});
|
|
return data
|
|
}
|
|
});
|
|
},
|
|
getGroup(groupid) {
|
|
return getCall("/user/groups/" + groupid);
|
|
},
|
|
async subscribeToGroup(groupid){
|
|
delete userNameCache[groupid];
|
|
return await getCall("/user/groups/" + groupid + "/subscribe");
|
|
},
|
|
acceptGroupRequest(profileid, groupid){
|
|
delete userNameCache[groupid];
|
|
return postCall("/user/groups/accept", {profileid, groupid});
|
|
},
|
|
rejectGroupRequest(profileid, groupid){
|
|
delete userNameCache[groupid];
|
|
return postCall("/user/groups/reject", {profileid, groupid});
|
|
},
|
|
unsubscribeToGroup(groupid){
|
|
delete userNameCache[groupid];
|
|
return getCall("/user/groups/" + groupid + "/unsubscribe");
|
|
},
|
|
//Payments
|
|
paymentIntent(userid, price, description){
|
|
return postCall("/payments/intent", {userid, price, description});
|
|
},
|
|
paymentRegister(userid, result){
|
|
return postCall("/payments/register/", {userid, result});
|
|
}
|
|
}
|
|
|
|
export default API;
|