diff --git a/package.json b/package.json index 23315ce..840defc 100755 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "axios": "^1.4.0", "js-cookie": "^3.0.5", "jsonwebtoken": "^9.0.0", - "jwt-decode": "^3.1.2", + "jwt-decode": "^4.0.0", "luxon": "^3.3.0", "socket.io-client": "^4.6.1" }, diff --git a/src/index.js b/src/index.js index c482296..ee1ef5e 100755 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,13 @@ if (globalThis.isServerMode) { } } +/** + * Creates websockets by disconnecting and removing listeners from existing instances, + * then creating new instances for each websocket in the remote.websockets array. + * Registers event listeners for connection, disconnection, reconnection, error, and any other events. + * + * @return {Promise} A promise that resolves when all websockets have been created and event listeners have been registered. + */ export async function createWebsockets() { if (!remote.websockets) { return false @@ -94,6 +101,11 @@ export async function createWebsockets() { } } +/** + * Disconnects all websocket instances by calling the `disconnect` method on each instance. + * + * @return {Promise} A promise that resolves when all websocket instances have been disconnected. + */ export async function disconnectWebsockets() { const instances = globalThis.__comty_shared_state.sockets @@ -104,6 +116,11 @@ export async function disconnectWebsockets() { } } +/** + * Reconnects all websocket instances by disconnecting and reconnecting them with the current token. + * + * @return {Promise} A promise that resolves when all websocket instances have been reconnected. + */ export async function reconnectWebsockets() { const instances = globalThis.__comty_shared_state.sockets @@ -121,6 +138,11 @@ export async function reconnectWebsockets() { } } +/** + * Reauthenticates all websocket instances with the current token. If a websocket instance is not connected, it connects to the server. If it is connected, it emits an "auth:reauth" event with the current token. + * + * @return {Promise} Promise that resolves when all websocket instances have been reauthenticated. + */ export async function reauthenticateWebsockets() { const instances = globalThis.__comty_shared_state.sockets @@ -139,14 +161,21 @@ export async function reauthenticateWebsockets() { } } +/** + * Create a client with the specified access key, private key, and websocket enablement. + * + * @param {Object} options - Optional parameters for accessKey, privateKey, and enableWs + * @return {Object} sharedState - Object containing eventBus, mainOrigin, baseRequest, sockets, rest, and version + */ export function createClient({ accessKey = null, privateKey = null, enableWs = false, + origin = remote.origin, } = {}) { const sharedState = globalThis.__comty_shared_state = { eventBus: new EventEmitter(), - mainOrigin: remote.origin, + mainOrigin: origin, baseRequest: null, sockets: new Map(), rest: null, @@ -160,7 +189,7 @@ export function createClient({ } sharedState.baseRequest = axios.create({ - baseURL: remote.origin, + baseURL: origin, headers: { "Content-Type": "application/json", } diff --git a/src/models/api/index.js b/src/models/api/index.js new file mode 100644 index 0000000..f5b9e13 --- /dev/null +++ b/src/models/api/index.js @@ -0,0 +1,78 @@ +import request from "../../request" + +export default class API { + /** + * Retrieves the server keys associated with the current user. + * + * @return {object} The server keys data + */ + static async getMyServerKeys() { + const response = await request({ + method: "GET", + url: "/server-keys/my", + }) + + return response.data + } + + /** + * Creates a new server key. + * + * @param {object} options - Options for the new server key. + * @param {string} options.name - The name of the server key. + * @param {string} options.description - The description of the server key. + * @param {string} options.access - The access level of the server key. + * @return {object} The newly created server key data. + */ + static async createNewServerKey({ + name, + description, + access, + } = {}) { + const response = await request({ + method: "POST", + url: "/server-keys/generate", + data: { + name, + description, + access + } + }) + + return response.data + } + + /** + * Regenerates a secret token for a server key. + * + * @param {object} options - Options for regenerating the secret token. + * @param {string} access_id - The ID of the server key to regenerate the secret token for. + * @return {object} The regenerated secret token data. + */ + static async regenerateSecretToken(access_id) { + const response = await request({ + method: "POST", + url: `/server-keys/${access_id}/regenerate`, + }) + + return response.data + } + + /** + * Deletes a server key by its access ID. + * + * @param {string} access_id - The ID of the server key to delete. + * @return {Promise} - A promise that resolves to the response data. + */ + static async deleteServerKey(access_id) { + const response = await request({ + method: "DELETE", + url: `/server-keys/${access_id}`, + data: { + access_id + } + }) + + return response.data + } +} \ No newline at end of file diff --git a/src/models/auth/index.js b/src/models/auth/index.js index 9af533e..7e0b9ff 100755 --- a/src/models/auth/index.js +++ b/src/models/auth/index.js @@ -2,15 +2,18 @@ import request from "../../request" import SessionModel from "../session" export default class AuthModel { - static login = async (payload, callback) => { + /** + * Async function to handle the login process. + * + * @param {Object} payload - The payload containing username, password, and MFA code. + * @param {Function} callback - Optional callback function to handle further actions. + * @return {Object|boolean} The response data if login successful, false if MFA is required. + */ + static async login(payload, callback) { const response = await request({ method: "post", url: "/auth", - data: { - username: payload.username, - password: payload.password, - mfa_code: payload.mfa_code, - }, + data: payload, }) if (response.data.mfa_required) { @@ -40,7 +43,12 @@ export default class AuthModel { return response.data } - static logout = async () => { + /** + * Asynchronously logs out the user by destroying the current session and emitting an event for successful logout. + * + * @return {Promise} A Promise that resolves after the logout process is completed. + */ + static async logout() { await SessionModel.destroyCurrentSession() SessionModel.removeToken() @@ -48,7 +56,18 @@ export default class AuthModel { __comty_shared_state.eventBus.emit("auth:logout_success") } - static register = async (payload) => { + /** + * Registers a new user with the provided payload. + * + * @param {Object} payload - The payload containing the user's information. + * @param {string} payload.username - The username of the user. + * @param {string} payload.password - The password of the user. + * @param {string} payload.email - The email of the user. + * @param {boolean} payload.tos - The acceptance of the terms of service. + * @return {Promise} A Promise that resolves with the response data if the registration is successful, or false if there was an error. + * @throws {Error} Throws an error if the registration fails. + */ + static async register(payload) { const { username, password, email, tos } = payload const response = await request({ @@ -73,7 +92,14 @@ export default class AuthModel { return response } - static usernameValidation = async (username) => { + /** + * Validates the existence of a username by making a GET request to the `/auth/{username}/exists` endpoint. + * + * @param {string} username - The username to validate. + * @return {Promise} A Promise that resolves with the response data if the validation is successful, + * or false if there was an error. Throws an error if the validation fails. + */ + static async usernameValidation(username) { const response = await request({ method: "get", url: `/auth/${username}/exists`, @@ -90,7 +116,15 @@ export default class AuthModel { return response.data } - static availability = async (payload) => { + /** + * Retrieves the availability of a username and email by making a GET request to the `/availability` endpoint. + * + * @param {Object} payload - The payload containing the username and email. + * @param {string} payload.username - The username to check availability for. + * @param {string} payload.email - The email to check availability for. + * @return {Promise} A Promise that resolves with the availability data if successful, or false if an error occurred. + */ + static async availability(payload) { const { username, email } = payload const response = await request({ @@ -109,7 +143,15 @@ export default class AuthModel { return response.data } - static changePassword = async (payload) => { + /** + * A function to change the user's password. + * + * @param {Object} payload - An object containing the currentPassword and newPassword. + * @param {string} payload.currentPassword - The current password of the user. + * @param {string} payload.newPassword - The new password to set for the user. + * @return {Promise} The data response after changing the password. + */ + static async changePassword(payload) { const { currentPassword, newPassword } = payload const { data } = await request({ diff --git a/src/models/follows/index.js b/src/models/follows/index.js index 007bfe2..4709ac6 100755 --- a/src/models/follows/index.js +++ b/src/models/follows/index.js @@ -2,7 +2,14 @@ import { SessionModel } from "../../models" import request from "../../request" export default class FollowsModel { - static imFollowing = async (user_id) => { + /** + * Checks if the current user is following the specified user. + * + * @param {string} user_id - The ID of the user to check if the current user is following. + * @return {Promise} A promise that resolves with the response data indicating if the current user is following the specified user. + * @throws {Error} If the user_id parameter is not provided. + */ + static async imFollowing(user_id) { if (!user_id) { throw new Error("user_id is required") } @@ -15,9 +22,15 @@ export default class FollowsModel { return response.data } - static getFollowers = async (user_id, fetchData) => { + /** + * Retrieves the list of followers for a given user. + * + * @param {string} user_id - The ID of the user. If not provided, the current user ID will be used. + * @param {boolean} fetchData - Whether to fetch additional data for each follower. Defaults to false. + * @return {Promise} A promise that resolves with the list of followers and their data. + */ + static async getFollowers(user_id, fetchData) { if (!user_id) { - // set current user_id user_id = SessionModel.user_id } @@ -32,7 +45,13 @@ export default class FollowsModel { return response.data } - static toggleFollow = async ({ user_id }) => { + /** + * Toggles the follow status for a user. + * + * @param {Object} user_id - The ID of the user to toggle follow status. + * @return {Promise} The response data after toggling follow status. + */ + static async toggleFollow({ user_id }) { if (!user_id) { throw new Error("user_id is required") } diff --git a/src/models/nfc/index.js b/src/models/nfc/index.js index e5a2093..d1ec240 100755 --- a/src/models/nfc/index.js +++ b/src/models/nfc/index.js @@ -1,15 +1,26 @@ import request from "../../request" export default class NFCModel { + /** + * Retrieves the list of tags owned by the current user. + * + * @return {Promise} A promise that resolves with the data of the tags. + */ static async getOwnTags() { const { data } = await request({ method: "GET", - url: `/nfc/tags` + url: `/nfc/tag/my` }) return data } + /** + * Retrieves a tag by its ID. + * + * @param {type} id - The ID of the tag to retrieve. + * @return {type} The data of the retrieved tag. + */ static async getTagById(id) { if (!id) { throw new Error("ID is required") @@ -17,12 +28,19 @@ export default class NFCModel { const { data } = await request({ method: "GET", - url: `/nfc/tags/${id}` + url: `/nfc/tag/id/${id}` }) return data } + /** + * Retrieves a tag by its serial number. + * + * @param {string} serial - The serial number of the tag to retrieve. + * @throws {Error} If the serial number is not provided. + * @return {Promise} A promise that resolves with the data of the tag. + */ static async getTagBySerial(serial) { if (!serial) { throw new Error("Serial is required") @@ -36,6 +54,14 @@ export default class NFCModel { return data } + /** + * Registers a tag with the given serial number and payload. + * + * @param {string} serial - The serial number of the tag. + * @param {Object} payload - The payload data for the tag. + * @throws {Error} If the serial or payload is not provided. + * @return {Promise} The data of the registered tag. + */ static async registerTag(serial, payload) { if (!serial) { throw new Error("Serial is required") @@ -47,10 +73,23 @@ export default class NFCModel { const { data } = await request({ method: "POST", - url: `/nfc/tag/${serial}`, + url: `/nfc/tag/register/${serial}`, data: payload }) return data } + + static async deleteTag(id) { + if (!id) { + throw new Error("ID is required") + } + + const { data } = await request({ + method: "DELETE", + url: `/nfc/tag/id/${id}` + }) + + return data + } } \ No newline at end of file diff --git a/src/models/post/index.js b/src/models/post/index.js index 08003f9..e74a626 100755 --- a/src/models/post/index.js +++ b/src/models/post/index.js @@ -2,15 +2,30 @@ import request from "../../request" import Settings from "../../helpers/withSettings" export default class Post { + /** + * Retrieves the maximum length allowed for the post text. + * + * @return {number} The maximum length allowed for the post text. + */ static get maxPostTextLength() { return 3200 } + /** + * Returns the maximum length allowed for a comment. + * + * @return {number} The maximum length allowed for a comment. + */ static get maxCommentLength() { return 1200 } - static getPostingPolicy = async () => { + /** + * Retrieves the posting policy from the server. + * + * @return {Promise} The posting policy data. + */ + static async getPostingPolicy() { const { data } = await request({ method: "GET", url: "/posting_policy", @@ -19,7 +34,15 @@ export default class Post { return data } - static getPost = async ({ post_id }) => { + /** + * Retrieves the data of a post by its ID. + * + * @param {Object} options - The options for retrieving the post. + * @param {string} options.post_id - The ID of the post to retrieve. + * @throws {Error} If the post_id is not provided. + * @return {Promise} The data of the post. + */ + static async post({ post_id }) { if (!post_id) { throw new Error("Post ID is required") } @@ -32,6 +55,18 @@ export default class Post { return data } + static getPost = Post.post + + /** + * Retrieves the replies of a post by its ID. + * + * @param {Object} options - The options for retrieving the replies. + * @param {string} options.post_id - The ID of the post to retrieve replies for. + * @param {number} [options.trim=0] - The number of characters to trim the reply content. + * @param {number} [options.limit=Settings.get("feed_max_fetch")] - The maximum number of replies to fetch. + * @throws {Error} If the post_id is not provided. + * @return {Promise} The data of the replies. + */ static async replies({ post_id, trim, limit }) { if (!post_id) { throw new Error("Post ID is required") @@ -49,7 +84,15 @@ export default class Post { return data } - static getSavedPosts = async ({ trim, limit }) => { + /** + * Retrieves the saved posts with optional trimming and limiting. + * + * @param {Object} options - The options for retrieving the saved posts. + * @param {number} [options.trim=0] - The number of posts to trim from the result. + * @param {number} [options.limit=Settings.get("feed_max_fetch")] - The maximum number of posts to fetch. + * @return {Promise} The data of the saved posts. + */ + static async getSavedPosts({ trim, limit }) { const { data } = await request({ method: "GET", url: `/posts/saved`, @@ -62,7 +105,14 @@ export default class Post { return data } - static getLikedPosts = async ({ trim, limit }) => { + /** + * Retrieves the liked posts with optional trimming and limiting. + * + * @param {number} trim - The number of characters to trim the post content. + * @param {number} limit - The maximum number of liked posts to fetch. + * @return {Promise} The data of the liked posts. + */ + static async getLikedPosts({ trim, limit }) { const { data } = await request({ method: "GET", url: `/posts/liked`, @@ -75,7 +125,16 @@ export default class Post { return data } - static getUserPosts = async ({ user_id, trim, limit }) => { + /** + * Retrieves the posts of a user with optional trimming and limiting. + * + * @param {Object} options - The options for retrieving the user's posts. + * @param {string} options.user_id - The ID of the user whose posts to retrieve. If not provided, the current user's ID will be used. + * @param {number} [options.trim=0] - The number of characters to trim the post content. + * @param {number} [options.limit=Settings.get("feed_max_fetch")] - The maximum number of posts to fetch. + * @return {Promise} The data of the user's posts. + */ + static async getUserPosts({ user_id, trim, limit }) { if (!user_id) { // use current user_id user_id = app.userData?._id @@ -93,7 +152,15 @@ export default class Post { return data } - static toggleLike = async ({ post_id }) => { + /** + * Toggles the like status of a post. + * + * @param {Object} options - The options for toggling the like status. + * @param {string} options.post_id - The ID of the post to toggle the like status. + * @throws {Error} If the post_id is not provided. + * @return {Promise} The response data after toggling the like status. + */ + static async toggleLike({ post_id }) { if (!post_id) { throw new Error("Post ID is required") } @@ -106,7 +173,13 @@ export default class Post { return data } - static toggleSave = async ({ post_id }) => { + /** + * Toggles the save status of a post. + * + * @param {string} post_id - The ID of the post to toggle the save status. + * @return {Promise} The response data after toggling the save status. + */ + static async toggleSave({ post_id }) { if (!post_id) { throw new Error("Post ID is required") } @@ -119,7 +192,13 @@ export default class Post { return data } - static create = async (payload) => { + /** + * Creates a new post with the given payload. + * + * @param {Object} payload - The data to create the post with. + * @return {Promise} The response data after creating the post. + */ + static async create(payload) { const { data } = await request({ method: "POST", url: `/posts/new`, @@ -129,7 +208,17 @@ export default class Post { return data } - static update = async (post_id, update) => { + static createPost = Post.create + + /** + * Updates a post with the given post ID and update payload. + * + * @param {string} post_id - The ID of the post to update. + * @param {Object} update - The data to update the post with. + * @throws {Error} If the post_id is not provided. + * @return {Promise} The response data after updating the post. + */ + static async update(post_id, update) { if (!post_id) { throw new Error("Post ID is required") } @@ -143,7 +232,15 @@ export default class Post { return data } - static deletePost = async ({ post_id }) => { + static updatePost = Post.update + + /** + * Deletes a post with the given post ID. + * + * @param {string} post_id - The ID of the post to delete. + * @return {Object} The response data after deleting the post. + */ + static async delete({ post_id }) { if (!post_id) { throw new Error("Post ID is required") } @@ -155,4 +252,6 @@ export default class Post { return data } + + static deletePost = Post.delete } \ No newline at end of file diff --git a/src/models/search/index.js b/src/models/search/index.js index ac91693..b7551fb 100755 --- a/src/models/search/index.js +++ b/src/models/search/index.js @@ -1,7 +1,14 @@ import request from "../../request" export default class Search { - static search = async (keywords, params = {}) => { + /** + * Performs a search using the provided keywords and optional parameters. + * + * @param {string} keywords - The keywords to search for. + * @param {Object} [params={}] - Optional parameters for the search. + * @return {Promise} A promise that resolves with the search results. + */ + static async search(keywords, params = {}) { const { data } = await request({ method: "GET", url: `/search`, @@ -14,6 +21,12 @@ export default class Search { return data } + /** + * Performs a quick search using the provided parameters. + * + * @param {Object} params - The parameters for the search. + * @return {Promise} A promise that resolves with the search results data. + */ static async quickSearch(params) { const response = await request({ method: "GET", diff --git a/src/models/session/index.js b/src/models/session/index.js index e1bdab0..98ee1c2 100755 --- a/src/models/session/index.js +++ b/src/models/session/index.js @@ -1,4 +1,4 @@ -import jwt_decode from "jwt-decode" +import { jwtDecode } from "jwt-decode" import request from "../../request" import Storage from "../../helpers/withStorage" @@ -6,45 +6,97 @@ export default class Session { static storageTokenKey = "token" static storageRefreshTokenKey = "refreshToken" + /** + * Retrieves the token from the storage engine. + * + * @return {type} description of return value + */ static get token() { return Storage.engine.get(this.storageTokenKey) } + /** + * Sets the token in the storage engine. + * + * @param {string} token - The token to be set. + * @return {Promise} A promise that resolves when the token is successfully set. + */ static set token(token) { return Storage.engine.set(this.storageTokenKey, token) } + /** + * Retrieves the refresh token from the storage engine. + * + * @return {string} The refresh token stored in the storage engine. + */ static get refreshToken() { return Storage.engine.get(this.storageRefreshTokenKey) } + /** + * Sets the refresh token in the storage engine. + * + * @param {string} token - The refresh token to be set. + * @return {Promise} A promise that resolves when the refresh token is successfully set. + */ static set refreshToken(token) { return Storage.engine.set(this.storageRefreshTokenKey, token) } + /** + * Retrieves the roles from the decoded token object. + * + * @return {Array|undefined} The roles if they exist, otherwise undefined. + */ static get roles() { return this.getDecodedToken()?.roles } + /** + * Retrieves the user ID from the decoded token object. + * + * @return {string|undefined} The user ID if it exists, otherwise undefined. + */ static get user_id() { return this.getDecodedToken()?.user_id } + /** + * Retrieves the session UUID from the decoded token object. + * + * @return {string} The session UUID if it exists, otherwise undefined. + */ static get session_uuid() { return this.getDecodedToken()?.session_uuid } - static getDecodedToken = () => { + /** + * Retrieves the decoded token from the session storage. + * + * @return {Object|null} The decoded token object if it exists, otherwise null. + */ + static getDecodedToken() { const token = this.token - return token && jwt_decode(token) + return token && jwtDecode(token) } + /** + * Removes the token from the storage engine. + * + * @return {Promise} A promise that resolves when the token is successfully removed. + */ static removeToken() { return Storage.engine.remove(Session.storageTokenKey) } - static getAllSessions = async () => { + /** + * Retrieves all sessions from the server. + * + * @return {Promise} The data of all sessions. + */ + static async getAllSessions() { const response = await request({ method: "get", url: "/sessions/all" @@ -53,7 +105,12 @@ export default class Session { return response.data } - static getCurrentSession = async () => { + /** + * Retrieves the current session from the server. + * + * @return {Promise} The data of the current session. + */ + static async getCurrentSession() { const response = await request({ method: "get", url: "/sessions/current" @@ -62,7 +119,12 @@ export default class Session { return response.data } - static getTokenValidation = async () => { + /** + * Retrieves the token validation data from the server. + * + * @return {Promise} The token validation data. + */ + static async getTokenValidation() { const session = await Session.token const response = await request({ @@ -76,7 +138,12 @@ export default class Session { return response.data } - static destroyCurrentSession = async () => { + /** + * Destroys the current session by deleting it from the server. + * + * @return {Promise} The response data from the server after deleting the session. + */ + static async destroyCurrentSession() { const token = await Session.token const session = await Session.getDecodedToken() @@ -100,16 +167,16 @@ export default class Session { return response.data } - static destroyAllSessions = async () => { + static async destroyAllSessions() { throw new Error("Not implemented") } - // alias for validateToken method - static validSession = async (token) => { - return await Session.validateToken(token) - } - - static isCurrentTokenValid = async () => { + /** + * Retrieves the validity of the current token. + * + * @return {boolean} The validity status of the current token. + */ + static async isCurrentTokenValid() { const health = await Session.getTokenValidation() return health.valid