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 { 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;

View file

@ -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" },

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;

View file

@ -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" } ]
};
}

View file

@ -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;