mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 19:14:16 +00:00
268 lines
7.7 KiB
JavaScript
Executable File
268 lines
7.7 KiB
JavaScript
Executable File
import Core from "evite/src/core"
|
|
import config from "config"
|
|
import { Bridge } from "linebridge/dist/client"
|
|
import { Session } from "models"
|
|
|
|
function generateWSFunctionHandler(socket, type = "listen") {
|
|
if (!socket) {
|
|
return null
|
|
}
|
|
|
|
return (to, fn) => {
|
|
if (typeof to === "undefined") {
|
|
console.error("handleWSListener: to must be defined")
|
|
return false
|
|
}
|
|
if (typeof fn !== "function") {
|
|
console.error("handleWSListener: fn must be function")
|
|
return false
|
|
}
|
|
|
|
let ns = "main"
|
|
let event = null
|
|
|
|
if (typeof to === "string") {
|
|
event = to
|
|
} else if (typeof to === "object") {
|
|
ns = to.ns
|
|
event = to.event
|
|
}
|
|
|
|
switch (type) {
|
|
case "listen": {
|
|
return socket.sockets[ns].on(event, async (...context) => {
|
|
return await fn(...context)
|
|
})
|
|
}
|
|
|
|
case "unlisten": {
|
|
return socket.sockets[ns].removeListener(event)
|
|
}
|
|
|
|
default: {
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default class ApiCore extends Core {
|
|
constructor(props) {
|
|
super(props)
|
|
|
|
this.namespaces = Object()
|
|
|
|
this.onExpiredExceptionEvent = false
|
|
this.excludedExpiredExceptionURL = ["/regenerate_session_token"]
|
|
|
|
this.ctx.registerPublicMethod("api", this)
|
|
}
|
|
|
|
async customRequest(
|
|
namepace = undefined,
|
|
payload = {
|
|
method: "GET",
|
|
},
|
|
...args
|
|
) {
|
|
if (typeof namepace === "undefined") {
|
|
throw new Error("Namespace must be defined")
|
|
}
|
|
|
|
if (typeof this.namespaces[namepace] === "undefined") {
|
|
throw new Error("Namespace not found")
|
|
}
|
|
|
|
if (typeof payload === "string") {
|
|
payload = {
|
|
url: payload,
|
|
}
|
|
}
|
|
|
|
if (typeof payload.headers !== "object") {
|
|
payload.headers = {}
|
|
}
|
|
|
|
const sessionToken = await Session.token
|
|
|
|
if (sessionToken) {
|
|
payload.headers["Authorization"] = `Bearer ${sessionToken}`
|
|
|
|
} else {
|
|
console.warn("Making a request with no session token")
|
|
}
|
|
|
|
return await this.namespaces[namepace].httpInterface(payload, ...args)
|
|
}
|
|
|
|
request = (namespace = "main", method, endpoint, ...args) => {
|
|
if (!this.namespaces[namespace]) {
|
|
throw new Error(`Namespace ${namespace} not found`)
|
|
}
|
|
|
|
if (!this.namespaces[namespace].endpoints[method]) {
|
|
throw new Error(`Method ${method} not found`)
|
|
}
|
|
|
|
if (!this.namespaces[namespace].endpoints[method][endpoint]) {
|
|
throw new Error(`Endpoint ${endpoint} not found`)
|
|
}
|
|
|
|
return this.namespaces[namespace].endpoints[method][endpoint](...args)
|
|
}
|
|
|
|
withEndpoints = (namespace = "main") => {
|
|
if (!this.namespaces[namespace]) {
|
|
throw new Error(`Namespace ${namespace} not found`)
|
|
}
|
|
|
|
return this.namespaces[namespace].endpoints
|
|
}
|
|
|
|
handleBeforeRequest = async (request) => {
|
|
if (this.onExpiredExceptionEvent) {
|
|
if (this.excludedExpiredExceptionURL.includes(request.url)) return
|
|
|
|
await new Promise((resolve) => {
|
|
app.eventBus.once("session.regenerated", () => {
|
|
console.log(`Session has been regenerated, retrying request`)
|
|
resolve()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
handleRegenerationEvent = async (refreshToken, makeRequest) => {
|
|
window.app.eventBus.emit("session.expiredExceptionEvent", refreshToken)
|
|
|
|
this.onExpiredExceptionEvent = true
|
|
|
|
const expiredToken = await Session.token
|
|
|
|
// exclude regeneration endpoint
|
|
|
|
// send request to regenerate token
|
|
const response = await this.request("main", "post", "regenerateSessionToken", {
|
|
expiredToken: expiredToken,
|
|
refreshToken,
|
|
}).catch((error) => {
|
|
console.error(`Failed to regenerate token: ${error.message}`)
|
|
return false
|
|
})
|
|
|
|
if (!response) {
|
|
return window.app.eventBus.emit("session.invalid", "Failed to regenerate token")
|
|
}
|
|
|
|
// set new token
|
|
Session.token = response.token
|
|
|
|
//this.namespaces["main"].internalAbortController.abort()
|
|
|
|
this.onExpiredExceptionEvent = false
|
|
|
|
// emit event
|
|
window.app.eventBus.emit("session.regenerated")
|
|
}
|
|
|
|
attachBridge = (key, params) => {
|
|
return this.namespaces[key] = this.createBridge(params)
|
|
}
|
|
|
|
detachBridge = (key) => {
|
|
return delete this.namespaces[key]
|
|
}
|
|
|
|
createBridge(params = {}) {
|
|
const getSessionContext = async () => {
|
|
const obj = {}
|
|
const token = await Session.token
|
|
|
|
if (token) {
|
|
// append token to context
|
|
obj.headers = {
|
|
Authorization: `Bearer ${token ?? null}`,
|
|
}
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
const handleResponse = async (data, makeRequest) => {
|
|
// handle 401 responses
|
|
if (data instanceof Error) {
|
|
if (data.response.status === 401) {
|
|
// check if the server issue a refresh token on data
|
|
if (data.response.data.refreshToken) {
|
|
// handle regeneration event
|
|
await this.handleRegenerationEvent(data.response.data.refreshToken, makeRequest)
|
|
return await makeRequest()
|
|
} else {
|
|
return window.app.eventBus.emit("session.invalid", "Session expired, but the server did not issue a refresh token")
|
|
}
|
|
}
|
|
if (data.response.status === 403) {
|
|
return window.app.eventBus.emit("session.invalid", "Session not valid or not existent")
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof params !== "object") {
|
|
throw new Error("Params must be an object")
|
|
}
|
|
|
|
const bridgeOptions = {
|
|
wsOptions: {
|
|
autoConnect: false,
|
|
},
|
|
onBeforeRequest: this.handleBeforeRequest,
|
|
onRequest: getSessionContext,
|
|
onResponse: handleResponse,
|
|
...params,
|
|
origin: params.httpAddress ?? config.remotes.mainApi,
|
|
}
|
|
|
|
const bridge = new Bridge(bridgeOptions)
|
|
|
|
// handle main ws events
|
|
const mainWSSocket = bridge.wsInterface.sockets["main"]
|
|
|
|
mainWSSocket.on("authenticated", () => {
|
|
console.debug("[WS] Authenticated")
|
|
})
|
|
|
|
mainWSSocket.on("authenticateFailed", (error) => {
|
|
console.error("[WS] Authenticate Failed", error)
|
|
})
|
|
|
|
mainWSSocket.on("connect", () => {
|
|
this.ctx.eventBus.emit(`api.ws.main.connect`)
|
|
this.autenticateWS(mainWSSocket)
|
|
})
|
|
|
|
mainWSSocket.on("disconnect", (...context) => {
|
|
this.ctx.eventBus.emit(`api.ws.main.disconnect`, ...context)
|
|
})
|
|
|
|
mainWSSocket.on("connect_error", (...context) => {
|
|
this.ctx.eventBus.emit(`api.ws.main.connect_error`, ...context)
|
|
})
|
|
|
|
// generate functions
|
|
bridge.listenEvent = generateWSFunctionHandler(bridge.wsInterface, "listen")
|
|
bridge.unlistenEvent = generateWSFunctionHandler(bridge.wsInterface, "unlisten")
|
|
|
|
// return bridge
|
|
return bridge
|
|
}
|
|
|
|
autenticateWS = async (socket) => {
|
|
const token = await Session.token
|
|
|
|
if (token) {
|
|
socket.emit("authenticate", {
|
|
token,
|
|
})
|
|
}
|
|
}
|
|
} |