better utilize caching for rpc

This commit is contained in:
hippoz 2023-02-22 01:06:43 +02:00
parent adee96f697
commit 6ab0eb8351
Signed by: hippoz
GPG key ID: 56C4E02A85F2FBED
5 changed files with 52 additions and 28 deletions

View file

@ -1,25 +1,26 @@
import gateway from "./gateway"; import gateway from "./gateway";
import { apiRoute, getItem } from "./storage"; 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 = { export const methods = {
// methodName: [ methodId, requiresAuthentication ] // methodName: [ methodId, requiresAuthentication ]
createUser: [ 0, false ], createUser: method(0, false),
loginUser: [ 1, false ], loginUser: method(1, false),
getUserSelf: [ 2, true ], getUserSelf: withCacheable(method(2, true)),
promoteUserSelf: [ 3, true ], promoteUserSelf: method(3, true),
createChannel: [ 4, true ], createChannel: method(4, true),
updateChannelName: [ 5, true ], updateChannelName: method(5, true),
deleteChannel: [ 6, true ], deleteChannel: method(6, true),
getChannel: [ 7, true ], getChannel: withCacheable(method(7, true)),
getChannels: [ 8, true ], getChannels: withCacheable(method(8, true)),
createChannelMessage: [ 9, true ], createChannelMessage: method(9, true),
getChannelMessages: [ 10, true ], getChannelMessages: withCacheable(method(10, true)),
putChannelTyping: [ 11, true ], putChannelTyping: method(11, true),
deleteMessage: [ 12, true ], deleteMessage: method(12, true),
updateMessageContent: [ 13, true ], updateMessageContent: method(13, true),
getMessage: [ 14, true ] getMessage: withCacheable(method(14, true))
}; };
export function compatibleFetch(endpoint, options) { 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]]; const calls = [[methodId, ...args]];
if (requiresAuthentication && gateway.authenticated) { if (requiresAuthentication && gateway.authenticated && !cacheable) {
const replies = await gateway.sendRPCRequest(calls); const replies = await gateway.sendRPCRequest(calls);
const ok = Array.isArray(replies) && replies[0] && !replies[0].code; const ok = Array.isArray(replies) && replies[0] && !replies[0].code;
return { 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.ok = response.ok && Array.isArray(response.json) && response.json[0] && !response.json[0].code;
response.json = response.ok ? response.json[0] : null; response.json = response.ok ? response.json[0] : null;
return response; return response;

View file

@ -1,6 +1,6 @@
export const errors = { export const errors = {
INVALID_RPC_CALL: { code: 6000, message: "Invalid RPC call. Please see 'detail' property." }, BAD_REQUEST: { code: 6000, message: "Bad request, see 'detail' field for more information" },
INVALID_DATA: { code: 6001, message: "Invalid data" }, 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_LOGIN_CREDENTIALS: { code: 6002, message: "Bad login credentials provided" },
BAD_AUTH: { code: 6003, message: "Bad authentication" }, BAD_AUTH: { code: 6003, message: "Bad authentication" },
NOT_FOUND: { code: 6004, message: "Not found" }, NOT_FOUND: { code: 6004, message: "Not found" },

View file

@ -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; export default router;

View file

@ -17,7 +17,7 @@ methodButWarningDoesNotAuthenticate(
const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]); const existingUser = await query("SELECT * FROM users WHERE username = $1", [username]);
if (existingUser && existingUser.rowCount > 0) { if (existingUser && existingUser.rowCount > 0) {
return { return {
...errors.INVALID_DATA, ...errors.RPC_VALIDATION_ERROR,
errors: [ { index: 0, msg: "Username already exists" } ] errors: [ { index: 0, msg: "Username already exists" } ]
}; };
} }

View file

@ -56,13 +56,13 @@ export const methodButWarningDoesNotAuthenticate = (name: string, args: RPCArgum
export const userInvokeMethod = async (user: User | null, methodId: number, args: any[]) => { export const userInvokeMethod = async (user: User | null, methodId: number, args: any[]) => {
const methodData = methods.get(methodId); const methodData = methods.get(methodId);
if (!methodData) return { if (!methodData) return {
...errors.INVALID_RPC_CALL, ...errors.BAD_REQUEST,
detail: "The method was not found." detail: "The method was not found."
}; };
const argSchema = methodData.args; const argSchema = methodData.args;
if (argSchema.length !== args.length) return { if (argSchema.length !== args.length) return {
...errors.INVALID_RPC_CALL, ...errors.BAD_REQUEST,
detail: "Invalid number of arguments provided to method." 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) { if (validationErrors.length !== 0) {
return { return {
...errors.INVALID_DATA, ...errors.RPC_VALIDATION_ERROR,
errors: validationErrors 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) => { export const processMethodBatch = async (user: User | null, calls: any) => {
if (!Array.isArray(calls) || !calls.length || calls.length > 5) { if (!Array.isArray(calls) || !calls.length || calls.length > 5) {
return { 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." 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) => { calls.forEach((call, index) => {
if (!Array.isArray(call) || !call.length || call.length > 8) { if (!Array.isArray(call) || !call.length || call.length > 8) {
responses[index] = { 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." detail: "Invalid method call. Expected inner array with at least one element and at most 8 elements."
}; };
return; return;