reimplement api, with namespaces

This commit is contained in:
srgooglo 2022-08-04 12:46:20 +02:00
parent 8d42494f10
commit a09b79aaea
19 changed files with 147 additions and 180 deletions

View File

@ -35,7 +35,7 @@ export default [
"onUpdate": async (value) => { "onUpdate": async (value) => {
const selfId = await User.selfUserId() const selfId = await User.selfUserId()
const result = window.app.request.post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
fullName: value fullName: value
@ -52,7 +52,7 @@ export default [
"icon": "Delete", "icon": "Delete",
"title": "Unset", "title": "Unset",
"onClick": async () => { "onClick": async () => {
window.app.request.post.unsetPublicName() window.app.api.withEndpoints("main").post.unsetPublicName()
} }
} }
], ],
@ -78,7 +78,7 @@ export default [
"onUpdate": async (value) => { "onUpdate": async (value) => {
const selfId = await User.selfUserId() const selfId = await User.selfUserId()
const result = window.app.request.post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
email: value email: value
@ -105,7 +105,7 @@ export default [
"onUpdate": async (value) => { "onUpdate": async (value) => {
const selfId = await User.selfUserId() const selfId = await User.selfUserId()
const result = window.app.request.post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
avatar: value avatar: value
@ -132,7 +132,7 @@ export default [
"onUpdate": async (value) => { "onUpdate": async (value) => {
const selfId = await User.selfUserId() const selfId = await User.selfUserId()
const result = window.app.request.post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
cover: value cover: value
@ -173,7 +173,7 @@ export default [
"onUpdate": async (value) => { "onUpdate": async (value) => {
const selfId = await User.selfUserId() const selfId = await User.selfUserId()
const result = window.app.request.post.updateUser({ const result = window.app.api.withEndpoints("main").post.updateUser({
_id: selfId, _id: selfId,
update: { update: {
description: value description: value

View File

@ -313,12 +313,19 @@ class App extends React.Component {
const initializationTasks = [ const initializationTasks = [
async () => { async () => {
try { try {
await this.props.cores.ApiCore.attachAPIConnection() // mount main api bridge
await this.props.cores.ApiCore.connectBridge("main", {
locked: true
})
// and initialize it
await this.props.cores.ApiCore.namespaces["main"].initialize()
app.eventBus.emit("app.initialization.api_success") app.eventBus.emit("app.initialization.api_success")
} catch (error) { } catch (error) {
app.eventBus.emit("app.initialization.api_error", error) app.eventBus.emit("app.initialization.api_error", error)
console.error(`[App] Error while initializing api`, error)
throw { throw {
cause: "Cannot connect to API", cause: "Cannot connect to API",
details: `Sorry but we cannot connect to the API. Please try again later. [${config.remotes.mainApi}]`, details: `Sorry but we cannot connect to the API. Please try again later. [${config.remotes.mainApi}]`,
@ -353,20 +360,6 @@ class App extends React.Component {
} }
} }
}, },
async () => {
try {
await this.__WSInit()
app.eventBus.emit("app.initialization.ws_success")
} catch (error) {
app.eventBus.emit("app.initialization.ws_error", error)
throw {
cause: "Cannot connect to WebSocket",
details: error.message,
}
}
},
] ]
await Promise.tasked(initializationTasks).catch((reason) => { await Promise.tasked(initializationTasks).catch((reason) => {
@ -394,14 +387,6 @@ class App extends React.Component {
await this.setState({ session }) await this.setState({ session })
} }
__WSInit = async () => {
if (!this.state.session) {
return false
}
await this.props.cores.ApiCore.attachWSConnection()
}
__UserInit = async () => { __UserInit = async () => {
if (!this.state.session) { if (!this.state.session) {
return false return false

View File

@ -71,7 +71,7 @@ export default class UserDataManager extends React.Component {
loading: false, loading: false,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
if (!this.props.user && this.props.userId) { if (!this.props.user && this.props.userId) {

View File

@ -12,7 +12,7 @@ export default class UserRolesManager extends React.Component {
roles: null, roles: null,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
await this.fetchRoles() await this.fetchRoles()

View File

@ -12,7 +12,7 @@ export default class ImageUploader extends React.Component {
urlList: [], urlList: [],
} }
api = window.app.request api = window.app.api.withEndpoints("main")
handleChange = ({ fileList }) => { handleChange = ({ fileList }) => {
this.setState({ fileList }) this.setState({ fileList })

View File

@ -309,7 +309,7 @@ export const PostCard = React.memo(({
React.useEffect(() => { React.useEffect(() => {
// first listen to post changes // first listen to post changes
window.app.ws.listen(`post.dataUpdate.${data._id}`, onDataUpdate) window.app.api.namespaces["main"].listenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
// proccess post info // proccess post info
// {...} // {...}
@ -319,7 +319,7 @@ export const PostCard = React.memo(({
return () => { return () => {
// remove the listener // remove the listener
window.app.ws.unlisten(`post.dataUpdate.${data._id}`, onDataUpdate) window.app.api.namespaces["main"].unlistenEvent(`post.dataUpdate.${data._id}`, onDataUpdate)
} }
}, []) }, [])

View File

@ -12,7 +12,7 @@ import "./index.less"
const maxMessageLength = 512 const maxMessageLength = 512
export default (props) => { export default (props) => {
const api = window.app.request const api = window.app.api.withEndpoints("main")
const additionsRef = React.useRef(null) const additionsRef = React.useRef(null)
const [pending, setPending] = React.useState([]) const [pending, setPending] = React.useState([])

View File

@ -30,7 +30,7 @@ export default class PostsFeed extends React.Component {
renderList: [], renderList: [],
} }
api = window.app.request api = window.app.api.withEndpoints()
listRef = React.createRef() listRef = React.createRef()
@ -48,7 +48,7 @@ export default class PostsFeed extends React.Component {
// load ws events // load ws events
Object.keys(this.wsEvents).forEach((event) => { Object.keys(this.wsEvents).forEach((event) => {
window.app.ws.listen(event, this.wsEvents[event]) window.app.api.namespaces["main"].listenEvent(event, this.wsEvents[event])
}) })
// TODO: register keybindings to handle directions key scrolling to posts (use app.shortcuts) // TODO: register keybindings to handle directions key scrolling to posts (use app.shortcuts)
@ -67,7 +67,7 @@ export default class PostsFeed extends React.Component {
componentWillUnmount = async () => { componentWillUnmount = async () => {
// unload ws events // unload ws events
Object.keys(this.wsEvents).forEach((event) => { Object.keys(this.wsEvents).forEach((event) => {
window.app.ws.unlisten(event, this.wsEvents[event]) window.app.api.namespaces["main"].unlistenEvent(event, this.wsEvents[event])
}) })
} }

View File

@ -18,7 +18,7 @@ export default class StepsForm extends React.Component {
renderStep: null, renderStep: null,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
if (this.props.defaultValues) { if (this.props.defaultValues) {

View File

@ -66,7 +66,7 @@ const steps = [
] ]
export default (props) => { export default (props) => {
const api = window.app.request const api = window.app.api.withEndpoints("main")
const onSubmit = async (values) => { const onSubmit = async (values) => {
const result = await api.post.register(values).catch((err) => { const result = await api.post.register(values).catch((err) => {

View File

@ -14,7 +14,7 @@ export default class UserSelector extends React.Component {
searchValue: null, searchValue: null,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
this.toogleLoading(true) this.toogleLoading(true)

View File

@ -7,50 +7,40 @@ export default class ApiCore extends Core {
constructor(props) { constructor(props) {
super(props) super(props)
this.apiBridge = this.createBridge() this.namespaces = Object()
this.WSInterface = { this.ctx.registerPublicMethod("api", this)
...this.apiBridge.wsInterface, }
request: this.WSRequest,
listen: this.listenEvent, request = (namespace = "main", method, endpoint, ...args) => {
unlisten: this.unlistenEvent, if (!this.namespaces[namespace]) {
mainSocketConnected: false throw new Error(`Namespace ${namespace} not found`)
} }
this.ctx.registerPublicMethod("api", this.apiBridge) if (!this.namespaces[namespace].endpoints[method]) {
this.ctx.registerPublicMethod("ws", this.WSInterface) throw new Error(`Method ${method} not found`)
this.ctx.registerPublicMethod("request", this.apiBridge.endpoints) }
this.ctx.registerPublicMethod("WSRequest", this.WSInterface.wsEndpoints)
if (!this.namespaces[namespace].endpoints[method][endpoint]) {
throw new Error(`Endpoint ${endpoint} not found`)
}
return this.namespaces[namespace].endpoints[method][endpoint](...args)
} }
async intialize() { withEndpoints = (namespace = "main") => {
this.WSInterface.sockets.main.on("authenticated", () => { if (!this.namespaces[namespace]) {
console.debug("[WS] Authenticated") throw new Error(`Namespace ${namespace} not found`)
}) }
this.WSInterface.sockets.main.on("authenticateFailed", (error) => {
console.error("[WS] Authenticate Failed", error)
})
this.WSInterface.sockets.main.on("connect", () => { return this.namespaces[namespace].endpoints
this.ctx.eventBus.emit("websocket_connected")
this.WSInterface.mainSocketConnected = true
})
this.WSInterface.sockets.main.on("disconnect", (...context) => {
this.ctx.eventBus.emit("websocket_disconnected", ...context)
this.WSInterface.mainSocketConnected = false
})
this.WSInterface.sockets.main.on("connect_error", (...context) => {
this.ctx.eventBus.emit("websocket_connection_error", ...context)
this.WSInterface.mainSocketConnected = false
})
} }
createBridge() { connectBridge = (key, params) => {
this.namespaces[key] = this.createBridge(params)
}
createBridge(params = {}) {
const getSessionContext = async () => { const getSessionContext = async () => {
const obj = {} const obj = {}
const token = await Session.token const token = await Session.token
@ -80,111 +70,103 @@ export default class ApiCore extends Core {
} }
} }
return new Bridge({ if (typeof params !== "object") {
origin: config.remotes.mainApi, throw new Error("Params must be an object")
wsOrigin: config.remotes.websocketApi, }
const bridgeOptions = {
wsOptions: { wsOptions: {
autoConnect: false, autoConnect: false,
}, },
onRequest: getSessionContext, onRequest: getSessionContext,
onResponse: handleResponse, onResponse: handleResponse,
}) ...params,
} origin: params.httpAddress ?? config.remotes.mainApi,
wsOrigin: params.wsAddress ?? config.remotes.websocketApi,
async attachWSConnection() {
if (!this.WSInterface.sockets.main.connected) {
await this.WSInterface.sockets.main.connect()
} }
let startTime = null const bridge = new Bridge(bridgeOptions)
let latency = null
let latencyWarning = false
let pingInterval = setInterval(() => { // handle main ws events
if (!this.WSInterface.mainSocketConnected) { const mainWSSocket = bridge.wsInterface.sockets["main"]
return clearTimeout(pingInterval)
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.${mainWSSocket.id}.connect`)
})
mainWSSocket.on("disconnect", (...context) => {
this.ctx.eventBus.emit(`api.ws.${mainWSSocket.id}.disconnect`, ...context)
})
mainWSSocket.on("connect_error", (...context) => {
this.ctx.eventBus.emit(`api.ws.${mainWSSocket.id}.connect_error`, ...context)
})
// generate functions
bridge.listenEvent = this.generateMainWSEventListener(bridge.wsInterface)
bridge.unlistenEvent = this.generateMainWSEventUnlistener(bridge.wsInterface)
// return bridge
return bridge
}
generateMainWSEventListener(obj) {
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
} }
startTime = Date.now() let ns = "main"
this.WSInterface.sockets.main.emit("ping") let event = null
}, 2000)
this.WSInterface.sockets.main.on("pong", () => { if (typeof to === "string") {
latency = Date.now() - startTime event = to
} else if (typeof to === "object") {
if (latency > 800 && this.WSInterface.mainSocketConnected) { ns = to.ns
latencyWarning = true event = to.event
console.error("[WS] Latency is too high > 800ms", latency)
window.app.eventBus.emit("websocket_latency_too_high", latency)
} else if (latencyWarning && this.WSInterface.mainSocketConnected) {
latencyWarning = false
window.app.eventBus.emit("websocket_latency_normal", latency)
} }
})
}
async attachAPIConnection() { return obj.sockets[ns].on(event, async (...context) => {
await this.apiBridge.initialize() return await fn(...context)
}
listenEvent = (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
}
return window.app.ws.sockets[ns].on(event, async (...context) => {
return await fn(...context)
})
}
unlistenEvent = (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
}
return window.app.ws.sockets[ns].removeListener(event, fn)
}
WSRequest = (socket = "main", channel, ...args) => {
return new Promise(async (resolve, reject) => {
const request = await window.app.ws.sockets[socket].emit(channel, ...args)
request.on("responseError", (...errors) => {
return reject(...errors)
}) })
request.on("response", (...responses) => { }
return resolve(...responses) }
})
}) generateMainWSEventUnlistener(obj) {
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
}
return obj.sockets[ns].removeListener(event, fn)
}
} }
} }

View File

@ -5,7 +5,7 @@ import { Storage } from '@capacitor/storage'
export default class Session { export default class Session {
static get bridge() { static get bridge() {
return window.app?.request return window.app?.api.withEndpoints("main")
} }
static capStorage = async (method, value) => { static capStorage = async (method, value) => {

View File

@ -2,7 +2,7 @@ import Session from "../session"
export default class User { export default class User {
static get bridge() { static get bridge() {
return window.app?.request return window.app?.api.withEndpoints("main")
} }
static async data() { static async data() {

View File

@ -82,7 +82,7 @@ export default class Account extends React.Component {
isNotExistent: false, isNotExistent: false,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
const token = await Session.decodedToken() const token = await Session.decodedToken()

View File

@ -136,7 +136,7 @@ export default class StreamViewer extends React.Component {
} }
gatherStreamInfo = async () => { gatherStreamInfo = async () => {
const result = await app.request.get.streamInfoFromUsername(undefined, { const result = await app.api.withEndpoints("main").get.streamInfoFromUsername(undefined, {
username: this.state.streamKey, username: this.state.streamKey,
}).catch((error) => { }).catch((error) => {
console.error(error) console.error(error)
@ -152,7 +152,7 @@ export default class StreamViewer extends React.Component {
} }
gatherUserInfo = async () => { gatherUserInfo = async () => {
const result = await app.request.get.user(undefined, { const result = await app.api.withEndpoints("main").get.user(undefined, {
username: this.state.streamKey, username: this.state.streamKey,
}).catch((error) => { }).catch((error) => {
console.error(error) console.error(error)

View File

@ -35,7 +35,7 @@ export default (props) => {
const [serverTier, setServerTier] = React.useState(null) const [serverTier, setServerTier] = React.useState(null)
const checkStreamingKey = async () => { const checkStreamingKey = async () => {
const result = await app.request.get.streamingKey().catch((error) => { const result = await app.api.withEndpoints("main").get.streamingKey().catch((error) => {
console.error(error) console.error(error)
antd.message.error(error.message) antd.message.error(error.message)
@ -48,7 +48,7 @@ export default (props) => {
} }
const checkTagetServer = async () => { const checkTagetServer = async () => {
const result = await app.request.get.targetStreamingServer() const result = await app.api.withEndpoints("main").get.targetStreamingServer()
if (result) { if (result) {
const targetServer = `${result.protocol}://${result.address}:${result.port}/${result.space}` const targetServer = `${result.protocol}://${result.address}:${result.port}/${result.space}`
@ -61,7 +61,7 @@ export default (props) => {
title: "Regenerate streaming key", title: "Regenerate streaming key",
content: "Are you sure you want to regenerate the streaming key? After this, all other generated keys will be deleted.", content: "Are you sure you want to regenerate the streaming key? After this, all other generated keys will be deleted.",
onOk: async () => { onOk: async () => {
const result = await app.request.post.regenerateStreamingKey().catch((error) => { const result = await app.api.withEndpoints("main").post.regenerateStreamingKey().catch((error) => {
console.error(error) console.error(error)
antd.message.error(error.message) antd.message.error(error.message)

View File

@ -10,7 +10,7 @@ export default class Streams extends React.Component {
list: [], list: [],
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
await this.updateStreamsList() await this.updateStreamsList()

View File

@ -11,7 +11,7 @@ export default class Users extends React.Component {
selectionEnabled: false, selectionEnabled: false,
} }
api = window.app.request api = window.app.api.withEndpoints("main")
componentDidMount = async () => { componentDidMount = async () => {
await this.loadData() await this.loadData()