mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 18:44:16 +00:00
create new public package comty.js
This commit is contained in:
parent
d306297ac1
commit
e6cc61d85d
14
packages/comty.js/package.json
Normal file
14
packages/comty.js/package.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "comty.js",
|
||||
"version": "0.1.0",
|
||||
"main": "./dist/index.js",
|
||||
"author": "RageStudio <support@ragestudio.net>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@foxify/events": "^2.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"linebridge": "^0.15.12"
|
||||
}
|
||||
}
|
53
packages/comty.js/src/handlers/measurePing.js
Normal file
53
packages/comty.js/src/handlers/measurePing.js
Normal file
@ -0,0 +1,53 @@
|
||||
import request from "./request"
|
||||
|
||||
export default async () => {
|
||||
const timings = {}
|
||||
|
||||
const promises = [
|
||||
new Promise(async (resolve) => {
|
||||
const start = Date.now()
|
||||
|
||||
request({
|
||||
method: "GET",
|
||||
url: "/ping",
|
||||
})
|
||||
.then(() => {
|
||||
// set http timing in ms
|
||||
timings.http = Date.now() - start
|
||||
|
||||
resolve()
|
||||
})
|
||||
.catch(() => {
|
||||
timings.http = "failed"
|
||||
resolve()
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
timings.http = "failed"
|
||||
|
||||
resolve()
|
||||
}, 10000)
|
||||
}),
|
||||
new Promise((resolve) => {
|
||||
const start = Date.now()
|
||||
|
||||
__comty_shared_state.wsInstances["default"].on("pong", () => {
|
||||
timings.ws = Date.now() - start
|
||||
|
||||
resolve()
|
||||
})
|
||||
|
||||
__comty_shared_state.wsInstances["default"].emit("ping")
|
||||
|
||||
setTimeout(() => {
|
||||
timings.ws = "failed"
|
||||
|
||||
resolve()
|
||||
}, 10000)
|
||||
})
|
||||
]
|
||||
|
||||
await Promise.all(promises)
|
||||
|
||||
return timings
|
||||
}
|
60
packages/comty.js/src/handlers/request.js
Normal file
60
packages/comty.js/src/handlers/request.js
Normal file
@ -0,0 +1,60 @@
|
||||
import handleBeforeRequest from "../helpers/handleBeforeRequest"
|
||||
import handleAfterRequest from "../helpers/handleAfterRequest"
|
||||
import SessionModel from "../models/session"
|
||||
|
||||
export default async (
|
||||
request = {
|
||||
method: "GET",
|
||||
},
|
||||
...args
|
||||
) => {
|
||||
const instance = request.instance ?? __comty_shared_state.instances.default
|
||||
|
||||
if (!instance) {
|
||||
throw new Error("No instance provided")
|
||||
}
|
||||
|
||||
// handle before request
|
||||
await handleBeforeRequest(request)
|
||||
|
||||
if (typeof request === "string") {
|
||||
request = {
|
||||
url: request,
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof request.headers !== "object") {
|
||||
request.headers = {}
|
||||
}
|
||||
|
||||
let result = null
|
||||
|
||||
const makeRequest = async () => {
|
||||
const sessionToken = await SessionModel.token
|
||||
|
||||
if (sessionToken) {
|
||||
request.headers["Authorization"] = `${globalThis.isServerMode ? "Server" : "Bearer"} ${sessionToken}`
|
||||
} else {
|
||||
console.warn("Making a request with no session token")
|
||||
}
|
||||
|
||||
const _result = await instance(request, ...args)
|
||||
.catch((error) => {
|
||||
return error
|
||||
})
|
||||
|
||||
result = _result
|
||||
}
|
||||
|
||||
await makeRequest()
|
||||
|
||||
// handle after request
|
||||
await handleAfterRequest(result, makeRequest)
|
||||
|
||||
// if error, throw it
|
||||
if (result instanceof Error) {
|
||||
throw result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
34
packages/comty.js/src/helpers/handleAfterRequest.js
Normal file
34
packages/comty.js/src/helpers/handleAfterRequest.js
Normal file
@ -0,0 +1,34 @@
|
||||
import handleRegenerationEvent from "./handleRegenerationEvent"
|
||||
|
||||
export default async (data, callback) => {
|
||||
// handle 401, 403 responses
|
||||
if (data instanceof Error) {
|
||||
if (data.code && (data.code === "ECONNABORTED" || data.code === "ERR_NETWORK")) {
|
||||
console.error(`Request aborted or network error, ignoring`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (data.response.status === 401) {
|
||||
// check if the server issue a refresh token on data
|
||||
if (data.response.data.refreshToken) {
|
||||
console.log(`Session expired, but the server issued a refresh token, handling regeneration event`)
|
||||
|
||||
// handle regeneration event
|
||||
await handleRegenerationEvent(data.response.data.refreshToken)
|
||||
|
||||
return await callback()
|
||||
}
|
||||
|
||||
// check if route is from "session" namespace
|
||||
if (data.config.url.includes("/session")) {
|
||||
return __comty_shared_state.eventBus.emit("session.invalid", "Session expired, but the server did not issue a refresh token")
|
||||
}
|
||||
}
|
||||
|
||||
if (data.response.status === 403) {
|
||||
if (data.config.url.includes("/session")) {
|
||||
return __comty_shared_state.eventBus.emit("session.invalid", "Session not valid or not existent")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
packages/comty.js/src/helpers/handleBeforeRequest.js
Normal file
13
packages/comty.js/src/helpers/handleBeforeRequest.js
Normal file
@ -0,0 +1,13 @@
|
||||
export default async (request) => {
|
||||
if (__comty_shared_state.onExpiredExceptionEvent) {
|
||||
if (__comty_shared_state.excludedExpiredExceptionURL.includes(request.url)) return
|
||||
|
||||
await new Promise((resolve) => {
|
||||
__comty_shared_state.eventBus.once("session.regenerated", () => {
|
||||
console.log(`Session has been regenerated, retrying request`)
|
||||
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
39
packages/comty.js/src/helpers/handleRegenerationEvent.js
Normal file
39
packages/comty.js/src/helpers/handleRegenerationEvent.js
Normal file
@ -0,0 +1,39 @@
|
||||
import SessionModel from "../models/session"
|
||||
import request from "../handlers/request"
|
||||
|
||||
export default async (refreshToken) =>{
|
||||
__comty_shared_state.eventBus.emit("session.expiredExceptionEvent", refreshToken)
|
||||
|
||||
__comty_shared_state.onExpiredExceptionEvent = true
|
||||
|
||||
const expiredToken = await SessionModel.token
|
||||
|
||||
// send request to regenerate token
|
||||
const response = await request({
|
||||
method: "POST",
|
||||
url: "/session/regenerate",
|
||||
data: {
|
||||
expiredToken: expiredToken,
|
||||
refreshToken,
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(`Failed to regenerate token: ${error.message}`)
|
||||
return false
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
return __comty_shared_state.eventBus.emit("session.invalid", "Failed to regenerate token")
|
||||
}
|
||||
|
||||
if (!response.data?.token) {
|
||||
return __comty_shared_state.eventBus.emit("session.invalid", "Failed to regenerate token, invalid server response.")
|
||||
}
|
||||
|
||||
// set new token
|
||||
SessionModel.token = response.data.token
|
||||
|
||||
__comty_shared_state.onExpiredExceptionEvent = false
|
||||
|
||||
// emit event
|
||||
__comty_shared_state.eventBus.emit("session.regenerated")
|
||||
}
|
25
packages/comty.js/src/helpers/withSettings.js
Normal file
25
packages/comty.js/src/helpers/withSettings.js
Normal file
@ -0,0 +1,25 @@
|
||||
export default class Settings {
|
||||
static get = (key) => {
|
||||
if (typeof window === "undefined") {
|
||||
return null
|
||||
}
|
||||
|
||||
return window?.app?.cores?.settings.get(key)
|
||||
}
|
||||
|
||||
static set = (key, value) => {
|
||||
if (typeof window === "undefined") {
|
||||
return null
|
||||
}
|
||||
|
||||
return window?.app?.cores?.settings.set(key, value)
|
||||
}
|
||||
|
||||
static is = (key) => {
|
||||
if (typeof window === "undefined") {
|
||||
return null
|
||||
}
|
||||
|
||||
return window?.app?.cores?.settings.is(key)
|
||||
}
|
||||
}
|
31
packages/comty.js/src/helpers/withStorage.js
Normal file
31
packages/comty.js/src/helpers/withStorage.js
Normal file
@ -0,0 +1,31 @@
|
||||
import jscookies from "js-cookie"
|
||||
|
||||
class InternalStorage {
|
||||
#storage = {}
|
||||
|
||||
get(key) {
|
||||
// get value from storage
|
||||
return this.#storage[key]
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
// storage securely in memory
|
||||
return this.#storage[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
export default class Storage {
|
||||
static get engine() {
|
||||
// check if is running in browser, if is import js-cookie
|
||||
// else use in-memory safe storage
|
||||
if (typeof window !== "undefined") {
|
||||
return jscookies
|
||||
}
|
||||
|
||||
if (!globalThis.__comty_shared_state["_internal_storage"]) {
|
||||
globalThis.__comty_shared_state["_internal_storage"] = new InternalStorage()
|
||||
}
|
||||
|
||||
return globalThis.__comty_shared_state["_internal_storage"]
|
||||
}
|
||||
}
|
32
packages/comty.js/src/hooks/useRequest/index.js
Normal file
32
packages/comty.js/src/hooks/useRequest/index.js
Normal file
@ -0,0 +1,32 @@
|
||||
import React from "react"
|
||||
|
||||
export default (method, ...args) => {
|
||||
if (typeof method !== "function") {
|
||||
throw new Error("useRequest: method must be a function")
|
||||
}
|
||||
|
||||
const [loading, setLoading] = React.useState(true)
|
||||
const [result, setResult] = React.useState(null)
|
||||
const [error, setError] = React.useState(null)
|
||||
|
||||
const makeRequest = (...newArgs) => {
|
||||
method(...newArgs)
|
||||
.then((data) => {
|
||||
setResult(data)
|
||||
setLoading(false)
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err)
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
makeRequest(...args)
|
||||
}, [])
|
||||
|
||||
return [loading, result, error, (...newArgs) => {
|
||||
setLoading(true)
|
||||
makeRequest(...newArgs)
|
||||
}]
|
||||
}
|
103
packages/comty.js/src/index.js
Normal file
103
packages/comty.js/src/index.js
Normal file
@ -0,0 +1,103 @@
|
||||
import EventEmitter from "@foxify/events"
|
||||
|
||||
import axios from "axios"
|
||||
import { io } from "socket.io-client"
|
||||
|
||||
import remotes from "./remotes"
|
||||
|
||||
import request from "./handlers/request"
|
||||
import Storage from "./helpers/withStorage"
|
||||
|
||||
import SessionModel from "./models/session"
|
||||
import { createHandlers } from "./models"
|
||||
|
||||
globalThis.isServerMode = typeof window === "undefined" && typeof global !== "undefined"
|
||||
|
||||
if (globalThis.isServerMode) {
|
||||
const { Buffer } = require("buffer")
|
||||
|
||||
globalThis.b64Decode = (data) => {
|
||||
return Buffer.from(data, "base64").toString("utf-8")
|
||||
}
|
||||
globalThis.b64Encode = (data) => {
|
||||
return Buffer.from(data, "utf-8").toString("base64")
|
||||
}
|
||||
}
|
||||
|
||||
export default function createClient({
|
||||
wsEvents = Object(),
|
||||
useWs = false,
|
||||
accessKey = null,
|
||||
privateKey = null,
|
||||
} = {}) {
|
||||
const sharedState = globalThis.__comty_shared_state = {
|
||||
onExpiredExceptionEvent: false,
|
||||
excludedExpiredExceptionURL: ["/session/regenerate"],
|
||||
eventBus: new EventEmitter(),
|
||||
mainOrigin: remotes.default.origin,
|
||||
instances: Object(),
|
||||
wsInstances: Object(),
|
||||
curl: null,
|
||||
}
|
||||
|
||||
if (globalThis.isServerMode) {
|
||||
sharedState.curl = createHandlers()
|
||||
}
|
||||
|
||||
if (privateKey && accessKey && globalThis.isServerMode) {
|
||||
Storage.engine.set("token", `${accessKey}:${privateKey}`)
|
||||
}
|
||||
|
||||
// create instances for every remote
|
||||
for (const [key, remote] of Object.entries(remotes)) {
|
||||
sharedState.instances[key] = axios.create({
|
||||
baseURL: remote.origin,
|
||||
})
|
||||
|
||||
if (useWs && remote.hasWebsocket) {
|
||||
sharedState.wsInstances[key] = io(remote.wsOrigin ?? remote.origin, {
|
||||
transports: ["websocket"],
|
||||
autoConnect: true,
|
||||
...remote.wsParams ?? {},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// register ws events
|
||||
Object.keys(sharedState.wsInstances).forEach((key) => {
|
||||
const ws = sharedState.wsInstances[key]
|
||||
|
||||
ws.on("connect", () => {
|
||||
console.log(`[WS-API][${key}] Connected`)
|
||||
|
||||
if (remotes[key].needsAuth) {
|
||||
// try to auth
|
||||
ws.emit("authenticate", {
|
||||
token: SessionModel.token,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
ws.on("disconnect", () => {
|
||||
console.log(`[WS-API][${key}] Disconnected`)
|
||||
})
|
||||
|
||||
ws.on("error", (error) => {
|
||||
console.error(`[WS-API][${key}] Error`, error)
|
||||
})
|
||||
|
||||
ws.onAny((event, ...args) => {
|
||||
console.log(`[WS-API][${key}] Event recived`, event, ...args)
|
||||
})
|
||||
|
||||
const customEvents = wsEvents[key]
|
||||
|
||||
if (customEvents) {
|
||||
for (const [eventName, eventHandler] of Object.entries(customEvents)) {
|
||||
ws.on(eventName, eventHandler)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return sharedState
|
||||
}
|
53
packages/comty.js/src/models/auth/index.js
Executable file
53
packages/comty.js/src/models/auth/index.js
Executable file
@ -0,0 +1,53 @@
|
||||
import request from "../../handlers/request"
|
||||
import SessionModel from "../session"
|
||||
|
||||
export default class AuthModel {
|
||||
static login = async (payload) => {
|
||||
const response = await request({
|
||||
method: "post",
|
||||
url: "/auth/login",
|
||||
data: {
|
||||
username: payload.username, //window.btoa(payload.username),
|
||||
password: payload.password, //window.btoa(payload.password),
|
||||
},
|
||||
})
|
||||
|
||||
SessionModel.token = response.data.token
|
||||
|
||||
__comty_shared_state.eventBus.emit("auth:login_success")
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static logout = async () => {
|
||||
await SessionModel.destroyCurrentSession()
|
||||
|
||||
SessionModel.removeToken()
|
||||
|
||||
__comty_shared_state.eventBus.emit("auth:logout_success")
|
||||
}
|
||||
|
||||
static register = async (payload) => {
|
||||
const { username, password, email } = payload
|
||||
|
||||
const response = await request({
|
||||
method: "post",
|
||||
url: "/auth/register",
|
||||
data: {
|
||||
username,
|
||||
password,
|
||||
email,
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (!response) {
|
||||
throw new Error("Unable to register user")
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
}
|
82
packages/comty.js/src/models/feed/index.js
Executable file
82
packages/comty.js/src/models/feed/index.js
Executable file
@ -0,0 +1,82 @@
|
||||
import request from "../../handlers/request"
|
||||
import Settings from "../../helpers/withSettings"
|
||||
|
||||
export default class FeedModel {
|
||||
static getMusicFeed = async ({ trim, limit } = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/feed/music`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getGlobalMusicFeed = async ({ trim, limit } = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/feed/music/global`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getTimelineFeed = async ({ trim, limit } = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/feed/timeline`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getPostsFeed = async ({ trim, limit } = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/feed/posts`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getPlaylistsFeed = async ({ trim, limit } = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/feed/playlists`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static search = async (keywords, params = {}) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/search`,
|
||||
params: {
|
||||
keywords: keywords,
|
||||
params: params
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
48
packages/comty.js/src/models/follows/index.js
Executable file
48
packages/comty.js/src/models/follows/index.js
Executable file
@ -0,0 +1,48 @@
|
||||
import { SessionModel } from "../../models"
|
||||
import request from "../../handlers/request"
|
||||
|
||||
export default class FollowsModel {
|
||||
static imFollowing = async (user_id) => {
|
||||
if (!user_id) {
|
||||
throw new Error("user_id is required")
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/follow/user/${user_id}`,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getFollowers = async (user_id) => {
|
||||
if (!user_id) {
|
||||
// set current user_id
|
||||
user_id = SessionModel.user_id
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/follow/user/${user_id}/followers`,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static toogleFollow = async ({ user_id, username }) => {
|
||||
if (!user_id && !username) {
|
||||
throw new Error("user_id or username is required")
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "POST",
|
||||
url: "/follow/user/toogle",
|
||||
data: {
|
||||
user_id: user_id,
|
||||
username: username
|
||||
},
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
}
|
44
packages/comty.js/src/models/index.js
Normal file
44
packages/comty.js/src/models/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
import AuthModel from "./auth"
|
||||
import FeedModel from "./feed"
|
||||
import FollowsModel from "./follows"
|
||||
import LivestreamModel from "./livestream"
|
||||
import PlaylistsModel from "./playlists"
|
||||
import PostModel from "./post"
|
||||
import SessionModel from "./session"
|
||||
import SyncModel from "./sync"
|
||||
import UserModel from "./user"
|
||||
|
||||
function getEndpointsFromModel(model) {
|
||||
return Object.entries(model).reduce((acc, [key, value]) => {
|
||||
acc[key] = value
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
function createHandlers() {
|
||||
return {
|
||||
auth: getEndpointsFromModel(AuthModel),
|
||||
feed: getEndpointsFromModel(FeedModel),
|
||||
follows: getEndpointsFromModel(FollowsModel),
|
||||
livestream: getEndpointsFromModel(LivestreamModel),
|
||||
playlists: getEndpointsFromModel(PlaylistsModel),
|
||||
post: getEndpointsFromModel(PostModel),
|
||||
session: getEndpointsFromModel(SessionModel),
|
||||
sync: getEndpointsFromModel(SyncModel),
|
||||
user: getEndpointsFromModel(UserModel),
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
AuthModel,
|
||||
FeedModel,
|
||||
FollowsModel,
|
||||
LivestreamModel,
|
||||
PlaylistsModel,
|
||||
PostModel,
|
||||
SessionModel,
|
||||
SyncModel,
|
||||
UserModel,
|
||||
createHandlers,
|
||||
}
|
84
packages/comty.js/src/models/livestream/index.js
Executable file
84
packages/comty.js/src/models/livestream/index.js
Executable file
@ -0,0 +1,84 @@
|
||||
import request from "../../handlers/request"
|
||||
|
||||
export default class Livestream {
|
||||
static deleteProfile = async (profile_id) => {
|
||||
const response = await request({
|
||||
method: "DELETE",
|
||||
url: `/tv/streaming/profile`,
|
||||
data: {
|
||||
profile_id
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static postProfile = async (payload) => {
|
||||
const response = await request({
|
||||
method: "POST",
|
||||
url: `/tv/streaming/profile`,
|
||||
data: payload,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getProfiles = async () => {
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/tv/streaming/profiles`,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static regenerateStreamingKey = async (profile_id) => {
|
||||
const response = await request({
|
||||
method: "POST",
|
||||
url: `/tv/streaming/regenerate_key`,
|
||||
data: {
|
||||
profile_id
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getCategories = async (key) => {
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/tv/streaming/categories`,
|
||||
params: {
|
||||
key,
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getLivestream = async (payload = {}) => {
|
||||
if (!payload.username) {
|
||||
throw new Error("Username is required")
|
||||
}
|
||||
|
||||
let response = await request({
|
||||
method: "GET",
|
||||
url: `/tv/streams`,
|
||||
params: {
|
||||
username: payload.username,
|
||||
profile_id: payload.profile_id,
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getLivestreams = async () => {
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/tv/streams`,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
}
|
48
packages/comty.js/src/models/playlists/index.js
Executable file
48
packages/comty.js/src/models/playlists/index.js
Executable file
@ -0,0 +1,48 @@
|
||||
import request from "../../handlers/request"
|
||||
|
||||
export default class PlaylistsModel {
|
||||
static putPlaylist = async (payload) => {
|
||||
if (!payload) {
|
||||
throw new Error("Payload is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "PUT",
|
||||
url: `/playlist`,
|
||||
data: payload,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getPlaylist = async (id) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/playlist/data/${id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getMyReleases = async () => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/playlist/self`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static deletePlaylist = async (id) => {
|
||||
if (!id) {
|
||||
throw new Error("ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "DELETE",
|
||||
url: `/playlist/${id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
169
packages/comty.js/src/models/post/index.js
Executable file
169
packages/comty.js/src/models/post/index.js
Executable file
@ -0,0 +1,169 @@
|
||||
import request from "../../handlers/request"
|
||||
import Settings from "../../helpers/withSettings"
|
||||
|
||||
export default class Post {
|
||||
static get maxPostTextLength() {
|
||||
return 3200
|
||||
}
|
||||
|
||||
static get maxCommentLength() {
|
||||
return 1200
|
||||
}
|
||||
|
||||
static getPostingPolicy = async () => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: "/posting_policy",
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getPost = async ({ post_id }) => {
|
||||
if (!post_id) {
|
||||
throw new Error("Post ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/posts/post/${post_id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getPostComments = async ({ post_id }) => {
|
||||
if (!post_id) {
|
||||
throw new Error("Post ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/comments/post/${post_id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static sendComment = async ({ post_id, comment }) => {
|
||||
if (!post_id || !comment) {
|
||||
throw new Error("Post ID and/or comment are required")
|
||||
}
|
||||
|
||||
const request = await request({
|
||||
method: "POST",
|
||||
url: `/comments/post/${post_id}`,
|
||||
data: {
|
||||
message: comment,
|
||||
},
|
||||
})
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
static deleteComment = async ({ post_id, comment_id }) => {
|
||||
if (!post_id || !comment_id) {
|
||||
throw new Error("Post ID and/or comment ID are required")
|
||||
}
|
||||
|
||||
const request = await request({
|
||||
method: "DELETE",
|
||||
url: `/comments/post/${post_id}/${comment_id}`,
|
||||
})
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
static getExplorePosts = async ({ trim, limit }) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/posts/explore`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getSavedPosts = async ({ trim, limit }) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/posts/saved`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getUserPosts = async ({ user_id, trim, limit }) => {
|
||||
if (!user_id) {
|
||||
// use current user_id
|
||||
user_id = app.userData?._id
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/posts/user/${user_id}`,
|
||||
params: {
|
||||
trim: trim ?? 0,
|
||||
limit: limit ?? Settings.get("feed_max_fetch"),
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static toogleLike = async ({ post_id }) => {
|
||||
if (!post_id) {
|
||||
throw new Error("Post ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "POST",
|
||||
url: `/posts/${post_id}/toogle_like`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static toogleSave = async ({ post_id }) => {
|
||||
if (!post_id) {
|
||||
throw new Error("Post ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "POST",
|
||||
url: `/posts/${post_id}/toogle_save`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static create = async (payload) => {
|
||||
const { data } = await request({
|
||||
method: "POST",
|
||||
url: `/posts/new`,
|
||||
data: payload,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static deletePost = async ({ post_id }) => {
|
||||
if (!post_id) {
|
||||
throw new Error("Post ID is required")
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "DELETE",
|
||||
url: `/posts/${post_id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
114
packages/comty.js/src/models/session/index.js
Executable file
114
packages/comty.js/src/models/session/index.js
Executable file
@ -0,0 +1,114 @@
|
||||
import jwt_decode from "jwt-decode"
|
||||
import request from "../../handlers/request"
|
||||
import Storage from "../../helpers/withStorage"
|
||||
|
||||
export default class Session {
|
||||
static storageTokenKey = "token"
|
||||
|
||||
static get token() {
|
||||
return Storage.engine.get(this.storageTokenKey)
|
||||
}
|
||||
|
||||
static set token(token) {
|
||||
return Storage.engine.set(this.storageTokenKey, token)
|
||||
}
|
||||
|
||||
static get user_id() {
|
||||
return this.getDecodedToken()?.user_id
|
||||
}
|
||||
|
||||
static get session_uuid() {
|
||||
return this.getDecodedToken()?.session_uuid
|
||||
}
|
||||
|
||||
static getDecodedToken = () => {
|
||||
const token = this.token
|
||||
|
||||
return token && jwt_decode(token)
|
||||
}
|
||||
|
||||
static getAllSessions = async () => {
|
||||
const response = await request({
|
||||
method: "get",
|
||||
url: "/session/all"
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getCurrentSession = async () => {
|
||||
const response = await request({
|
||||
method: "get",
|
||||
url: "/session/current"
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static getTokenValidation = async () => {
|
||||
const session = await Session.token
|
||||
|
||||
const response = await request({
|
||||
method: "get",
|
||||
url: "/session/validate",
|
||||
data: {
|
||||
session: session
|
||||
}
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static removeToken() {
|
||||
return Storage.engine.remove(Session.storageTokenKey)
|
||||
}
|
||||
|
||||
static destroyCurrentSession = async () => {
|
||||
const token = await Session.token
|
||||
const session = await Session.getDecodedToken()
|
||||
|
||||
if (!session || !token) {
|
||||
return false
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "delete",
|
||||
url: "/session/current"
|
||||
}).catch((error) => {
|
||||
console.error(error)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
Session.removeToken()
|
||||
|
||||
__comty_shared_state.emit("session.destroyed")
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static destroyAllSessions = async () => {
|
||||
const session = await Session.getDecodedToken()
|
||||
|
||||
if (!session) {
|
||||
return false
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "delete",
|
||||
url: "/session/all"
|
||||
})
|
||||
|
||||
Session.removeToken()
|
||||
|
||||
__comty_shared_state.emit("session.destroyed")
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static isCurrentTokenValid = async () => {
|
||||
const health = await Session.getTokenValidation()
|
||||
|
||||
return health.valid
|
||||
}
|
||||
}
|
87
packages/comty.js/src/models/sync/cores/spotifyCore.js
Executable file
87
packages/comty.js/src/models/sync/cores/spotifyCore.js
Executable file
@ -0,0 +1,87 @@
|
||||
export default class SpotifySyncModel {
|
||||
static get spotify_redirect_uri() {
|
||||
return window.location.origin + "/callbacks/sync/spotify"
|
||||
}
|
||||
|
||||
static get spotify_authorize_endpoint() {
|
||||
return "https://accounts.spotify.com/authorize?response_type=code&client_id={{client_id}}&scope={{scope}}&redirect_uri={{redirect_uri}}&response_type=code"
|
||||
}
|
||||
|
||||
static async authorizeAccount() {
|
||||
const scopes = [
|
||||
"user-read-private",
|
||||
"user-modify-playback-state",
|
||||
"user-read-currently-playing",
|
||||
"user-read-playback-state",
|
||||
"streaming",
|
||||
]
|
||||
|
||||
const { client_id } = await SpotifySyncModel.get_client_id()
|
||||
|
||||
const parsedUrl = SpotifySyncModel.spotify_authorize_endpoint
|
||||
.replace("{{client_id}}", client_id)
|
||||
.replace("{{scope}}", scopes.join(" "))
|
||||
.replace("{{redirect_uri}}", SpotifySyncModel.spotify_redirect_uri)
|
||||
|
||||
// open on a new tab
|
||||
window.open(parsedUrl, "_blank")
|
||||
}
|
||||
|
||||
static async get_client_id() {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "GET",
|
||||
url: `/sync/spotify/client_id`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static async syncAuthCode(code) {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "POST",
|
||||
url: `/sync/spotify/auth`,
|
||||
data: {
|
||||
redirect_uri: SpotifySyncModel.spotify_redirect_uri,
|
||||
code,
|
||||
},
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static async unlinkAccount() {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "POST",
|
||||
url: `/sync/spotify/unlink`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static async isAuthorized() {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "GET",
|
||||
url: `/sync/spotify/is_authorized`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static async getData() {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "GET",
|
||||
url: `/sync/spotify/data`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static async getCurrentPlaying() {
|
||||
const { data } = await app.cores.api.customRequest( {
|
||||
method: "GET",
|
||||
url: `/sync/spotify/currently_playing`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
11
packages/comty.js/src/models/sync/index.js
Executable file
11
packages/comty.js/src/models/sync/index.js
Executable file
@ -0,0 +1,11 @@
|
||||
import SpotifySyncModel from "./cores/spotifyCore"
|
||||
|
||||
export default class SyncModel {
|
||||
static get bridge() {
|
||||
return window.app?.cores.api.withEndpoints()
|
||||
}
|
||||
|
||||
static get spotifyCore() {
|
||||
return SpotifySyncModel
|
||||
}
|
||||
}
|
159
packages/comty.js/src/models/user/index.js
Executable file
159
packages/comty.js/src/models/user/index.js
Executable file
@ -0,0 +1,159 @@
|
||||
import SessionModel from "../session"
|
||||
import request from "../../handlers/request"
|
||||
|
||||
export default class User {
|
||||
static data = async (payload = {}) => {
|
||||
let {
|
||||
username,
|
||||
user_id,
|
||||
} = payload
|
||||
|
||||
if (!username && !user_id) {
|
||||
user_id = SessionModel.user_id
|
||||
}
|
||||
|
||||
if (username && !user_id) {
|
||||
// resolve user_id from username
|
||||
const resolveResponse = await request({
|
||||
method: "GET",
|
||||
url: `/user/user_id/${username}`,
|
||||
})
|
||||
|
||||
user_id = resolveResponse.data.user_id
|
||||
}
|
||||
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: `/user/${user_id}/data`,
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static updateData = async (payload) => {
|
||||
const response = await request({
|
||||
method: "POST",
|
||||
url: "/user/self/update_data",
|
||||
data: {
|
||||
update: payload,
|
||||
},
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static unsetFullName = async () => {
|
||||
const response = await request({
|
||||
method: "DELETE",
|
||||
url: "/user/self/public_name",
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static selfRoles = async () => {
|
||||
const response = await request({
|
||||
method: "GET",
|
||||
url: "/roles/self",
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
static haveRole = async (role) => {
|
||||
const roles = await User.selfRoles()
|
||||
|
||||
if (!roles) {
|
||||
return false
|
||||
}
|
||||
|
||||
return Array.isArray(roles) && roles.includes(role)
|
||||
}
|
||||
|
||||
static haveAdmin = async () => {
|
||||
return User.haveRole("admin")
|
||||
}
|
||||
|
||||
static getUserBadges = async (user_id) => {
|
||||
if (!user_id) {
|
||||
user_id = SessionModel.user_id
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/badge/user/${user_id}`,
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static changePassword = async (payload) => {
|
||||
const { currentPassword, newPassword } = payload
|
||||
|
||||
const { data } = await request({
|
||||
method: "POST",
|
||||
url: "/self/update_password",
|
||||
data: {
|
||||
currentPassword,
|
||||
newPassword,
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getUserFollowers = async ({
|
||||
user_id,
|
||||
limit = 20,
|
||||
offset = 0,
|
||||
}) => {
|
||||
// if user_id or username is not provided, set with current user
|
||||
if (!user_id && !username) {
|
||||
user_id = SessionModel.user_id
|
||||
}
|
||||
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/user/${user_id}/followers`,
|
||||
params: {
|
||||
limit,
|
||||
offset,
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static getConnectedUsersFollowing = async () => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: "/status/connected/following",
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static checkUsernameAvailability = async (username) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/user/username_available`,
|
||||
params: {
|
||||
username,
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
static checkEmailAvailability = async (email) => {
|
||||
const { data } = await request({
|
||||
method: "GET",
|
||||
url: `/user/email_available`,
|
||||
params: {
|
||||
email,
|
||||
}
|
||||
})
|
||||
|
||||
return data
|
||||
}
|
||||
}
|
18
packages/comty.js/src/models/widget/index.js
Normal file
18
packages/comty.js/src/models/widget/index.js
Normal file
@ -0,0 +1,18 @@
|
||||
import request from "../../handlers/request"
|
||||
|
||||
export default class WidgetModel {
|
||||
static browse = async ({ limit, offset, keywords } = {}) => {
|
||||
const response = await request({
|
||||
instance: globalThis.__comty_shared_state.instances["marketplace"],
|
||||
method: "GET",
|
||||
url: "/widgets",
|
||||
params: {
|
||||
limit,
|
||||
offset,
|
||||
keywords: JSON.stringify(keywords),
|
||||
},
|
||||
})
|
||||
|
||||
return response.data
|
||||
}
|
||||
}
|
45
packages/comty.js/src/remotes.js
Normal file
45
packages/comty.js/src/remotes.js
Normal file
@ -0,0 +1,45 @@
|
||||
function composeRemote(path) {
|
||||
return envOrigins[process.env.NODE_ENV][path]
|
||||
}
|
||||
|
||||
function getCurrentHostname() {
|
||||
if (typeof window === "undefined") {
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
return window?.location?.hostname ?? "localhost"
|
||||
}
|
||||
|
||||
const envOrigins = {
|
||||
"development": {
|
||||
default: `http://${getCurrentHostname()}:3010`,
|
||||
messaging: `http://${getCurrentHostname()}:3020`,
|
||||
livestreaming: `http://${getCurrentHostname()}:3030`,
|
||||
marketplace: `http://${getCurrentHostname()}:3040`,
|
||||
},
|
||||
"production": {
|
||||
default: "https://api.comty.app",
|
||||
messaging: `https://messaging_api.comty.app`,
|
||||
livestreaming: `https://livestreaming_api.comty.app`,
|
||||
marketplace: `https://marketplace_api.comty.app`,
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
default: {
|
||||
origin: composeRemote("default"),
|
||||
hasWebsocket: true,
|
||||
needsAuth: true,
|
||||
},
|
||||
messaging: {
|
||||
origin: composeRemote("messaging"),
|
||||
hasWebsocket: true,
|
||||
needsAuth: true,
|
||||
},
|
||||
livestreaming: {
|
||||
origin: composeRemote("livestreaming"),
|
||||
},
|
||||
marketplace: {
|
||||
origin: composeRemote("marketplace"),
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user