Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BC-7960 - Move config api to tldraw server #105

Merged
merged 13 commits into from
Oct 22, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ data:
NODE_ENV: "production"
TZ: "Europe/Berlin"
PORT: "3046"
TLDRAW_SERVER_URL: "wss://{{ DOMAIN }}/tldraw-server"
CONFIG_PATH: "{{ '/api/tldraw/config/public' if WITH_TLDRAW2 else '/api/v3/config/public' }}"
2 changes: 1 addition & 1 deletion nginx.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ server {
set $csp "default-src 'self'; connect-src 'self' data:; base-uri 'self'; script-src 'nonce-$request_id' 'strict-dynamic' https:; object-src 'none'; font-src 'self' data:; img-src 'self' data:; style-src 'self' 'unsafe-inline';";

location /tldraw-client-runtime.config.json {
return 200 '{ "tldrawServerURL" : "${TLDRAW_SERVER_URL}" }';
return 200 '{ "CONFIG_PATH": "${CONFIG_PATH}" }';
add_header Content-Type application/json;
}

Expand Down
43 changes: 42 additions & 1 deletion src/configuration/api/api.configuration.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,49 @@
// remove this code by BC-7906
// set CONFIG_PATH to /api/tldraw/config/public
// remove line 7 and 8 in ngnix.conf.template
import { HttpStatusCode } from "../../types/StatusCodeEnums";
import { setErrorData } from "../../utils/errorData";
import { redirectToErrorPage } from "../../utils/redirectUtils";

const getConfigOptions = async (): Promise<{
CONFIG_PATH: string;
}> => {
const connectionOptions = {
CONFIG_PATH: configApiUrl(),
};

if (import.meta.env.PROD) {
try {
const response = await fetch("/tldraw-client-runtime.config.json");

if (!response.ok) {
throw new Error(`${response.status} - ${response.statusText}`);
}

const data: { CONFIG_PATH: string } = await response.json();
connectionOptions.CONFIG_PATH = data.CONFIG_PATH;
} catch (error) {
setErrorData(HttpStatusCode.InternalServerError, "error.500");
redirectToErrorPage();
}
}

return connectionOptions;
};

const configApiUrl = () => {
const configApiUrl = import.meta.env.VITE_SERVER_TLDRAW_2_ENABLED
? `/api/tldraw/config/public`
: `/api/v3/config/public`;

return configApiUrl;
};

export const API = {
FILE_UPLOAD: "/api/v3/file/upload/school/SCHOOLID/boardnodes/CONTEXTID",
FILE_DELETE: "/api/v3/file/delete/FILERECORD_ID",
FILE_RESTORE: "/api/v3/file/restore/FILERECORD_ID",
LOGIN_REDIRECT: "/login?redirect=/tldraw?parentId=PARENTID",
USER_DATA: `/api/v3/user/me`,
ENV_CONFIG: `/api/v3/config/public`,
CONFIG_PATH: await getConfigOptions().then((options) => options.CONFIG_PATH),
};
10 changes: 5 additions & 5 deletions src/hooks/useMultiplayerState.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { renderHook, act } from "@testing-library/react";
import { act, renderHook } from "@testing-library/react";
import * as Tldraw from "@tldraw/tldraw";
import {
TDAsset,
TDBinding,
TDShape,
TDUser,
TldrawApp,
TDUserStatus,
TldrawApp,
} from "@tldraw/tldraw";
import * as Tldraw from "@tldraw/tldraw";
import { useMultiplayerState } from "./useMultiplayerState";
import { doc, room, undoManager } from "../stores/setup";
import { deleteAsset, handleAssets } from "../utils/handleAssets";
import { useMultiplayerState } from "./useMultiplayerState";

vi.mock("@tldraw/tldraw", async () => {
const tldraw = await vi.importActual("@tldraw/tldraw");
Expand Down Expand Up @@ -74,7 +74,7 @@ vi.mock("../stores/setup", () => ({
},
envs: {
TLDRAW__ASSETS_ENABLED: true,
TLDRAW__ASSETS_MAX_SIZE: 1000000,
TLDRAW__ASSETS_MAX_SIZE_BYTES: 1000000,
TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST: ["image/png", "image/jpeg"],
},
}));
Expand Down
40 changes: 20 additions & 20 deletions src/hooks/useMultiplayerState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import lodash from "lodash";
import { Utils } from "@tldraw/core";
import {
TDAsset,
TDAssetType,
Expand All @@ -11,36 +11,36 @@ import {
TldrawApp,
TldrawPatch,
} from "@tldraw/tldraw";
import { Vec } from "@tldraw/vec";
import { User } from "@y-presence/client";
import lodash from "lodash";
import { useCallback, useEffect, useState } from "react";
import { toast } from "react-toastify";
import {
doc,
room,
envs,
pauseSync,
provider,
resumeSync,
room,
undoManager,
user,
yAssets,
yBindings,
yShapes,
user,
envs,
pauseSync,
resumeSync,
} from "../stores/setup";
import { STORAGE_SETTINGS_KEY } from "../utils/userSettings";
import { UserPresence } from "../types/UserPresence";
import {
importAssetsToS3,
openFromFileSystem,
} from "../utils/boardImportUtils";
import {
fileToBase64,
fileToText,
saveToFileSystem,
} from "../utils/boardExportUtils";
import {
importAssetsToS3,
openFromFileSystem,
} from "../utils/boardImportUtils";
import { uploadFileToStorage } from "../utils/fileUpload";
import { getImageBlob } from "../utils/tldrawImageExportUtils";
import { Utils } from "@tldraw/core";
import { deleteAsset, handleAssets } from "../utils/handleAssets";
import {
getImageSizeFromSrc,
getVideoSizeFromSrc,
Expand All @@ -49,8 +49,8 @@ import {
openAssetsFromFileSystem,
VIDEO_EXTENSIONS,
} from "../utils/tldrawFileUploadUtils";
import { Vec } from "@tldraw/vec";
import { deleteAsset, handleAssets } from "../utils/handleAssets";
import { getImageBlob } from "../utils/tldrawImageExportUtils";
import { STORAGE_SETTINGS_KEY } from "../utils/userSettings";

declare const window: Window & { app: TldrawApp };

Expand Down Expand Up @@ -350,21 +350,21 @@ export function useMultiplayerState({
file: File,
id: string,
): Promise<string | false> => {
if (!envs!.TLDRAW__ASSETS_ENABLED) {
if (!envs.TLDRAW__ASSETS_ENABLED) {
toast.info("Asset uploading is disabled");
return false;
}

if (file.size > envs!.TLDRAW__ASSETS_MAX_SIZE) {
if (file.size > envs.TLDRAW__ASSETS_MAX_SIZE_BYTES) {
const bytesInMb = 1048576;
const sizeInMb = envs!.TLDRAW__ASSETS_MAX_SIZE / bytesInMb;
const sizeInMb = envs.TLDRAW__ASSETS_MAX_SIZE_BYTES / bytesInMb;
toast.info(`Asset is too big - max. ${sizeInMb}MB`);
return false;
}

const isMimeTypeDisallowed =
envs!.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST &&
!envs!.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST.includes(file.type);
envs.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST &&
!envs.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST.includes(file.type);

if (isMimeTypeDisallowed) {
toast.info("Asset of this type is not allowed");
Expand Down
12 changes: 7 additions & 5 deletions src/mapper/configuration.mapper.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Envs } from "../types/Envs";
import { TypeGuard } from "../guards/type.guard";
import { Envs } from "../types/Envs";

const checkEnvType = (obj: Record<string, unknown>): void => {
TypeGuard.checkKeyAndValueExists(obj, "TLDRAW__WEBSOCKET_URL");
TypeGuard.checkKeyAndValueExists(obj, "FEATURE_TLDRAW_ENABLED");
TypeGuard.checkKeyAndValueExists(obj, "TLDRAW__ASSETS_ENABLED");
TypeGuard.checkKeyAndValueExists(obj, "TLDRAW__ASSETS_MAX_SIZE");
TypeGuard.checkKeyAndValueExists(obj, "TLDRAW__ASSETS_MAX_SIZE_BYTES");
TypeGuard.checkKeyAndValueExists(
obj,
"TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST",
);
TypeGuard.checkBoolean(obj.FEATURE_TLDRAW_ENABLED);
TypeGuard.checkBoolean(obj.FEATURE_TLDRAW_ENABLED);
TypeGuard.checkNumber(obj.TLDRAW__ASSETS_MAX_SIZE);
TypeGuard.checkNumber(obj.TLDRAW__ASSETS_MAX_SIZE_BYTES);
TypeGuard.checkArray(obj.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST);
};

Expand All @@ -27,9 +27,11 @@ export class ConfigurationMapper {
const configuration = castToEnv(obj);

const mappedConfiguration: Envs = {
TLDRAW__WEBSOCKET_URL: configuration.TLDRAW__WEBSOCKET_URL,
FEATURE_TLDRAW_ENABLED: configuration.FEATURE_TLDRAW_ENABLED,
TLDRAW__ASSETS_ENABLED: configuration.TLDRAW__ASSETS_ENABLED,
TLDRAW__ASSETS_MAX_SIZE: configuration.TLDRAW__ASSETS_MAX_SIZE,
TLDRAW__ASSETS_MAX_SIZE_BYTES:
configuration.TLDRAW__ASSETS_MAX_SIZE_BYTES,
TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST:
configuration.TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST,
};
Expand Down
32 changes: 14 additions & 18 deletions src/stores/setup.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import { TDAsset, TDBinding, TDShape } from "@tldraw/tldraw";
import { Doc, Map, UndoManager } from "yjs";
import { WebsocketProvider } from "y-websocket";
import { Room } from "@y-presence/client";
import { WebsocketProvider } from "y-websocket";
import { Doc, Map, UndoManager } from "yjs";
import { UserPresence } from "../types/UserPresence";
import { getConnectionOptions, getParentId } from "../utils/connectionOptions";
import { getEnvs } from "../utils/envConfig";
import { getUserData } from "../utils/userData";
import { clearErrorData } from "../utils/errorData";
import {
getParentId,
handleRedirectIfNotValid,
redirectToNotFoundErrorPage,
} from "../utils/redirectUtils";
import { clearErrorData } from "../utils/errorData";
import { getUserData } from "../utils/userData";
import { setDefaultState } from "../utils/userSettings";

clearErrorData();

const [connectionOptions, envs, userResult] = await Promise.all([
getConnectionOptions(),
getEnvs(),
getUserData(),
]);
const [envs, userResult] = await Promise.all([getEnvs(), getUserData()]);

handleRedirectIfNotValid(userResult, envs);

Expand All @@ -29,7 +25,7 @@ const user = userResult.user;
const parentId = getParentId();
const doc = new Doc();
const provider = new WebsocketProvider(
connectionOptions.websocketUrl,
envs?.TLDRAW__WEBSOCKET_URL,
parentId,
doc,
{
Expand Down Expand Up @@ -67,16 +63,16 @@ const resumeSync = () => {
};

export {
doc,
envs,
user,
parentId,
doc,
pauseSync,
provider,
resumeSync,
room,
yShapes,
yBindings,
yAssets,
undoManager,
pauseSync,
resumeSync,
user,
yAssets,
yBindings,
yShapes,
};
3 changes: 2 additions & 1 deletion src/types/Envs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export type Envs = {
FEATURE_TLDRAW_ENABLED: boolean;
TLDRAW__ASSETS_ENABLED: boolean;
TLDRAW__ASSETS_MAX_SIZE: number;
TLDRAW__ASSETS_MAX_SIZE_BYTES: number;
TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST: string[];
TLDRAW__WEBSOCKET_URL: string;
};
42 changes: 0 additions & 42 deletions src/utils/connectionOptions.ts

This file was deleted.

34 changes: 27 additions & 7 deletions src/utils/envConfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Envs } from "../types/Envs";
import { API } from "../configuration/api/api.configuration";
import { ConfigurationMapper } from "../mapper/configuration.mapper";
import { HttpGuard } from "../guards/http.guard";
import { ConfigurationMapper } from "../mapper/configuration.mapper";
import { Envs } from "../types/Envs";
import { HttpStatusCode } from "../types/StatusCodeEnums";
import { setErrorData } from "./errorData";
import { redirectToErrorPage } from "./redirectUtils";

// the try catch should not part of getEnvs, the place that use it must handle the errors
// should be part of a store
// Without loading the config the Promise.all should not be finished and proceed.
export const getEnvs = async (): Promise<Envs | undefined> => {
export const getEnvs = async (): Promise<Envs> => {
try {
// TODO: check order..
const response = await fetch(API.ENV_CONFIG);
const response = await fetch(API.CONFIG_PATH);
HttpGuard.checkStatusOk(response);
const responseData = await response.json();

Expand All @@ -18,8 +21,25 @@ export const getEnvs = async (): Promise<Envs | undefined> => {

return configuration;
} catch (error) {
// It should exists one place that execute the console.error in the application. A errorHandler.
// If we want to collect this informations to send it back to us, then we have currently no possibility to implement it.
console.error("Error fetching env config:", error);
if (import.meta.env.PROD) {
setErrorData(HttpStatusCode.InternalServerError, "error.500");
redirectToErrorPage();
throw error;
} else {
const configuration: Envs = {
TLDRAW__WEBSOCKET_URL: "ws://localhost:3345",
bergatco marked this conversation as resolved.
Show resolved Hide resolved
TLDRAW__ASSETS_ENABLED: true,
TLDRAW__ASSETS_MAX_SIZE_BYTES: 10485760,
TLDRAW__ASSETS_ALLOWED_MIME_TYPES_LIST: [
"image/png",
"image/jpeg",
"image/gif",
"image/svg+xml",
],
FEATURE_TLDRAW_ENABLED: true,
};

return configuration;
}
}
};
Loading