mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-11 03:24:16 +00:00
384 lines
11 KiB
JavaScript
Executable File
384 lines
11 KiB
JavaScript
Executable File
import Core from "evite/src/core"
|
|
import { Bridge } from "linebridge/dist/client"
|
|
|
|
import config from "config"
|
|
import { SessionModel } 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 {
|
|
static refName = "api"
|
|
static namespace = "api"
|
|
static depends = ["settings"]
|
|
|
|
excludedExpiredExceptionURL = ["/session/regenerate"]
|
|
|
|
onExpiredExceptionEvent = false
|
|
|
|
instance = null
|
|
|
|
public = {
|
|
instance: function () {
|
|
return this.instance
|
|
}.bind(this),
|
|
customRequest: this.customRequest.bind(this),
|
|
request: this.request.bind(this),
|
|
withEndpoints: this.withEndpoints.bind(this),
|
|
attach: this.attach.bind(this),
|
|
createBridge: this.createBridge.bind(this),
|
|
autenticateWS: this.autenticateWS.bind(this),
|
|
listenEvent: this.listenEvent.bind(this),
|
|
unlistenEvent: this.unlistenEvent.bind(this),
|
|
measurePing: this.measurePing.bind(this),
|
|
}
|
|
|
|
async attach() {
|
|
// get remotes origins from config
|
|
const defaultRemotes = config.remotes
|
|
|
|
// get storaged remotes origins
|
|
const storedRemotes = await app.cores.settings.get("remotes") ?? {}
|
|
|
|
const origin = storedRemotes.mainApi ?? defaultRemotes.mainApi
|
|
|
|
this.instance = this.createBridge({
|
|
origin,
|
|
})
|
|
|
|
await this.instance.initialize()
|
|
|
|
console.debug(`[API] Attached to ${origin}`, this.instance)
|
|
|
|
return this.instance
|
|
}
|
|
|
|
async customRequest(
|
|
request = {
|
|
method: "GET",
|
|
},
|
|
...args
|
|
) {
|
|
// handle before request
|
|
await this.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"] = `Bearer ${sessionToken}`
|
|
} else {
|
|
console.warn("Making a request with no session token")
|
|
}
|
|
|
|
const _result = await this.instance.httpInterface(request, ...args)
|
|
.catch((error) => {
|
|
return error
|
|
})
|
|
|
|
result = _result
|
|
}
|
|
|
|
await makeRequest()
|
|
|
|
// handle after request
|
|
await this.handleAfterRequest(result, makeRequest)
|
|
|
|
// if error, throw it
|
|
if (result instanceof Error) {
|
|
throw result
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
request(method, endpoint, ...args) {
|
|
return this.instance.endpoints[method][endpoint](...args)
|
|
}
|
|
|
|
withEndpoints() {
|
|
return this.instance.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()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
handleAfterRequest = async (data, callback) => {
|
|
// 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) {
|
|
console.log(`Session expired, but the server issued a refresh token, handling regeneration event`)
|
|
|
|
// handle regeneration event
|
|
await this.handleRegenerationEvent(data.response.data.refreshToken)
|
|
|
|
return await callback()
|
|
} 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")
|
|
}
|
|
}
|
|
}
|
|
|
|
handleRegenerationEvent = async (refreshToken) => {
|
|
window.app.eventBus.emit("session.expiredExceptionEvent", refreshToken)
|
|
|
|
this.onExpiredExceptionEvent = true
|
|
|
|
const expiredToken = await SessionModel.token
|
|
|
|
// send request to regenerate token
|
|
const response = await this.customRequest({
|
|
method: "POST",
|
|
url: "/session/regenerate",
|
|
data: {
|
|
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")
|
|
}
|
|
|
|
if (!response.data?.token) {
|
|
return window.app.eventBus.emit("session.invalid", "Failed to regenerate token, invalid server response.")
|
|
}
|
|
|
|
// set new token
|
|
SessionModel.token = response.data.token
|
|
|
|
//this.namespaces["main"].internalAbortController.abort()
|
|
|
|
this.onExpiredExceptionEvent = false
|
|
|
|
// emit event
|
|
window.app.eventBus.emit("session.regenerated")
|
|
}
|
|
|
|
createBridge(params = {}) {
|
|
const getSessionContext = async () => {
|
|
const obj = {}
|
|
const token = await SessionModel.token
|
|
|
|
if (token) {
|
|
// append token to context
|
|
obj.headers = {
|
|
Authorization: `Bearer ${token ?? null}`,
|
|
}
|
|
}
|
|
|
|
return obj
|
|
}
|
|
|
|
if (typeof params !== "object") {
|
|
throw new Error("Params must be an object")
|
|
}
|
|
|
|
const bridgeOptions = {
|
|
wsOptions: {
|
|
autoConnect: false,
|
|
},
|
|
onBeforeRequest: this.handleBeforeRequest,
|
|
onRequest: getSessionContext,
|
|
onResponse: this.handleAfterRequest,
|
|
...params,
|
|
origin: params.httpAddress ?? config.remotes.mainApi,
|
|
}
|
|
|
|
const bridge = new Bridge(bridgeOptions)
|
|
|
|
// handle main ws onEvents
|
|
const mainSocket = bridge.wsInterface.sockets["main"]
|
|
|
|
mainSocket.on("authenticated", () => {
|
|
console.debug("[WS] Authenticated")
|
|
})
|
|
|
|
mainSocket.on("authenticateFailed", (error) => {
|
|
console.error("[WS] Authenticate Failed", error)
|
|
})
|
|
|
|
mainSocket.on("connect", () => {
|
|
if (this.ctx.eventBus) {
|
|
this.ctx.eventBus.emit(`api.ws.main.connect`)
|
|
}
|
|
|
|
console.debug("[WS] Connected, authenticating...")
|
|
|
|
this.autenticateWS(mainSocket)
|
|
})
|
|
|
|
mainSocket.on("disconnect", (...context) => {
|
|
if (this.ctx.eventBus) {
|
|
this.ctx.eventBus.emit(`api.ws.main.disconnect`, ...context)
|
|
}
|
|
})
|
|
|
|
mainSocket.on("connect_error", (...context) => {
|
|
if (this.ctx.eventBus) {
|
|
this.ctx.eventBus.emit(`api.ws.main.connect_error`, ...context)
|
|
}
|
|
})
|
|
|
|
mainSocket.onAny((event, ...args) => {
|
|
console.debug(`[WS] Recived Event (${event})`, ...args)
|
|
})
|
|
|
|
// mainSocket.onAnyOutgoing((event, ...args) => {
|
|
// console.debug(`[WS] Sent Event (${event})`, ...args)
|
|
// })
|
|
|
|
return bridge
|
|
}
|
|
|
|
listenEvent(event, callback) {
|
|
if (!this.instance.wsInterface) {
|
|
throw new Error("API is not attached")
|
|
}
|
|
|
|
return this.instance.wsInterface.sockets["main"].on(event, callback)
|
|
}
|
|
|
|
unlistenEvent(event, callback) {
|
|
if (!this.instance.wsInterface) {
|
|
throw new Error("API is not attached")
|
|
}
|
|
|
|
return this.instance.wsInterface.sockets["main"].off(event, callback)
|
|
}
|
|
|
|
async autenticateWS(socket) {
|
|
const token = await SessionModel.token
|
|
|
|
if (!token) {
|
|
return console.error("Failed to authenticate WS, no token found")
|
|
}
|
|
|
|
socket.emit("authenticate", {
|
|
token,
|
|
})
|
|
}
|
|
|
|
async measurePing() {
|
|
const timings = {}
|
|
|
|
const promises = [
|
|
new Promise(async (resolve) => {
|
|
const start = Date.now()
|
|
|
|
this.customRequest({
|
|
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()
|
|
|
|
this.instance.wsInterface.sockets["main"].on("pong", () => {
|
|
timings.ws = Date.now() - start
|
|
|
|
resolve()
|
|
})
|
|
|
|
this.instance.wsInterface.sockets["main"].emit("ping")
|
|
|
|
setTimeout(() => {
|
|
timings.ws = "failed"
|
|
|
|
resolve()
|
|
}, 10000)
|
|
})
|
|
]
|
|
|
|
await Promise.all(promises)
|
|
|
|
return timings
|
|
}
|
|
} |