mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-07 20:44:05 +09:00
rename user-content -> build
This commit is contained in:
1 parent
09002aaee5
commit
248eadfe49
10 files changed
+86
-91
No files matched your search
@@ -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=
|
||||
@@ -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}>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 });
|
||||
|
||||
+30
-34
@@ -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"
|
||||
);
|
||||
});
|
||||
+21
-19
@@ -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";
|
||||
@@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user