rename user-content -> build

This commit is contained in:
Roman Pominov committed 2022-10-12 10:58:02 +04:00
1 parent 09002aaee5
commit 248eadfe49
10 files changed
+86 -91

No files matched your search

+6 -5
View File
@@ -27,11 +27,12 @@ PUBLISHER_ENDPOINT=
PUBLISHER_HOST=
PUBLISHER_TOKEN=
# Base URL for accessing user-generated content (canvas / preview / etc)
# Base origin for accessing user-generated build (canvas / preview / etc)
# Should contatin protocol and host (e.g. https://example.com)
# Will be detected automatically in development environments like localhost or preview deployment
USER_CONTENT_BASE_URL=
BUILD_ORIGIN=
# If set to `true`, user-generated content will be available
# only at {project.domain} subdomain of {USER_CONTENT_BASE_URL}
USER_CONTENT_REQUIRE_SUBDOMAIN=
# If set to `true`, user-generated build will be available
# only at {project.domain} subdomain of {BUILD_ORIGIN}.
# This is used in production deployment for security reasons.
BUILD_REQUIRE_SUBDOMAIN=
+11 -11
View File
@@ -260,7 +260,7 @@ export type DesignerProps = {
project: Project;
pages: Pages;
pageId: string;
userContentBaseUrl: string;
buildOrigin: string;
};
export const Designer = ({
@@ -268,7 +268,7 @@ export const Designer = ({
project,
pages,
pageId,
userContentBaseUrl,
buildOrigin,
}: DesignerProps) => {
useSubscribeSyncStatus();
useSubscribeRootInstance();
@@ -304,19 +304,19 @@ export const Designer = ({
return page;
}, [pages, pageId]);
const userContentUrl = new URL(userContentBaseUrl);
userContentUrl.pathname = page.path;
if (env.USER_CONTENT_REQUIRE_SUBDOMAIN) {
userContentUrl.host = `${project.domain}.${userContentUrl.host}`;
const buildUrl = new URL(buildOrigin);
buildUrl.pathname = page.path;
if (env.BUILD_REQUIRE_SUBDOMAIN) {
buildUrl.host = `${project.domain}.${buildUrl.host}`;
} else {
userContentUrl.searchParams.set("projectId", project.id);
buildUrl.searchParams.set("projectId", project.id);
}
userContentUrl.searchParams.set("mode", "edit");
const canvasUrl = userContentUrl.toString();
buildUrl.searchParams.set("mode", "edit");
const canvasUrl = buildUrl.toString();
userContentUrl.searchParams.set("mode", "preview");
const previewUrl = userContentUrl.toString();
buildUrl.searchParams.set("mode", "preview");
const previewUrl = buildUrl.toString();
return (
<ChromeWrapper isPreviewMode={isPreviewMode}>
+2 -2
View File
@@ -4,7 +4,7 @@ import { initSentry } from "./shared/sentry";
import { prisma } from "@webstudio-is/prisma-client";
import { handleRequest as handleRequestDesigner } from "./shared/remix";
import { handleRequest as handleRequestCanvas } from "@webstudio-is/react-sdk";
import { getUserContentParams } from "./shared/router-utils";
import { getBuildParams } from "./shared/router-utils";
initSentry({
integrations: [new Sentry.Integrations.Prisma({ client: prisma })],
@@ -23,7 +23,7 @@ export default function handleRequest(
(match) =>
match.route.id === "routes/$" || match.route.id === "routes/index"
) &&
getUserContentParams(request) !== undefined
getBuildParams(request) !== undefined
) {
handle = handleRequestCanvas;
}
+1 -2
View File
@@ -8,8 +8,7 @@ const env = {
DESIGNER_HOST: process.env.DESIGNER_HOST,
PUBLISHER_ENDPOINT: process.env.PUBLISHER_ENDPOINT || null,
PUBLISHER_HOST: process.env.PUBLISHER_HOST || null,
USER_CONTENT_REQUIRE_SUBDOMAIN:
process.env.USER_CONTENT_REQUIRE_SUBDOMAIN === "true",
BUILD_REQUIRE_SUBDOMAIN: process.env.BUILD_REQUIRE_SUBDOMAIN === "true",
} as const;
export default env;
+8 -11
View File
@@ -13,13 +13,10 @@ import env, { Env } from "~/env.server";
import { sentryException } from "~/shared/sentry";
import { Canvas } from "~/canvas";
import { ErrorMessage } from "~/shared/error";
import {
type CanvasRouteMode,
getUserContentParams,
} from "~/shared/router-utils";
import { type BuildMode, getBuildParams } from "~/shared/router-utils";
type Data =
| (CanvasData & { env: Env; mode: CanvasRouteMode })
| (CanvasData & { env: Env; mode: BuildMode })
| { errors: string; env: Env };
export const meta: MetaFunction = ({ data }: { data: Data }) => {
@@ -34,18 +31,18 @@ export const loader: LoaderFunction = async ({
request,
}): Promise<Data | Response> => {
try {
const userContentParams = getUserContentParams(request);
const buildParams = getBuildParams(request);
if (userContentParams === undefined) {
if (buildParams === undefined) {
return redirect(config.dashboardPath);
}
const { mode, pathname } = userContentParams;
const { mode, pathname } = buildParams;
const project =
"projectId" in userContentParams
? await db.project.loadById(userContentParams.projectId)
: await db.project.loadByDomain(userContentParams.projectDomain);
"projectId" in buildParams
? await db.project.loadById(buildParams.projectId)
: await db.project.loadByDomain(buildParams.projectDomain);
if (project === null) {
throw json("Project not found", { status: 404 });
@@ -6,7 +6,7 @@ import config from "~/config";
import { ErrorMessage } from "~/shared/error";
import { action, useAction } from "./_assets";
import { sentryException } from "~/shared/sentry";
import { getUserContentBaseUrl } from "~/shared/router-utils";
import { getBuildOrigin } from "~/shared/router-utils";
export { action, links };
@@ -35,7 +35,7 @@ export const loader: LoaderFunction = async ({
project,
pages: devBuild.pages,
pageId: pageIdParam || devBuild.pages.homePage.id,
userContentBaseUrl: getUserContentBaseUrl(request),
buildOrigin: getBuildOrigin(request),
};
} catch (error) {
sentryException({ error });
@@ -1,7 +1,4 @@
import {
getUserContentParams,
getUserContentBaseUrl,
} from "./user-content-params";
import { getBuildParams, getBuildOrigin } from "./build-params";
const makeRequest = (url: string, headers: { [key: string]: string } = {}) => ({
url,
@@ -21,8 +18,8 @@ const makeRequest = (url: string, headers: { [key: string]: string } = {}) => ({
const originalEnv = { ...process.env };
afterEach(() => {
for (const name of [
"USER_CONTENT_BASE_URL",
"USER_CONTENT_REQUIRE_SUBDOMAIN",
"BUILD_ORIGIN",
"BUILD_REQUIRE_SUBDOMAIN",
"VERCEL_ENV",
"VERCEL_URL",
"NODE_ENV",
@@ -37,31 +34,31 @@ afterEach(() => {
}
});
describe("getUserContentBaseUrl", () => {
describe("getBuildOrigin", () => {
test("Normal operation", () => {
process.env.USER_CONTENT_BASE_URL = "https://foo.com";
expect(getUserContentBaseUrl(makeRequest("https://bar.com"))).toBe(
process.env.BUILD_ORIGIN = "https://foo.com";
expect(getBuildOrigin(makeRequest("https://bar.com"))).toBe(
"https://foo.com"
);
});
test("Local development", () => {
process.env.NODE_ENV = "development";
expect(getUserContentBaseUrl(makeRequest("http://localhost"))).toBe(
expect(getBuildOrigin(makeRequest("http://localhost"))).toBe(
"http://localhost"
);
expect(getUserContentBaseUrl(makeRequest("http://localhost:1234"))).toBe(
expect(getBuildOrigin(makeRequest("http://localhost:1234"))).toBe(
"http://localhost:1234"
);
expect(getBuildOrigin(makeRequest("http://foo.localhost:1234"))).toBe(
"http://localhost:1234"
);
expect(
getUserContentBaseUrl(makeRequest("http://foo.localhost:1234"))
).toBe("http://localhost:1234");
});
test("Vercel preview", () => {
process.env.VERCEL_ENV = "preview";
process.env.VERCEL_URL = "bar.foo.com";
expect(getUserContentBaseUrl(makeRequest("https://baz.com"))).toBe(
expect(getBuildOrigin(makeRequest("https://baz.com"))).toBe(
"https://bar.foo.com"
);
});
@@ -69,60 +66,59 @@ describe("getUserContentBaseUrl", () => {
describe("getCanvasRequestParams", () => {
beforeEach(() => {
process.env.USER_CONTENT_BASE_URL = "https://foo.com";
process.env.BUILD_ORIGIN = "https://foo.com";
});
test("detects project domain", () => {
const request = makeRequest("https://bar.foo.com");
expect(
(getUserContentParams(request) as { projectDomain: string }).projectDomain
(getBuildParams(request) as { projectDomain: string }).projectDomain
).toBe("bar");
});
test("doesn't detect project domain if base domain is not a user content domain", () => {
const request = makeRequest("https://bar.baz.com");
expect(getUserContentParams(request)).toBeUndefined();
expect(getBuildParams(request)).toBeUndefined();
});
test("detects project id", () => {
const request = makeRequest("https://foo.com?projectId=123");
expect(
(getUserContentParams(request) as { projectId: string }).projectId
).toBe("123");
expect((getBuildParams(request) as { projectId: string }).projectId).toBe(
"123"
);
});
test("doesn't detect project id if domain is not a user content domain", () => {
const request = makeRequest("https://baz.com?projectId=123");
expect(getUserContentParams(request)).toBeUndefined();
expect(getBuildParams(request)).toBeUndefined();
});
test("doesn't detect project id if USER_CONTENT_REQUIRE_SUBDOMAIN is true", () => {
process.env.USER_CONTENT_REQUIRE_SUBDOMAIN = "true";
test("doesn't detect project id if BUILD_REQUIRE_SUBDOMAIN is true", () => {
process.env.BUILD_REQUIRE_SUBDOMAIN = "true";
const request = makeRequest("https://foo.com?projectId=123");
expect(getUserContentParams(request)).toBeUndefined();
expect(getBuildParams(request)).toBeUndefined();
});
test("detects path", () => {
expect(getBuildParams(makeRequest("https://bar.foo.com"))?.pathname).toBe(
"/"
);
expect(
getUserContentParams(makeRequest("https://bar.foo.com"))?.pathname
).toBe("/");
expect(
getUserContentParams(makeRequest("https://bar.foo.com/abc"))?.pathname
getBuildParams(makeRequest("https://bar.foo.com/abc"))?.pathname
).toBe("/abc");
expect(
getUserContentParams(makeRequest("https://bar.foo.com/abc/123"))?.pathname
getBuildParams(makeRequest("https://bar.foo.com/abc/123"))?.pathname
).toBe("/abc/123");
});
test("detects mode", () => {
expect(
getUserContentParams(makeRequest("https://bar.foo.com?mode=edit"))?.mode
getBuildParams(makeRequest("https://bar.foo.com?mode=edit"))?.mode
).toBe("edit");
expect(
getUserContentParams(makeRequest("https://bar.foo.com?mode=preview"))
?.mode
getBuildParams(makeRequest("https://bar.foo.com?mode=preview"))?.mode
).toBe("preview");
expect(getUserContentParams(makeRequest("https://bar.foo.com"))?.mode).toBe(
expect(getBuildParams(makeRequest("https://bar.foo.com"))?.mode).toBe(
"published"
);
});
@@ -1,6 +1,6 @@
export type CanvasRouteMode = "edit" | "preview" | "published";
const modes = ["edit", "preview", "published"] as CanvasRouteMode[];
const getMode = (url: URL): CanvasRouteMode => {
export type BuildMode = "edit" | "preview" | "published";
const modes = ["edit", "preview", "published"] as BuildMode[];
const getMode = (url: URL): BuildMode => {
const modeParam = url.searchParams.get("mode");
const mode =
modeParam === null ? "published" : modes.find((mode) => mode === modeParam);
@@ -19,16 +19,19 @@ type MinimalRequest = {
const getRequestHost = (request: MinimalRequest): string =>
request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
export const getUserContentBaseUrl = (request: MinimalRequest): string => {
const { USER_CONTENT_BASE_URL } = process.env;
if (USER_CONTENT_BASE_URL !== undefined && USER_CONTENT_BASE_URL !== "") {
return USER_CONTENT_BASE_URL;
export const getBuildOrigin = (
request: MinimalRequest,
env = process.env
): string => {
const { BUILD_ORIGIN } = env;
if (BUILD_ORIGIN !== undefined && BUILD_ORIGIN !== "") {
return BUILD_ORIGIN;
}
// Local development special case
const host = getRequestHost(request);
if (
process.env.NODE_ENV === "development" &&
env.NODE_ENV === "development" &&
/^(.*\.)?localhost(:\d+)?$/.test(host)
) {
return `http://${host.split(".").pop()}`;
@@ -36,38 +39,37 @@ export const getUserContentBaseUrl = (request: MinimalRequest): string => {
// Vercel preview special case
if (
(process.env.VERCEL_ENV === "preview" ||
process.env.VERCEL_ENV === "development") &&
typeof process.env.VERCEL_URL === "string"
(env.VERCEL_ENV === "preview" || env.VERCEL_ENV === "development") &&
typeof env.VERCEL_URL === "string"
) {
return `https://${process.env.VERCEL_URL}`;
return `https://${env.VERCEL_URL}`;
}
throw new Error("Could not determine user content host");
};
export const getUserContentParams = (
export const getBuildParams = (
request: MinimalRequest
):
| {
projectId: string;
mode: CanvasRouteMode;
mode: BuildMode;
pathname: string;
}
| {
projectDomain: string;
mode: CanvasRouteMode;
mode: BuildMode;
pathname: string;
}
| undefined => {
const url = new URL(request.url);
const requestHost = getRequestHost(request);
const userContentHost = new URL(getUserContentBaseUrl(request)).host;
const buildHost = new URL(getBuildOrigin(request)).host;
if (process.env.USER_CONTENT_REQUIRE_SUBDOMAIN !== "true") {
if (process.env.BUILD_REQUIRE_SUBDOMAIN !== "true") {
const projectId = url.searchParams.get("projectId");
if (projectId !== null && userContentHost === requestHost) {
if (projectId !== null && buildHost === requestHost) {
return {
projectId,
mode: getMode(url),
@@ -78,7 +80,7 @@ export const getUserContentParams = (
const [projectDomain, ...rest] = requestHost.split(".");
const baseHost = rest.join(".");
if (baseHost === userContentHost) {
if (baseHost === buildHost) {
return {
projectDomain,
mode: getMode(url),
@@ -1 +1 @@
export * from "./user-content-params";
export * from "./build-params";
+4 -4
View File
@@ -40,14 +40,14 @@ if (process.env.DEPLOYMENT_ENVIRONMENT === "production") {
"👉 In production DESIGNER_HOST is required for website functionality. Please set it to your production URL of the designer."
);
}
if (!process.env.USER_CONTENT_BASE_URL) {
if (!process.env.BUILD_ORIGIN) {
errors.push(
"👉 In production USER_CONTENT_BASE_URL is required for security reasons."
"👉 In production BUILD_ORIGIN is required for security reasons."
);
}
if (process.env.USER_CONTENT_REQUIRE_SUBDOMAIN !== "true") {
if (process.env.BUILD_REQUIRE_SUBDOMAIN !== "true") {
errors.push(
"👉 In production USER_CONTENT_REQUIRE_SUBDOMAIN must be set to 'true' for security reasons."
"👉 In production BUILD_REQUIRE_SUBDOMAIN must be set to 'true' for security reasons."
);
}
}