improve gateway rpc signals
This commit is contained in:
parent
dd4ea1cd79
commit
b640f742ba
12 changed files with 114 additions and 53 deletions
|
@ -2,7 +2,7 @@
|
||||||
import { onDestroy, onMount } from "svelte";
|
import { onDestroy, onMount } from "svelte";
|
||||||
import { getItem } from "../storage";
|
import { getItem } from "../storage";
|
||||||
import { messagesStoreProvider, overlayStore, setMessageInputEvent, smallViewport, typingStore, userInfoStore, usesKeyboardNavigation } from "../stores";
|
import { messagesStoreProvider, overlayStore, setMessageInputEvent, smallViewport, typingStore, userInfoStore, usesKeyboardNavigation } from "../stores";
|
||||||
import { getErrorFromResponse, methods, remoteBlobUpload, remoteCall, responseOk } from "../request";
|
import { getErrorFromResponse, methods, remoteBlobUpload, remoteCall, remoteSignal, responseOk } from "../request";
|
||||||
|
|
||||||
export let channel;
|
export let channel;
|
||||||
let messageInput = "";
|
let messageInput = "";
|
||||||
|
@ -84,7 +84,7 @@
|
||||||
for (let i = 0; i < filesToUpload.length; i++) {
|
for (let i = 0; i < filesToUpload.length; i++) {
|
||||||
const file = filesToUpload[i].file;
|
const file = filesToUpload[i].file;
|
||||||
|
|
||||||
const res = await remoteBlobUpload(methods.createMessageAttachment, file, [messageId, file.name]);
|
const res = await remoteBlobUpload({...methods.createMessageAttachment, _isSignal: true}, file, [messageId, file.name]);
|
||||||
if (!responseOk(res)) {
|
if (!responseOk(res)) {
|
||||||
const error = getErrorFromResponse(res);
|
const error = getErrorFromResponse(res);
|
||||||
const message = error.validationErrors && error.validationErrors.length ? error.validationErrors[0].msg : error.message;
|
const message = error.validationErrors && error.validationErrors.length ? error.validationErrors[0].msg : error.message;
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
const messagesStoreForChannel = messagesStoreProvider.getStore(channel.id);
|
const messagesStoreForChannel = messagesStoreProvider.getStore(channel.id);
|
||||||
messagesStoreForChannel.addMessage(optimisticMessage);
|
messagesStoreForChannel.addMessage(optimisticMessage);
|
||||||
|
|
||||||
const res = await remoteCall(methods.createChannelMessage, channel.id, content, optimisticMessageId, null, files.length);
|
const res = await remoteSignal(methods.createChannelMessage, channel.id, content, optimisticMessageId, null, files.length);
|
||||||
|
|
||||||
if (!responseOk(res)) {
|
if (!responseOk(res)) {
|
||||||
messagesStoreForChannel.deleteMessage({
|
messagesStoreForChannel.deleteMessage({
|
||||||
|
|
|
@ -125,9 +125,11 @@ export default {
|
||||||
log("ready");
|
log("ready");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GatewayPayloadType.RPCResponse: {
|
case GatewayPayloadType.RPCResponse: /* through */
|
||||||
|
default: {
|
||||||
if (this.waitingSerials.get(payload.s)) {
|
if (this.waitingSerials.get(payload.s)) {
|
||||||
(this.waitingSerials.get(payload.s))(payload.d);
|
// Any payload with `s` (sequence) can be an RPC response for a single RPC call
|
||||||
|
(this.waitingSerials.get(payload.s))(payload.t === GatewayPayloadType.RPCResponse ? payload.d : [payload.d]);
|
||||||
this.waitingSerials.delete(payload.s);
|
this.waitingSerials.delete(payload.s);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -146,7 +146,7 @@ body {
|
||||||
color: var(--foreground-color-1);
|
color: var(--foreground-color-1);
|
||||||
background-color: var(--background-color-1);
|
background-color: var(--background-color-1);
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font-family: "Open Sans Variable", "Iosevka Waffle Web", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
font-family: "Open Sans Variable", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||||
line-height: 26px;
|
line-height: 26px;
|
||||||
letter-spacing: 0.01em;
|
letter-spacing: 0.01em;
|
||||||
|
|
||||||
|
@ -537,19 +537,15 @@ body {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-button:hover {
|
.sidebar-button:hover,
|
||||||
background-color: var(--background-color-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-button.selected {
|
.sidebar-button.selected {
|
||||||
color: var(--foreground-color-1);
|
|
||||||
background-color: var(--background-color-2);
|
background-color: var(--background-color-2);
|
||||||
}
|
}
|
||||||
|
.sidebar-button.selected .sidebar-button-text {
|
||||||
.sidebar-button.selected .icon-button {
|
|
||||||
color: var(--foreground-color-1);
|
color: var(--foreground-color-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.material-icons-outlined, .material-icons {
|
.material-icons-outlined, .material-icons {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
color: var(--foreground-special-color-1);
|
color: var(--foreground-special-color-1);
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { gatewayErrors } from "../errors";
|
||||||
import { GatewayPayload } from "../types/gatewaypayload";
|
import { GatewayPayload } from "../types/gatewaypayload";
|
||||||
import { GatewayPayloadType, GatewayPresenceStatus } from "./gatewaypayloadtype";
|
import { GatewayPayloadType, GatewayPresenceStatus } from "./gatewaypayloadtype";
|
||||||
import { GatewayPresenceEntry } from "../types/gatewaypresence";
|
import { GatewayPresenceEntry } from "../types/gatewaypresence";
|
||||||
import { processMethodBatch } from "../rpc/rpc";
|
import { RPCContext, processMethodBatch } from "../rpc/rpc";
|
||||||
import { maxGatewayJsonStringByteLength, maxGatewayJsonStringLength, maxGatewayPayloadByteLength } from "../serverconfig";
|
import { maxGatewayJsonStringByteLength, maxGatewayJsonStringLength, maxGatewayPayloadByteLength } from "../serverconfig";
|
||||||
|
|
||||||
const GATEWAY_BATCH_INTERVAL = 50000;
|
const GATEWAY_BATCH_INTERVAL = 50000;
|
||||||
|
@ -159,7 +159,7 @@ function ensureFormattedGatewayPayload(payload: any): GatewayPayload | null {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class GatewayClient {
|
export class GatewayClient {
|
||||||
ws: WebSocket;
|
ws: WebSocket;
|
||||||
user?: User;
|
user?: User;
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
|
@ -427,7 +427,33 @@ class GatewayClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPCSignal is like RPCRequest however it does not send RPC method output unless there is an error
|
// RPCSignal is like RPCRequest however it does not send RPC method output unless there is an error
|
||||||
processMethodBatch(this.user, payload.d, (payload.t === GatewayPayloadType.RPCSignal ? true : false), binaryStream).then((results) => {
|
const isSignal = payload.t === GatewayPayloadType.RPCSignal;
|
||||||
|
let hasAlreadyRepliedToSelfViaDispatch = false;
|
||||||
|
|
||||||
|
let context: RPCContext | undefined = undefined;
|
||||||
|
if (isSignal && Array.isArray(payload.d) && payload.d.length === 1 && typeof payload.s === "number") {
|
||||||
|
const seq = payload.s;
|
||||||
|
context = {
|
||||||
|
isRealtime: true,
|
||||||
|
gatewayDispatch: (chan, message) => {
|
||||||
|
if (this._dispatchButSignalReplyToSelf(seq, chan, message)) {
|
||||||
|
hasAlreadyRepliedToSelfViaDispatch = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
processMethodBatch(this.user, payload.d, isSignal, binaryStream, context).then((results) => {
|
||||||
|
if (isSignal && hasAlreadyRepliedToSelfViaDispatch && Array.isArray(results) && results.length === 1 && !(typeof results[0] === "object" && !!results[0] && results[0].code)) {
|
||||||
|
// There are many signals that dispatch to all users when something was performed.
|
||||||
|
// Often times, we send a signal to the client, and the server sends *both* the signal success RPCResponse,
|
||||||
|
// as well as the message that was dispatched to all users from the signal's code.
|
||||||
|
// For some reason, this really bothers me. So, I added this hacky solution that hooks into the
|
||||||
|
// gatewayDispatch of the signal, and, if the dispatch happens to reach us, we send the sequence
|
||||||
|
// of the RPC request, thereby letting the client know "hey, we've handled this signal,
|
||||||
|
// and this is what was dispatched", which removes the need for the RPCResponse.
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.send({
|
this.send({
|
||||||
t: GatewayPayloadType.RPCResponse,
|
t: GatewayPayloadType.RPCResponse,
|
||||||
d: results,
|
d: results,
|
||||||
|
@ -441,6 +467,26 @@ class GatewayClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_dispatchButSignalReplyToSelf(seq: number, channel: string, message: GatewayPayload | ((ws: GatewayClient | null) => GatewayPayload)) {
|
||||||
|
let hasRepliedToSelf = false;
|
||||||
|
dispatch(channel, (other) => {
|
||||||
|
let effectivePayload: GatewayPayload;
|
||||||
|
if (typeof message === "function") effectivePayload = message(other);
|
||||||
|
else effectivePayload = message;
|
||||||
|
|
||||||
|
if (other === this) {
|
||||||
|
hasRepliedToSelf = true;
|
||||||
|
return {
|
||||||
|
...effectivePayload,
|
||||||
|
s: seq
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return effectivePayload;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return hasRepliedToSelf;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function(server: Server) {
|
export default function(server: Server) {
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { query } from "./database";
|
import { query } from "./database";
|
||||||
import { dispatch } from "./gateway";
|
import { dispatch } from "./gateway";
|
||||||
import { GatewayPayloadType } from "./gateway/gatewaypayloadtype";
|
import { GatewayPayloadType } from "./gateway/gatewaypayloadtype";
|
||||||
|
import { RPCContext } from "./rpc/rpc";
|
||||||
|
|
||||||
export default async function sendMessage(user: User, channelId: number, optimisticId: number | null, content: string, nickUsername: string | null, pendingAttachments: number) {
|
export default async function sendMessage(user: User, channelId: number, optimisticId: number | null, content: string, nickUsername: string | null, pendingAttachments: number, ctx: RPCContext) {
|
||||||
const authorId = user.id;
|
const authorId = user.id;
|
||||||
const createdAt = Date.now().toString();
|
const createdAt = Date.now().toString();
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ export default async function sendMessage(user: User, channelId: number, optimis
|
||||||
attachments: null
|
attachments: null
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(`channel:${channelId}`, (ws) => {
|
ctx.gatewayDispatch(`channel:${channelId}`, (ws) => {
|
||||||
let payload: any = returnObject;
|
let payload: any = returnObject;
|
||||||
if (ws && ws.user && ws.user.id === user.id && optimisticId) {
|
if (ws && ws.user && ws.user.id === user.id && optimisticId) {
|
||||||
payload = {
|
payload = {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import express from "express";
|
||||||
import { authenticateRoute, loginAttempt } from "../../auth";
|
import { authenticateRoute, loginAttempt } from "../../auth";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { getMessagesByChannelAfterPage, getMessagesByChannelFirstPage } from "../../database/templates";
|
import { getMessagesByChannelAfterPage, getMessagesByChannelFirstPage } from "../../database/templates";
|
||||||
import { waitForEvent } from "../../gateway";
|
import { dispatch, waitForEvent } from "../../gateway";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import sendMessage from "../../impl";
|
import sendMessage from "../../impl";
|
||||||
|
|
||||||
|
@ -334,7 +334,10 @@ router.put(
|
||||||
error: "Message body must be a string between 1 and 2000 characters"
|
error: "Message body must be a string between 1 and 2000 characters"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const message = await sendMessage(req.user, channelId, null, req.body.body, null, 0);
|
const message = await sendMessage(req.user, channelId, null, req.body.body, null, 0, {
|
||||||
|
isRealtime: false,
|
||||||
|
gatewayDispatch: dispatch
|
||||||
|
});
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
errcode: "M_UNKNOWN",
|
errcode: "M_UNKNOWN",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { bufferSlice, method, string, uint } from "../rpc";
|
import { RPCContext, bufferSlice, method, string, uint } from "../rpc";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { errors } from "../../errors";
|
import { errors } from "../../errors";
|
||||||
import { UploadTarget, getSafeUploadPath, sanitizeFilename, supportedImageMime } from "../../uploading";
|
import { UploadTarget, getSafeUploadPath, sanitizeFilename, supportedImageMime } from "../../uploading";
|
||||||
|
@ -16,7 +16,7 @@ const fileType = eval("import('file-type')");
|
||||||
method(
|
method(
|
||||||
"createMessageAttachment",
|
"createMessageAttachment",
|
||||||
[uint(), string(2, 128), bufferSlice()],
|
[uint(), string(2, 128), bufferSlice()],
|
||||||
async (user: User, messageId: number, filenameUnsafe: string, inputBuffer: Buffer) => {
|
async (user: User, messageId: number, filenameUnsafe: string, inputBuffer: Buffer, ctx: RPCContext) => {
|
||||||
if (inputBuffer.byteLength >= 16777220) {
|
if (inputBuffer.byteLength >= 16777220) {
|
||||||
return { ...errors.BAD_REQUEST, detail: "Uploaded file exceeds 16MiB limit." };
|
return { ...errors.BAD_REQUEST, detail: "Uploaded file exceeds 16MiB limit." };
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ method(
|
||||||
attachmentObject
|
attachmentObject
|
||||||
];
|
];
|
||||||
|
|
||||||
dispatch(`channel:${messageCheckResult.rows[0].channel_id}`, {
|
ctx.gatewayDispatch(`channel:${messageCheckResult.rows[0].channel_id}`, {
|
||||||
t: GatewayPayloadType.MessageUpdate,
|
t: GatewayPayloadType.MessageUpdate,
|
||||||
d: messageCheckResult.rows[0]
|
d: messageCheckResult.rows[0]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { channelNameRegex, method, int, string, uint, withOptional, withRegexp } from "../rpc";
|
import { channelNameRegex, method, int, string, uint, withOptional, withRegexp, RPCContext } from "../rpc";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { getMessagesByChannelFirstPage, getMessagesByChannelPage } from "../../database/templates";
|
import { getMessagesByChannelFirstPage, getMessagesByChannelPage } from "../../database/templates";
|
||||||
import { errors } from "../../errors";
|
import { errors } from "../../errors";
|
||||||
|
@ -10,7 +10,7 @@ import serverConfig from "../../serverconfig";
|
||||||
method(
|
method(
|
||||||
"createChannel",
|
"createChannel",
|
||||||
[withRegexp(channelNameRegex, string(1, 32)), withOptional(uint())],
|
[withRegexp(channelNameRegex, string(1, 32)), withOptional(uint())],
|
||||||
async (user: User, name: string, communityId: number | null) => {
|
async (user: User, name: string, communityId: number | null, ctx: RPCContext) => {
|
||||||
if (serverConfig.superuserRequirement.createChannel && !user.is_superuser) {
|
if (serverConfig.superuserRequirement.createChannel && !user.is_superuser) {
|
||||||
return errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS;
|
return errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch("*", {
|
ctx.gatewayDispatch("*", {
|
||||||
t: GatewayPayloadType.ChannelCreate,
|
t: GatewayPayloadType.ChannelCreate,
|
||||||
d: result.rows[0]
|
d: result.rows[0]
|
||||||
});
|
});
|
||||||
|
@ -36,7 +36,7 @@ method(
|
||||||
method(
|
method(
|
||||||
"updateChannelName",
|
"updateChannelName",
|
||||||
[uint(), withRegexp(channelNameRegex, string(1, 32))],
|
[uint(), withRegexp(channelNameRegex, string(1, 32))],
|
||||||
async (user: User, id: number, name: string) => {
|
async (user: User, id: number, name: string, ctx: RPCContext) => {
|
||||||
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
|
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
|
||||||
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -50,7 +50,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(`channel:${id}`, {
|
ctx.gatewayDispatch(`channel:${id}`, {
|
||||||
t: GatewayPayloadType.ChannelUpdate,
|
t: GatewayPayloadType.ChannelUpdate,
|
||||||
d: result.rows[0]
|
d: result.rows[0]
|
||||||
});
|
});
|
||||||
|
@ -62,7 +62,7 @@ method(
|
||||||
method(
|
method(
|
||||||
"deleteChannel",
|
"deleteChannel",
|
||||||
[uint()],
|
[uint()],
|
||||||
async (user: User, id: number) => {
|
async (user: User, id: number, ctx: RPCContext) => {
|
||||||
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
|
const permissionCheckResult = await query("SELECT owner_id FROM channels WHERE id = $1", [id]);
|
||||||
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -76,7 +76,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(`channel:${id}`, {
|
ctx.gatewayDispatch(`channel:${id}`, {
|
||||||
t: GatewayPayloadType.ChannelDelete,
|
t: GatewayPayloadType.ChannelDelete,
|
||||||
d: {id}
|
d: {id}
|
||||||
});
|
});
|
||||||
|
@ -111,8 +111,8 @@ method(
|
||||||
method(
|
method(
|
||||||
"createChannelMessage",
|
"createChannelMessage",
|
||||||
[uint(), string(1, 4000), withOptional(uint()), withOptional(string(1, 64)), withOptional(uint())],
|
[uint(), string(1, 4000), withOptional(uint()), withOptional(string(1, 64)), withOptional(uint())],
|
||||||
async (user: User, id: number, content: string, optimistic_id: number | null, nick_username: string | null, pending_attachments: number | null) => {
|
async (user: User, id: number, content: string, optimistic_id: number | null, nick_username: string | null, pending_attachments: number | null, ctx: RPCContext) => {
|
||||||
return await sendMessage(user, id, optimistic_id, content, nick_username, pending_attachments ?? 0);
|
return await sendMessage(user, id, optimistic_id, content, nick_username, pending_attachments ?? 0, ctx);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -139,8 +139,8 @@ method(
|
||||||
method(
|
method(
|
||||||
"putChannelTyping",
|
"putChannelTyping",
|
||||||
[uint()],
|
[uint()],
|
||||||
async (user: User, channelId: number) => {
|
async (user: User, channelId: number, ctx: RPCContext) => {
|
||||||
dispatch(`channel:${channelId}`, {
|
ctx.gatewayDispatch(`channel:${channelId}`, {
|
||||||
t: GatewayPayloadType.TypingStart,
|
t: GatewayPayloadType.TypingStart,
|
||||||
d: {
|
d: {
|
||||||
user: {
|
user: {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { channelNameRegex, method, int, string, uint, withRegexp } from "../rpc";
|
import { channelNameRegex, method, int, string, uint, withRegexp, RPCContext } from "../rpc";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { errors } from "../../errors";
|
import { errors } from "../../errors";
|
||||||
import { dispatch, dispatchChannelSubscribe } from "../../gateway";
|
import { dispatch, dispatchChannelSubscribe } from "../../gateway";
|
||||||
|
@ -8,7 +8,7 @@ import serverConfig from "../../serverconfig";
|
||||||
method(
|
method(
|
||||||
"createCommunity",
|
"createCommunity",
|
||||||
[withRegexp(channelNameRegex, string(1, 64))],
|
[withRegexp(channelNameRegex, string(1, 64))],
|
||||||
async (user: User, name: string) => {
|
async (user: User, name: string, ctx: RPCContext) => {
|
||||||
if (serverConfig.superuserRequirement.createChannel && !user.is_superuser) {
|
if (serverConfig.superuserRequirement.createChannel && !user.is_superuser) {
|
||||||
return errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS;
|
return errors.FORBIDDEN_DUE_TO_MISSING_PERMISSIONS;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch("*", {
|
ctx.gatewayDispatch("*", {
|
||||||
t: GatewayPayloadType.CommunityCreate,
|
t: GatewayPayloadType.CommunityCreate,
|
||||||
d: result.rows[0]
|
d: result.rows[0]
|
||||||
});
|
});
|
||||||
|
@ -31,7 +31,7 @@ method(
|
||||||
method(
|
method(
|
||||||
"updateCommunityName",
|
"updateCommunityName",
|
||||||
[uint(), withRegexp(channelNameRegex, string(1, 32))],
|
[uint(), withRegexp(channelNameRegex, string(1, 32))],
|
||||||
async (user: User, id: number, name: string) => {
|
async (user: User, id: number, name: string, ctx: RPCContext) => {
|
||||||
const permissionCheckResult = await query("SELECT owner_id FROM communities WHERE id = $1", [id]);
|
const permissionCheckResult = await query("SELECT owner_id FROM communities WHERE id = $1", [id]);
|
||||||
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -45,7 +45,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(`community:${id}`, {
|
ctx.gatewayDispatch(`community:${id}`, {
|
||||||
t: GatewayPayloadType.CommunityUpdate,
|
t: GatewayPayloadType.CommunityUpdate,
|
||||||
d: result.rows[0]
|
d: result.rows[0]
|
||||||
});
|
});
|
||||||
|
@ -57,7 +57,7 @@ method(
|
||||||
method(
|
method(
|
||||||
"deleteCommunity",
|
"deleteCommunity",
|
||||||
[uint()],
|
[uint()],
|
||||||
async (user: User, id: number) => {
|
async (user: User, id: number, ctx: RPCContext) => {
|
||||||
const permissionCheckResult = await query("SELECT owner_id FROM communities WHERE id = $1", [id]);
|
const permissionCheckResult = await query("SELECT owner_id FROM communities WHERE id = $1", [id]);
|
||||||
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -71,7 +71,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(`community:${id}`, {
|
ctx.gatewayDispatch(`community:${id}`, {
|
||||||
t: GatewayPayloadType.CommunityDelete,
|
t: GatewayPayloadType.CommunityDelete,
|
||||||
d: {id}
|
d: {id}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { method, string, uint } from "./../rpc";
|
import { RPCContext, method, string, uint } from "./../rpc";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { getMessageById } from "../../database/templates";
|
import { getMessageById } from "../../database/templates";
|
||||||
import { errors } from "../../errors";
|
import { errors } from "../../errors";
|
||||||
|
@ -11,7 +11,7 @@ import { UploadTarget, getSafeUploadPath } from "../../uploading";
|
||||||
method(
|
method(
|
||||||
"deleteMessage",
|
"deleteMessage",
|
||||||
[uint()],
|
[uint()],
|
||||||
async (user: User, id: number) => {
|
async (user: User, id: number, ctx: RPCContext) => {
|
||||||
const messageCheckResult = await query(getMessageById, [id]);
|
const messageCheckResult = await query(getMessageById, [id]);
|
||||||
if (!messageCheckResult || messageCheckResult.rowCount < 1) {
|
if (!messageCheckResult || messageCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -46,7 +46,7 @@ method(
|
||||||
return errors.GOT_NO_DATABASE_DATA;
|
return errors.GOT_NO_DATABASE_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(`channel:${message.channel_id}`, {
|
ctx.gatewayDispatch(`channel:${message.channel_id}`, {
|
||||||
t: GatewayPayloadType.MessageDelete,
|
t: GatewayPayloadType.MessageDelete,
|
||||||
d: {
|
d: {
|
||||||
id,
|
id,
|
||||||
|
@ -61,7 +61,7 @@ method(
|
||||||
method(
|
method(
|
||||||
"updateMessageContent",
|
"updateMessageContent",
|
||||||
[uint(), string(1, 4000)],
|
[uint(), string(1, 4000)],
|
||||||
async (user: User, id: number, content: string) => {
|
async (user: User, id: number, content: string, ctx: RPCContext) => {
|
||||||
const permissionCheckResult = await query(getMessageById, [id]);
|
const permissionCheckResult = await query(getMessageById, [id]);
|
||||||
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
if (!permissionCheckResult || permissionCheckResult.rowCount < 1) {
|
||||||
return errors.NOT_FOUND;
|
return errors.NOT_FOUND;
|
||||||
|
@ -80,7 +80,7 @@ method(
|
||||||
content
|
content
|
||||||
};
|
};
|
||||||
|
|
||||||
dispatch(`channel:${permissionCheckResult.rows[0].channel_id}`, {
|
ctx.gatewayDispatch(`channel:${permissionCheckResult.rows[0].channel_id}`, {
|
||||||
t: GatewayPayloadType.MessageUpdate,
|
t: GatewayPayloadType.MessageUpdate,
|
||||||
d: returnObject
|
d: returnObject
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { errors } from "../../errors";
|
||||||
import { query } from "../../database";
|
import { query } from "../../database";
|
||||||
import { compare, hash, hashSync } from "bcrypt";
|
import { compare, hash, hashSync } from "bcrypt";
|
||||||
import { getPublicUserObject, loginAttempt } from "../../auth";
|
import { getPublicUserObject, loginAttempt } from "../../auth";
|
||||||
import { bufferSlice, method, methodButWarningDoesNotAuthenticate, string, usernameRegex, withRegexp } from "./../rpc";
|
import { RPCContext, bufferSlice, method, methodButWarningDoesNotAuthenticate, string, usernameRegex, withRegexp } from "./../rpc";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { randomBytes } from "crypto";
|
import { randomBytes } from "crypto";
|
||||||
|
@ -92,7 +92,7 @@ const profilePictureSizes = [
|
||||||
method(
|
method(
|
||||||
"putUserAvatar",
|
"putUserAvatar",
|
||||||
[bufferSlice()],
|
[bufferSlice()],
|
||||||
async (user: User, buffer: Buffer) => {
|
async (user: User, buffer: Buffer, ctx: RPCContext) => {
|
||||||
if (buffer.byteLength >= 3145728) {
|
if (buffer.byteLength >= 3145728) {
|
||||||
// buffer exceeds 3MiB
|
// buffer exceeds 3MiB
|
||||||
return { ...errors.BAD_REQUEST, detail: "Uploaded file exceeds 3MiB limit." };
|
return { ...errors.BAD_REQUEST, detail: "Uploaded file exceeds 3MiB limit." };
|
||||||
|
@ -148,7 +148,7 @@ method(
|
||||||
|
|
||||||
user.avatar = avatarId;
|
user.avatar = avatarId;
|
||||||
|
|
||||||
dispatch("*", {
|
ctx.gatewayDispatch("*", {
|
||||||
t: GatewayPayloadType.UserUpdate,
|
t: GatewayPayloadType.UserUpdate,
|
||||||
d: getPublicUserObject(user),
|
d: getPublicUserObject(user),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { errors } from "../errors";
|
import { errors } from "../errors";
|
||||||
|
import { GatewayClient, dispatch } from "../gateway";
|
||||||
import { maxBufferByteLength } from "../serverconfig";
|
import { maxBufferByteLength } from "../serverconfig";
|
||||||
|
import { GatewayPayload } from "../types/gatewaypayload";
|
||||||
|
|
||||||
export const alphanumericRegex = new RegExp(/^[a-z0-9]+$/i);
|
export const alphanumericRegex = new RegExp(/^[a-z0-9]+$/i);
|
||||||
export const usernameRegex = new RegExp(/^[a-z0-9_]+$/i);
|
export const usernameRegex = new RegExp(/^[a-z0-9_]+$/i);
|
||||||
|
@ -19,6 +21,17 @@ export const withOptional = (arg: RPCArgument): RPCArgument => ({ ...arg, isOpti
|
||||||
const isInt = (val: any) => typeof val === "number" && Number.isSafeInteger(val);
|
const isInt = (val: any) => typeof val === "number" && Number.isSafeInteger(val);
|
||||||
const isUint = (val: any) => (isInt(val) && val >= 0);
|
const isUint = (val: any) => (isInt(val) && val >= 0);
|
||||||
|
|
||||||
|
export interface RPCContext {
|
||||||
|
isRealtime: boolean,
|
||||||
|
gatewayDispatch: (channel: string, message: GatewayPayload | ((ws: GatewayClient | null) => GatewayPayload)) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultRPCContext: RPCContext = {
|
||||||
|
isRealtime: false,
|
||||||
|
gatewayDispatch: (...a) => dispatch(...a)
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
enum RPCArgumentType {
|
enum RPCArgumentType {
|
||||||
Integer,
|
Integer,
|
||||||
String,
|
String,
|
||||||
|
@ -64,7 +77,7 @@ export const methodButWarningDoesNotAuthenticate = (name: string, args: RPCArgum
|
||||||
return method(name, args, func, false);
|
return method(name, args, func, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const userInvokeMethod = async (user: User | null, methodId: number, args: any[], buffer: Buffer | null) => {
|
export const userInvokeMethod = async (user: User | null, methodId: number, args: any[], buffer: Buffer | null, context: RPCContext) => {
|
||||||
const methodData = methods.get(methodId);
|
const methodData = methods.get(methodId);
|
||||||
if (!methodData) return {
|
if (!methodData) return {
|
||||||
...errors.BAD_REQUEST,
|
...errors.BAD_REQUEST,
|
||||||
|
@ -161,7 +174,7 @@ export const userInvokeMethod = async (user: User | null, methodId: number, args
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return await ((methodData.func)(user, ...args));
|
return await ((methodData.func)(user, ...args, context));
|
||||||
} else if (!user && !methodData.requiresAuthentication) {
|
} else if (!user && !methodData.requiresAuthentication) {
|
||||||
return await ((methodData.func)(...args));
|
return await ((methodData.func)(...args));
|
||||||
} else {
|
} else {
|
||||||
|
@ -169,7 +182,7 @@ export const userInvokeMethod = async (user: User | null, methodId: number, args
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const processMethodBatch = async (user: User | null, calls: any, ignoreNonErrors = false, buffer: Buffer | null) => {
|
export const processMethodBatch = async (user: User | null, calls: any, ignoreNonErrors = false, buffer: Buffer | null, context: RPCContext = defaultRPCContext) => {
|
||||||
if (!Array.isArray(calls) || !calls.length || calls.length > 5) {
|
if (!Array.isArray(calls) || !calls.length || calls.length > 5) {
|
||||||
return {
|
return {
|
||||||
...errors.BAD_REQUEST,
|
...errors.BAD_REQUEST,
|
||||||
|
@ -195,7 +208,7 @@ export const processMethodBatch = async (user: User | null, calls: any, ignoreNo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = userInvokeMethod(user, call[0], call.slice(1, call.length), buffer);
|
const promise = userInvokeMethod(user, call[0], call.slice(1, call.length), buffer, context);
|
||||||
promise.then(value => {
|
promise.then(value => {
|
||||||
if (ignoreNonErrors && !value.code) {
|
if (ignoreNonErrors && !value.code) {
|
||||||
responses[index] = null;
|
responses[index] = null;
|
||||||
|
|
Loading…
Reference in a new issue