1
0
mirror of https://github.com/spacebarchat/client.git synced 2024-11-25 19:52:31 +01:00

improved logging

This commit is contained in:
Puyodead1 2023-08-10 19:21:43 -04:00
parent 0a5497bd26
commit eacc1055aa
No known key found for this signature in database
GPG Key ID: A4FA4FEC0DD353FC
15 changed files with 150 additions and 84 deletions

View File

@ -9,6 +9,7 @@ import RegistrationPage from "./pages/RegistrationPage";
import { reaction } from "mobx";
import Loader from "./components/Loader";
import { UnauthenticatedGuard } from "./components/guards/UnauthenticatedGuard";
import useLogger from "./hooks/useLogger";
import AppPage from "./pages/AppPage";
import LogoutPage from "./pages/LogoutPage";
import ChannelPage from "./pages/subpages/ChannelPage";
@ -17,6 +18,7 @@ import { Globals } from "./utils/Globals";
function App() {
const app = useAppStore();
const logger = useLogger("App");
const navigate = useNavigate();
React.useEffect(() => {
@ -30,12 +32,12 @@ function App() {
app.setGatewayReady(false);
app.gateway.connect(Globals.routeSettings.gateway);
} else {
console.debug(
logger.debug(
"Gateway connect called but socket is not closed",
);
}
} else {
console.debug("user no longer authenticated");
logger.debug("user no longer authenticated");
if (app.gateway.readyState === WebSocket.OPEN) {
app.gateway.disconnect(
1000,
@ -51,7 +53,7 @@ function App() {
Globals.load();
app.loadToken();
console.debug("Loading complete");
logger.debug("Loading complete");
app.setAppLoading(false);
return dispose;

View File

@ -1,9 +1,10 @@
import React from "react";
import styled from "styled-components";
import Channel from "../stores/objects/Channel";
import Snowflake from "../utils/Snowflake";
import useLogger from "../hooks/useLogger";
import { useAppStore } from "../stores/AppStore";
import Channel from "../stores/objects/Channel";
import User from "../stores/objects/User";
import Snowflake from "../utils/Snowflake";
const Container = styled.div`
margin-top: -8px;
@ -37,6 +38,7 @@ interface Props {
function MessageInput(props: Props) {
const app = useAppStore();
const logger = useLogger("MessageInput");
const wrapperRef = React.useRef<HTMLDivElement>(null);
const placeholderRef = React.useRef<HTMLDivElement>(null);
const inputRef = React.useRef<HTMLDivElement>(null);
@ -95,18 +97,18 @@ function MessageInput(props: Props) {
function onKeyDown(e: React.KeyboardEvent<HTMLDivElement>) {
if (!props.channel) {
console.warn("No channel selected, cannot send message");
logger.warn("No channel selected, cannot send message");
return;
}
if (e.key === "Enter") {
e.preventDefault();
const shouldFail = app.experiments.isTreatmentEnabled(
'message_queue',
"message_queue",
2,
);
const shouldSend = !app.experiments.isTreatmentEnabled(
'message_queue',
"message_queue",
1,
);

View File

@ -4,6 +4,7 @@ import React from "react";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import useLogger from "../../hooks/useLogger";
import { useAppStore } from "../../stores/AppStore";
import { messageFromFieldError } from "../../utils/messageFromFieldError";
import {
@ -74,6 +75,7 @@ type FormValues = {
function CreateServerModal() {
const app = useAppStore();
const logger = useLogger("CreateServerModal");
const { openModal, closeModal } = useModals();
const [selectedFile, setSelectedFile] = React.useState<File>();
const fileInputRef = React.useRef<HTMLInputElement>(null);
@ -132,7 +134,7 @@ function CreateServerModal() {
}
} else {
// unknown error
console.error(r);
logger.error(r);
setError("name", {
type: "manual",
message: "Unknown Error",

5
src/hooks/useLogger.ts Normal file
View File

@ -0,0 +1,5 @@
import Logger from "../utils/Logger";
export default function (name: string) {
return new Logger(name);
}

View File

@ -25,6 +25,7 @@ import {
import { Divider } from "../components/Divider";
import HCaptcha, { HeaderContainer } from "../components/HCaptcha";
import ForgotPasswordModal from "../components/modals/ForgotPasswordModal";
import useLogger from "../hooks/useLogger";
import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore";
import { Globals, RouteSettings } from "../utils/Globals";
import REST from "../utils/REST";
@ -46,6 +47,7 @@ type FormValues = {
function LoginPage() {
const app = useAppStore();
const logger = useLogger("LoginPage");
const navigate = useNavigate();
const [loading, setLoading] = React.useState(false);
const [captchaSiteKey, setCaptchaSiteKey] = React.useState<string>();
@ -97,12 +99,12 @@ function LoginPage() {
return;
} else if ("ticket" in r) {
// mfa
console.log("MFA Required", r);
logger.info("MFA Required", r);
setMfaData(r);
return;
} else {
// unknown error
console.error(r);
logger.error(r);
setError("login", {
type: "manual",
message: "Unknown Error",
@ -157,7 +159,7 @@ function LoginPage() {
resetCaptcha();
} else {
// unknown error
console.error(r);
logger.error(r);
setError("login", {
type: "manual",
message: "Unknown Error",
@ -201,7 +203,7 @@ function LoginPage() {
});
}
console.debug(`Instance lookup has set routes to`, endpoints);
logger.debug(`Instance lookup has set routes to`, endpoints);
Globals.routeSettings = endpoints; // hmm
Globals.save();
setCheckingInstance(false);

View File

@ -24,6 +24,7 @@ import {
import DOBInput from "../components/DOBInput";
import { Divider } from "../components/Divider";
import HCaptcha from "../components/HCaptcha";
import useLogger from "../hooks/useLogger";
import { AUTH_NO_BRANDING, useAppStore } from "../stores/AppStore";
import {
IAPILoginResponseSuccess,
@ -42,6 +43,7 @@ type FormValues = {
function RegistrationPage() {
const app = useAppStore();
const logger = useLogger("RegistrationPage");
const navigate = useNavigate();
const [loading, setLoading] = React.useState(false);
const [captchaSiteKey, setCaptchaSiteKey] = React.useState<string>();
@ -89,7 +91,7 @@ function RegistrationPage() {
return;
} else {
// unknown error
console.error(r);
logger.error(r);
setError("email", {
type: "manual",
message: "Unknown Error",
@ -144,7 +146,7 @@ function RegistrationPage() {
resetCaptcha();
} else {
// unknown error
console.error(r);
logger.error(r);
setError("email", {
type: "manual",
message: "Unknown Error",

View File

@ -20,6 +20,7 @@ import {
Wrapper,
} from "../../components/AuthComponents";
import { Divider } from "../../components/Divider";
import useLogger from "../../hooks/useLogger";
import { useAppStore } from "../../stores/AppStore";
import {
IAPIError,
@ -35,6 +36,7 @@ type FormValues = {
function MFA(props: IAPILoginResponseMFARequired) {
const app = useAppStore();
const logger = useLogger("MFA");
const navigate = useNavigate();
const [loading, setLoading] = React.useState(false);
@ -81,7 +83,7 @@ function MFA(props: IAPILoginResponseMFARequired) {
}
} else {
// unknown error
console.error(r);
logger.error(r);
setError("code", {
type: "manual",
message: "Unknown Error",

View File

@ -1,6 +1,7 @@
import type { APIUser } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, makeAutoObservable, observable } from "mobx";
import secureLocalStorage from "react-secure-storage";
import Logger from "../utils/Logger";
import REST from "../utils/REST";
import AccountStore from "./AccountStore";
import ExperimentsStore from "./ExperimentsStore";
@ -16,6 +17,8 @@ import UserStore from "./UserStore";
export const AUTH_NO_BRANDING = false;
export default class AppStore {
private readonly logger: Logger = new Logger("AppStore");
// whether the gateway is ready
@observable isGatewayReady = false;
// whether the app is still loading
@ -57,7 +60,7 @@ export default class AppStore {
this.tokenLoaded = true;
if (save) {
secureLocalStorage.setItem("token", token);
console.log("Token saved to storage");
this.logger.info("Token saved to storage");
}
}
@ -73,10 +76,10 @@ export default class AppStore {
this.tokenLoaded = true;
if (token) {
console.debug("Loaded token from storage.");
this.logger.debug("Loaded token from storage.");
this.setToken(token);
} else {
console.debug("No token found in storage.");
this.logger.debug("No token found in storage.");
this.setGatewayReady(true);
}
}

View File

@ -28,12 +28,14 @@ import {
Snowflake,
} from "@spacebarchat/spacebar-api-types/v9";
import { action, makeObservable, observable, runInAction } from "mobx";
import Logger from "../utils/Logger";
import AppStore from "./AppStore";
const GATEWAY_VERSION = "9";
const GATEWAY_ENCODING = "json";
export default class GatewayConnectionStore {
private readonly logger: Logger = new Logger("GatewayConnectionStore");
@observable private socket: WebSocket | null = null;
@observable private sessionId: string | null = null;
@observable public readyState: number = WebSocket.CLOSED;
@ -65,7 +67,7 @@ export default class GatewayConnectionStore {
newUrl.searchParams.append("v", GATEWAY_VERSION);
newUrl.searchParams.append("encoding", GATEWAY_ENCODING);
this.url = newUrl.href;
console.debug(`[Connect] ${this.url}`);
this.logger.debug(`[Connect] ${this.url}`);
this.connectionStartTime = Date.now();
this.socket = new WebSocket(this.url);
this.readyState = WebSocket.CONNECTING;
@ -81,7 +83,7 @@ export default class GatewayConnectionStore {
}
this.readyState = WebSocket.CLOSING;
console.debug(`[Disconnect] ${this.url}`);
this.logger.debug(`[Disconnect] ${this.url}`);
this.socket?.close(code, reason);
}
@ -144,7 +146,7 @@ export default class GatewayConnectionStore {
}
private onopen = () => {
console.debug(
this.logger.debug(
`[Connected] ${this.url} (took ${
Date.now() - this.connectionStartTime!
}ms)`,
@ -158,7 +160,7 @@ export default class GatewayConnectionStore {
private onmessage = (e: MessageEvent<any>) => {
const payload: GatewayReceivePayload = JSON.parse(e.data);
if (payload.op !== GatewayOpcodes.Dispatch) {
console.debug(`[Gateway] -> ${payload.op}`, payload);
this.logger.debug(`[Gateway] -> ${payload.op}`, payload);
}
switch (payload.op) {
@ -181,13 +183,13 @@ export default class GatewayConnectionStore {
this.handleHeartbeatAck();
break;
default:
console.debug("Received unknown opcode");
this.logger.debug("Received unknown opcode");
break;
}
};
private onerror = (e: Event) => {
console.error("[Gateway] Socket Error", e);
this.logger.error("[Gateway] Socket Error", e);
};
private onclose = (e: CloseEvent) => {
@ -197,17 +199,17 @@ export default class GatewayConnectionStore {
private sendJson = (payload: GatewaySendPayload) => {
if (!this.socket) {
console.error("Socket is not open");
this.logger.error("Socket is not open");
return;
}
if (this.socket.readyState !== WebSocket.OPEN) {
console.error(
this.logger.error(
`Socket is not open; readyState: ${this.socket.readyState}`,
);
return;
}
console.debug(`[Gateway] <- ${payload.op}`, payload);
this.logger.debug(`[Gateway] <- ${payload.op}`, payload);
this.socket.send(JSON.stringify(payload));
};
@ -215,9 +217,9 @@ export default class GatewayConnectionStore {
* Sends Identify payload to gateway
*/
private handleIdentify = () => {
console.debug("handleIdentify called");
this.logger.debug("handleIdentify called");
if (!this.app.token) {
return console.error("Token shouldn't be null here");
return this.logger.error("Token shouldn't be null here");
}
this.identifyStartTime = Date.now();
@ -250,7 +252,7 @@ export default class GatewayConnectionStore {
private handleInvalidSession = (resumable: boolean) => {
this.cleanup();
console.debug(`Received invalid session; Can Resume: ${resumable}`);
this.logger.debug(`Received invalid session; Can Resume: ${resumable}`);
if (!resumable) {
return;
}
@ -263,13 +265,13 @@ export default class GatewayConnectionStore {
*/
private handleReconnect() {
this.cleanup();
console.debug("Received reconnect");
this.logger.debug("Received reconnect");
}
private handleResume() {
console.debug("handleResume called");
this.logger.debug("handleResume called");
if (!this.app.token) {
return console.error("Token shouldn't be null here");
return this.logger.error("Token shouldn't be null here");
}
this.sendJson({
@ -284,7 +286,7 @@ export default class GatewayConnectionStore {
private handleHello = (data: GatewayHelloData) => {
this.heartbeatInterval = data.heartbeat_interval;
console.info(
this.logger.info(
`[Hello] heartbeat interval: ${data.heartbeat_interval} (took ${
Date.now() - this.connectionStartTime!
}ms)`,
@ -314,7 +316,7 @@ export default class GatewayConnectionStore {
this.cleanup();
if (code === 4004) {
console.warn("closed because of authentication failure.");
this.logger.warn("closed because of authentication failure.");
// remove token, this will send us back to the login screen
// TODO: maybe we could show a toast here so the user knows why they got logged out
this.app.logout();
@ -382,7 +384,7 @@ export default class GatewayConnectionStore {
* Handles a heartbeat timeout
*/
private handleHeartbeatTimeout = () => {
console.warn(
this.logger.warn(
`[Heartbeat ACK Timeout] should reconnect in ${(
this.heartbeatInterval! / 1000
).toFixed(2)} seconds`,
@ -403,7 +405,7 @@ export default class GatewayConnectionStore {
op: GatewayOpcodes.Heartbeat,
d: this.sequence,
};
console.debug("Sending heartbeat");
this.logger.debug("Sending heartbeat");
this.sendJson(payload);
};
@ -411,7 +413,7 @@ export default class GatewayConnectionStore {
* Stops heartbeat interval and removes socket
*/
private cleanup = () => {
console.debug("Cleaning up");
this.logger.debug("Cleaning up");
this.stopHeartbeater();
this.socket = null;
};
@ -420,7 +422,7 @@ export default class GatewayConnectionStore {
* Processes a heartbeat ack opcode
*/
private handleHeartbeatAck = () => {
console.debug("Received heartbeat ack");
this.logger.debug("Received heartbeat ack");
this.heartbeatAck = true;
};
@ -429,11 +431,11 @@ export default class GatewayConnectionStore {
*/
private handleDispatch = (data: GatewayDispatchPayload) => {
const { d, t, s } = data;
console.debug(`[Gateway] -> ${t}`, d);
this.logger.debug(`[Gateway] -> ${t}`, d);
this.sequence = s;
const handler = this.dispatchHandlers.get(t);
if (!handler) {
console.debug(`No handler for dispatch event ${t}`);
this.logger.debug(`No handler for dispatch event ${t}`);
return;
}
@ -444,14 +446,14 @@ export default class GatewayConnectionStore {
* Processes a resumed dispatch event
*/
private onResumed = () => {
console.debug("Resumed");
this.logger.debug("Resumed");
};
/**
* Processes a ready dispatch event
*/
private onReady = (data: GatewayReadyDispatchData) => {
console.info(
this.logger.info(
`[Ready] took ${Date.now() - this.connectionStartTime!}ms`,
);
const { session_id, guilds, users, user, private_channels } = data;
@ -517,7 +519,7 @@ export default class GatewayConnectionStore {
// Start dispatch handlers
private onGuildCreate = (data: GatewayGuildCreateDispatchData) => {
console.debug("Received guild create event");
this.logger.debug("Received guild create event");
runInAction(() => {
this.app.guilds.add({
...data,
@ -527,12 +529,12 @@ export default class GatewayConnectionStore {
};
private onGuildUpdate = (data: GatewayGuildModifyDispatchData) => {
console.debug("Received guild update event");
this.logger.debug("Received guild update event");
this.app.guilds.get(data.id)?.update(data);
};
private onGuildDelete = (data: GatewayGuildDeleteDispatchData) => {
console.debug("Received guild delete event");
this.logger.debug("Received guild delete event");
runInAction(() => {
this.app.guilds.remove(data.id);
});
@ -541,12 +543,14 @@ export default class GatewayConnectionStore {
private onGuildMemberListUpdate = (
data: GatewayGuildMemberListUpdateDispatchData,
) => {
console.debug("Received GuildMemberListUpdate event");
this.logger.debug("Received GuildMemberListUpdate event");
const { guild_id } = data;
const guild = this.app.guilds.get(guild_id);
if (!guild) {
console.warn(`[GuildMemberListUpdate] Guild ${guild_id} not found`);
this.logger.warn(
`[GuildMemberListUpdate] Guild ${guild_id} not found`,
);
return;
}
@ -561,7 +565,7 @@ export default class GatewayConnectionStore {
const guild = this.app.guilds.get(data.guild_id!);
if (!guild) {
console.warn(
this.logger.warn(
`[ChannelCreate] Guild ${data.guild_id} not found for channel ${data.id}`,
);
return;
@ -577,7 +581,7 @@ export default class GatewayConnectionStore {
const guild = this.app.guilds.get(data.guild_id!);
if (!guild) {
console.warn(
this.logger.warn(
`[ChannelDelete] Guild ${data.guild_id} not found for channel ${data.id}`,
);
return;
@ -588,14 +592,14 @@ export default class GatewayConnectionStore {
private onMessageCreate = (data: GatewayMessageCreateDispatchData) => {
const guild = this.app.guilds.get(data.guild_id!);
if (!guild) {
console.warn(
this.logger.warn(
`[MessageCreate] Guild ${data.guild_id} not found for channel ${data.id}`,
);
return;
}
const channel = guild.channels.get(data.channel_id);
if (!channel) {
console.warn(
this.logger.warn(
`[MessageCreate] Channel ${data.channel_id} not found for message ${data.id}`,
);
return;
@ -608,14 +612,14 @@ export default class GatewayConnectionStore {
private onMessageUpdate = (data: GatewayMessageUpdateDispatchData) => {
const guild = this.app.guilds.get(data.guild_id!);
if (!guild) {
console.warn(
this.logger.warn(
`[MessageUpdate] Guild ${data.guild_id} not found for channel ${data.id}`,
);
return;
}
const channel = guild.channels.get(data.channel_id);
if (!channel) {
console.warn(
this.logger.warn(
`[MessageUpdate] Channel ${data.channel_id} not found for message ${data.id}`,
);
return;
@ -627,14 +631,14 @@ export default class GatewayConnectionStore {
private onMessageDelete = (data: GatewayMessageDeleteDispatchData) => {
const guild = this.app.guilds.get(data.guild_id!);
if (!guild) {
console.warn(
this.logger.warn(
`[MessageDelete] Guild ${data.guild_id} not found for channel ${data.id}`,
);
return;
}
const channel = guild.channels.get(data.channel_id);
if (!channel) {
console.warn(
this.logger.warn(
`[MessageDelete] Channel ${data.channel_id} not found for message ${data.id}`,
);
return;

View File

@ -4,11 +4,13 @@ import {
GatewayGuildMemberListUpdateOperation,
} from "@spacebarchat/spacebar-api-types/v9";
import { action, observable } from "mobx";
import Logger from "../utils/Logger";
import AppStore from "./AppStore";
import Guild from "./objects/Guild";
import GuildMember from "./objects/GuildMember";
export default class GuildMemberListStore {
private readonly logger: Logger = new Logger("GuildMemberListStore");
private readonly app: AppStore;
id: string;
@ -151,11 +153,11 @@ export default class GuildMemberListStore {
// );
// }
// }
console.debug("DELETE", item);
this.logger.debug("DELETE", item);
break;
}
case GatewayGuildMemberListUpdateOperation.UPDATE: {
console.debug("UPDATE", item);
this.logger.debug("UPDATE", item);
// for (const item of items) {
// if ("group" in item) {
// // this.listData[range[0]].title = item.group.id;
@ -195,7 +197,7 @@ export default class GuildMemberListStore {
break;
}
default: {
console.warn(`Uknown OP: ${op}`);
this.logger.warn(`Uknown OP: ${op}`);
break;
}
}

View File

@ -1,9 +1,11 @@
import type { GatewayGuild } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, observable, ObservableMap } from "mobx";
import Logger from "../utils/Logger";
import AppStore from "./AppStore";
import Guild from "./objects/Guild";
export default class GuildStore {
private readonly logger: Logger = new Logger("GuildStore");
private readonly app: AppStore;
@observable initialGuildsLoaded = false;
@observable readonly guilds = new ObservableMap<string, Guild>();
@ -15,7 +17,7 @@ export default class GuildStore {
@action
setInitialGuildsLoaded() {
this.initialGuildsLoaded = true;
console.debug("Initial guilds loaded");
this.logger.debug("Initial guilds loaded");
}
@action

View File

@ -15,11 +15,13 @@ import type {
} from "@spacebarchat/spacebar-api-types/v9";
import { ChannelType, Routes } from "@spacebarchat/spacebar-api-types/v9";
import { action, computed, makeObservable, observable } from "mobx";
import Logger from "../../utils/Logger";
import { APIError } from "../../utils/interfaces/api";
import AppStore from "../AppStore";
import MessageStore from "../MessageStore";
import { APIError } from "../../utils/interfaces/api";
export default class Channel {
private readonly logger: Logger = new Logger("Channel");
private readonly app: AppStore;
id: SnowflakeType;
@ -170,7 +172,7 @@ export default class Channel {
}
this.hasFetchedMessages = true;
console.log(`Fetching messags for ${this.id}`);
this.logger.info(`Fetching messags for ${this.id}`);
app.rest
.get<RESTGetAPIChannelMessagesResult | APIError>(
Routes.channelMessages(this.id),
@ -178,7 +180,7 @@ export default class Channel {
)
.then((res) => {
if ("code" in res) {
console.error(res);
this.logger.error(res);
return;
}
this.messages.addAll(
@ -191,7 +193,7 @@ export default class Channel {
);
})
.catch((err) => {
console.error(err);
this.logger.error(err);
});
}

View File

@ -1,3 +1,5 @@
import Logger from "./Logger";
export interface RouteSettings {
api: string;
cdn: string;
@ -12,21 +14,15 @@ export const DefaultRouteSettings: RouteSettings = {
wellknown: "https://spacebar.chat",
};
const logger = new Logger("Globals");
export const Globals: {
// logger: {
// debug: (...args: unknown[]) => void;
// info: (...args: unknown[]) => void;
// warn: (...args: unknown[]) => void;
// error: (...args: unknown[]) => void;
// };
load: () => void;
save: () => void;
routeSettings: RouteSettings;
} = {
// logger: useLogger('Globals'),
load: () => {
// Globals.logger.info('Initializing Globals');
console.log("Initializing Globals");
logger.info("Initializing Globals");
const settings = localStorage.getItem("routeSettings");
if (!settings) {
@ -34,8 +30,7 @@ export const Globals: {
}
Globals.routeSettings = JSON.parse(settings);
// Globals.logger.info('Loaded route settings from storage');
console.log("Loaded route settings from storage");
logger.info("Loaded route settings from storage");
},
save: () => {
localStorage.setItem(

39
src/utils/Logger.ts Normal file
View File

@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export default class Logger {
constructor(public readonly name: string) {
this.name = name;
}
debug(...args: any[]) {
console.debug(
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
`color: LimeGreen`,
...args,
);
}
info(...args: any[]) {
console.debug(
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
`color: DodgerBlue`,
...args,
);
}
warn(...args: any[]) {
console.debug(
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
`color: Tomato`,
...args,
);
}
error(...args: any[]) {
console.debug(
`%c${new Date().toLocaleTimeString()} | ${this.name} | DEBUG |`,
`color: Red`,
...args,
);
}
}

View File

@ -4,8 +4,10 @@
import AppStore from "../stores/AppStore";
import { Globals, RouteSettings } from "./Globals";
import Logger from "./Logger";
export default class REST {
private readonly logger = new Logger("REST");
private app: AppStore;
private headers: Record<string, string>;
@ -52,7 +54,7 @@ export default class REST {
const endpoints = await fetch(
`${url.toString()}${
url.pathname.includes("api") ? "" : "api"
}/policies/instance/domains`
}/policies/instance/domains`,
).then((x) => x.json());
return {
api: endpoints.apiEndpoint,
@ -112,7 +114,7 @@ export default class REST {
): Promise<U> {
return new Promise((resolve, reject) => {
const url = REST.makeAPIUrl(path, queryParams);
console.debug(`POST ${url}; payload:`, body);
this.logger.debug(`POST ${url}; payload:`, body);
return fetch(url, {
method: "POST",
headers: this.headers,