From 6ab0eb83514b6f640445ca86572e5b230b750cee Mon Sep 17 00:00:00 2001 From: hippoz <10706925-hippoz@users.noreply.gitlab.com> Date: Wed, 22 Feb 2023 01:06:43 +0200 Subject: [PATCH] better utilize caching for rpc --- frontend/src/request.js | 46 +++++++++++++++++++++++----------------- src/errors.ts | 4 ++-- src/routes/api/v1/rpc.ts | 18 ++++++++++++++++ src/rpc/apis/users.ts | 2 +- src/rpc/rpc.ts | 10 ++++----- 5 files changed, 52 insertions(+), 28 deletions(-) diff --git a/frontend/src/request.js b/frontend/src/request.js index ce4e8eb..7ec7bf5 100644 --- a/frontend/src/request.js +++ b/frontend/src/request.js @@ -1,25 +1,26 @@ import gateway from "./gateway"; import { apiRoute, getItem } from "./storage"; -// TODO: circular dependency -import { overlayStore, OverlayType } from "./stores"; + +const method = (methodId, requiresAuthentication) => ({methodId, requiresAuthentication}); +const withCacheable = (method) => ({ ...method, cacheable: true }) export const methods = { // methodName: [ methodId, requiresAuthentication ] - createUser: [ 0, false ], - loginUser: [ 1, false ], - getUserSelf: [ 2, true ], - promoteUserSelf: [ 3, true ], - createChannel: [ 4, true ], - updateChannelName: [ 5, true ], - deleteChannel: [ 6, true ], - getChannel: [ 7, true ], - getChannels: [ 8, true ], - createChannelMessage: [ 9, true ], - getChannelMessages: [ 10, true ], - putChannelTyping: [ 11, true ], - deleteMessage: [ 12, true ], - updateMessageContent: [ 13, true ], - getMessage: [ 14, true ] + createUser: method(0, false), + loginUser: method(1, false), + getUserSelf: withCacheable(method(2, true)), + promoteUserSelf: method(3, true), + createChannel: method(4, true), + updateChannelName: method(5, true), + deleteChannel: method(6, true), + getChannel: withCacheable(method(7, true)), + getChannels: withCacheable(method(8, true)), + createChannelMessage: method(9, true), + getChannelMessages: withCacheable(method(10, true)), + putChannelTyping: method(11, true), + deleteMessage: method(12, true), + updateMessageContent: method(13, true), + getMessage: withCacheable(method(14, true)) }; export function compatibleFetch(endpoint, options) { @@ -93,9 +94,9 @@ export default function doRequest(method, endpoint, auth=true, body=null) { }); } -export async function remoteCall([methodId, requiresAuthentication], ...args) { +export async function remoteCall({methodId, requiresAuthentication, cacheable}, ...args) { const calls = [[methodId, ...args]]; - if (requiresAuthentication && gateway.authenticated) { + if (requiresAuthentication && gateway.authenticated && !cacheable) { const replies = await gateway.sendRPCRequest(calls); const ok = Array.isArray(replies) && replies[0] && !replies[0].code; return { @@ -104,7 +105,12 @@ export async function remoteCall([methodId, requiresAuthentication], ...args) { }; } - const response = await doRequest("POST", apiRoute("rpc"), requiresAuthentication, calls); + let response; + if (cacheable) { + response = await doRequest("GET", apiRoute(`rpc?calls=${encodeURI(JSON.stringify(calls))}`), requiresAuthentication); + } else { + response = await doRequest("POST", apiRoute("rpc"), requiresAuthentication, calls); + } response.ok = response.ok && Array.isArray(response.json) && response.json[0] && !response.json[0].code; response.json = response.ok ? response.json[0] : null; return response; diff --git a/src/errors.ts b/src/errors.ts index 98cb854..4eecee7 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,6 @@ export const errors = { - INVALID_RPC_CALL: { code: 6000, message: "Invalid RPC call. Please see 'detail' property." }, - INVALID_DATA: { code: 6001, message: "Invalid data" }, + BAD_REQUEST: { code: 6000, message: "Bad request, see 'detail' field for more information" }, + RPC_VALIDATION_ERROR: { code: 6001, message: "RPC validation error, see 'errors' field for more information" }, BAD_LOGIN_CREDENTIALS: { code: 6002, message: "Bad login credentials provided" }, BAD_AUTH: { code: 6003, message: "Bad authentication" }, NOT_FOUND: { code: 6004, message: "Not found" }, diff --git a/src/routes/api/v1/rpc.ts b/src/routes/api/v1/rpc.ts index 2812104..b06f5ee 100644 --- a/src/routes/api/v1/rpc.ts +++ b/src/routes/api/v1/rpc.ts @@ -13,4 +13,22 @@ router.post( } ); +router.get( + "/", + authenticateRoute(false), + async (req, res) => { + const call = req.query.calls; + if (typeof call !== "string" || !call.length || call.length > 4500) { + return res.json({ ...errors.BAD_REQUEST, detail: "Bad 'call': expected string between 1 and 4500 characters" }); + } + let callJson; + try { + callJson = JSON.parse(call); + } catch(O_o) { + return res.json({ ...errors.BAD_REQUEST, detail: "Bad 'call': failed to parse as JSON" }); + } + res.json(await processMethodBatch(req.authenticated ? req.user : null, callJson)); + } +); + export default router; diff --git a/src/rpc/apis/users.ts b/src/rpc/apis/users.ts index 9a62ba3..df3a2d7 100644 --- a/src/rpc/apis/users.ts +++ b/src/rpc/apis/users.ts @@ -17,7 +17,7 @@ methodButWarningDoesNotAuthenticate( const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]); if (existingUser && existingUser.rowCount > 0) { return { - ...errors.INVALID_DATA, + ...errors.RPC_VALIDATION_ERROR, errors: [ { index: 0, msg: "Username already exists" } ] }; } diff --git a/src/rpc/rpc.ts b/src/rpc/rpc.ts index 74883bf..53c0b6d 100644 --- a/src/rpc/rpc.ts +++ b/src/rpc/rpc.ts @@ -56,13 +56,13 @@ export const methodButWarningDoesNotAuthenticate = (name: string, args: RPCArgum export const userInvokeMethod = async (user: User | null, methodId: number, args: any[]) => { const methodData = methods.get(methodId); if (!methodData) return { - ...errors.INVALID_RPC_CALL, + ...errors.BAD_REQUEST, detail: "The method was not found." }; const argSchema = methodData.args; if (argSchema.length !== args.length) return { - ...errors.INVALID_RPC_CALL, + ...errors.BAD_REQUEST, detail: "Invalid number of arguments provided to method." }; @@ -107,7 +107,7 @@ export const userInvokeMethod = async (user: User | null, methodId: number, args if (validationErrors.length !== 0) { return { - ...errors.INVALID_DATA, + ...errors.RPC_VALIDATION_ERROR, errors: validationErrors }; } @@ -124,7 +124,7 @@ export const userInvokeMethod = async (user: User | null, methodId: number, args export const processMethodBatch = async (user: User | null, calls: any) => { if (!Array.isArray(calls) || !calls.length || calls.length > 5) { return { - ...errors.INVALID_RPC_CALL, + ...errors.BAD_REQUEST, detail: "Expected RPC batch: an array of arrays with at least a single element and at most 5 elements, where each inner array represents a method call." }; } @@ -134,7 +134,7 @@ export const processMethodBatch = async (user: User | null, calls: any) => { calls.forEach((call, index) => { if (!Array.isArray(call) || !call.length || call.length > 8) { responses[index] = { - ...errors.INVALID_RPC_CALL, + ...errors.BAD_REQUEST, detail: "Invalid method call. Expected inner array with at least one element and at most 8 elements." }; return;