Embed Code Generator: Fix Preview HTML and Embed Lib path for production (#2688)

* Improve logging

* Improve logging

* Keep embed origin conigurable

* Make embed URL and embed origin conigurable through env

* Gitignore public embed

* Add fingerprint to preview as well

* Fix path

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
pull/2500/head^2
Hariom Balhara 2022-05-06 21:26:26 +05:30 committed by GitHub
parent 83ec6d69eb
commit 67cc3a6409
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 180 additions and 117 deletions

View File

@ -25,6 +25,7 @@ NEXT_PUBLIC_LICENSE_CONSENT=''
NEXT_PUBLIC_WEBAPP_URL='http://localhost:3000'
# Change to 'http://localhost:3001' if running the website simultaneously
NEXT_PUBLIC_WEBSITE_URL='http://localhost:3000'
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
# To enable SAML login, set both these variables
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login

3
apps/web/.gitignore vendored
View File

@ -61,3 +61,6 @@ yarn-error.log*
# Typescript
tsconfig.tsbuildinfo
# Autogenerated embed content
public/embed

View File

@ -12,6 +12,7 @@ import { Button, Switch } from "@calcom/ui";
import { Dialog, DialogContent, DialogClose } from "@calcom/ui/Dialog";
import { InputLeading, Label, TextArea, TextField } from "@calcom/ui/form/fields";
import { WEBAPP_URL, EMBED_LIB_URL } from "@lib/config/constants";
import { trpc } from "@lib/trpc";
import NavTabs from "@components/NavTabs";
@ -216,16 +217,10 @@ const embeds: {
];
function getEmbedSnippetString() {
let embedJsUrl = "https://cal.com/embed.js";
let isLocal = false;
if (location.hostname === "localhost") {
embedJsUrl = "http://localhost:3100/dist/embed.umd.js";
isLocal = true;
}
// TODO: Import this string from @calcom/embed-snippet
return `
(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, "${embedJsUrl}", "init");
Cal("init"${isLocal ? ', {origin:"http://localhost:3000/"}' : ""});
(function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement("script")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar); return; } p(cal, ar); }; })(window, "${EMBED_LIB_URL}", "init");
Cal("init", {origin:"${WEBAPP_URL}"});
`;
}
@ -815,7 +810,7 @@ ${getEmbedTypeSpecificString().trim()}
className="border-1 h-[75vh] border"
width="100%"
height="100%"
src={`http://localhost:3100/preview.html?embedType=${embedType}&calLink=${calLink}`}
src={`${WEBAPP_URL}/embed/preview.html?embedType=${embedType}&calLink=${calLink}`}
/>
</div>
</div>

View File

@ -44,7 +44,7 @@
}
p(cal, ar);
};
})(window, "//localhost:3100/dist/embed.umd.js", "init");
})(window, "//localhost:3000/embed/embed.js", "init");
</script>
<style>

View File

@ -4,11 +4,13 @@
"description": "This is the vanilla JS core script that embeds Cal Link",
"main": "./index.ts",
"scripts": {
"build": "NEXT_PUBLIC_EMBED_FINGER_PRINT=$(git rev-parse --short HEAD) vite build && cp dist/embed.umd.js ../../../apps/website/public/embed.js && echo 'You need to commit the newly generated embed.js in apps/website'",
"build:cal": "NEXT_PUBLIC_WEBSITE_URL='https://cal.com' yarn build",
"__build": "yarn tailwind && vite build",
"__dev": "yarn __build --mode development",
"build": "NEXT_PUBLIC_EMBED_FINGER_PRINT=$(git rev-parse --short HEAD) yarn __build",
"build-preview": "PREVIEW_BUILD=1 yarn __build ",
"vite": "vite",
"tailwind": "yarn tailwindcss -i ./src/styles.css -o ./src/tailwind.generated.css",
"buildWatchAndServer": "run-p 'build --watch' 'vite --port 3100 --strict-port --open'",
"buildWatchAndServer": "run-p '__dev' 'vite --port 3100 --strict-port --open'",
"dev": "yarn tailwind && run-p 'tailwind --watch' 'buildWatchAndServer'",
"dev-real": "vite dev --port 3100",
"type-check": "tsc --pretty --noEmit",

View File

@ -1,80 +1,29 @@
<html>
<head>
<style>
.row {
display: flex;
}
.cell-1 {
border-right: 1px solid #ded9d9;
padding-right: 10px;
}
.cell-2 {
margin: 10px;
}
</style>
<script>
(function (C, A, L) {
let p = function (a, ar) {
a.q.push(ar);
};
let d = C.document;
C.Cal =
C.Cal ||
function () {
let cal = C.Cal;
let ar = arguments;
if (!cal.loaded) {
cal.ns = {};
cal.q = cal.q || [];
d.head.appendChild(d.createElement("script")).src = A;
cal.loaded = true;
}
if (ar[0] === L) {
const api = function () {
p(api, arguments);
};
const namespace = ar[1];
api.q = api.q || [];
typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
return;
}
p(cal, ar);
};
})(window, "//localhost:3100/dist/embed.umd.js", "init");
Cal("init", {
origin: "http://localhost:3000",
});
const searchParams = new URL(document.URL).searchParams;
const embedType = searchParams.get("embedType");
const calLink = searchParams.get("calLink");
</script>
<style>
.row {
display:flex;
}
.cell-1 {
border-right:1px solid #ded9d9;
padding-right:10px;
}
.cell-2 {
margin:10px;
}
</style>
<script>
const searchParams= new URL(document.URL).searchParams;
const embedType = searchParams.get("embedType");
const calLink = searchParams.get("calLink");
</script>
</head>
<script type="module" src="./src/preview.ts"></script>
<body>
<div id="my-embed" style="width:100%;height:100%;overflow:scroll"></div>
<script>
if (embedType === "inline") {
Cal("inline", {
elementOrSelector: "#my-embed",
calLink,
});
} else if (embedType === "floating-popup") {
Cal("floatingButton", {
calLink,
attributes: {
id: "my-floating-button"
}
});
} else if (embedType === "element-click") {
const button = document.createElement('button')
button.setAttribute("data-cal-link", calLink)
button.innerHTML = 'I am a button that exists on your website'
document.body.appendChild(button);
}
<div id="my-embed" style="width: 100%; height: 100%; overflow: scroll"></div>
<script type="module">
</script>
</body>
</html>

View File

@ -7,6 +7,16 @@ export interface UiConfig {
theme?: "dark" | "light" | "auto";
styles?: EmbedStyles;
}
declare global {
interface Window {
CalEmbed: {
__logQueue?: any[];
embedStore: any;
};
CalComPageStatus: string;
CalComPlan: string;
}
}
const embedStore = {
// Store all embed styles here so that as and when new elements are mounted, styles can be applied to it.
@ -37,6 +47,9 @@ if (isBrowser) {
if (isSafariBrowser) {
log("Safari Detected: Using setTimeout instead of rAF");
}
window.CalEmbed = window.CalEmbed || {};
//TODO: Send postMessage to parent to get all log messages in the same queue.
window.CalEmbed.embedStore = embedStore;
}
function runAsap(fn: (...arg: any) => void) {
@ -47,23 +60,11 @@ function runAsap(fn: (...arg: any) => void) {
return requestAnimationFrame(fn);
}
declare global {
interface Window {
CalEmbed: {
__logQueue?: any[];
};
CalComPageStatus: string;
CalComPlan: string;
}
}
function log(...args: any[]) {
if (isBrowser) {
const namespace = getNamespace();
const searchParams = new URL(document.URL).searchParams;
//TODO: Send postMessage to parent to get all log messages in the same queue.
window.CalEmbed = window.CalEmbed || {};
const logQueue = (window.CalEmbed.__logQueue = window.CalEmbed.__logQueue || []);
args.push({
ns: namespace,

View File

@ -8,6 +8,12 @@ import css from "./embed.css";
import { SdkActionManager } from "./sdk-action-manager";
import allCss from "./tailwind.generated.css";
// HACK: Redefine and don't import WEBAPP_URL as it causes import statement to be present in built file.
// This is happening because we are not able to generate an App and a lib using single Vite Config.
const WEBAPP_URL =
(import.meta.env.NEXT_PUBLIC_WEBAPP_URL_TYPO as string) ||
`https://${import.meta.env.NEXT_PUBLIC_VERCEL_URL}`;
customElements.define("cal-modal-box", ModalBox);
customElements.define("cal-floating-button", FloatingButton);
customElements.define("cal-inline", Inline);
@ -414,8 +420,8 @@ export class Cal {
constructor(namespace: string, q: InstructionQueue) {
this.__config = {
// Keep cal.com hardcoded till the time embed.js deployment to cal.com/embed.js is automated. This is to prevent accidentally pushing of localhost domain to production
origin: /*import.meta.env.NEXT_PUBLIC_WEBSITE_URL || */ "https://app.cal.com",
// Use WEBAPP_URL till full page reload problem with website URL is solved
origin: WEBAPP_URL,
};
this.namespace = namespace;
this.actionManager = new SdkActionManager(namespace);
@ -507,7 +513,11 @@ window.addEventListener("message", (e) => {
if (!parsedAction) {
return;
}
const actionManager = Cal.actionsManagers[parsedAction.ns];
globalCal.__logQueue = globalCal.__logQueue || [];
globalCal.__logQueue.push({ ...parsedAction, data: detail.data });
if (!actionManager) {
throw new Error("Unhandled Action" + parsedAction);
}

View File

@ -1,6 +1,76 @@
import { CalWindow } from "@calcom/embed-snippet";
window.addEventListener("message", (e) => {
const WEBAPP_URL =
import.meta.env.NEXT_PUBLIC_WEBAPP_URL || `https://${import.meta.env.NEXT_PUBLIC_VERCEL_URL}`;
const EMBED_LIB_URL = import.meta.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
(window as any).fingerprint = import.meta.env.NEXT_PUBLIC_EMBED_FINGER_PRINT as string;
// Install Cal Embed Code Snippet
(function (C, A, L) {
// @ts-ignore
let p = function (a, ar) {
a.q.push(ar);
};
let d = C.document;
// @ts-ignore
C.Cal =
// @ts-ignore
C.Cal ||
function () {
// @ts-ignore
let cal = C.Cal;
let ar = arguments;
if (!cal.loaded) {
cal.ns = {};
cal.q = cal.q || [];
// @ts-ignore
d.head.appendChild(d.createElement("script")).src = A;
cal.loaded = true;
}
if (ar[0] === L) {
const api = function () {
p(api, arguments);
};
const namespace = ar[1];
// @ts-ignore
api.q = api.q || [];
// @ts-ignore
typeof namespace === "string" ? (cal.ns[namespace] = api) && p(api, ar) : p(cal, ar);
return;
}
p(cal, ar);
};
})(window, EMBED_LIB_URL, "init");
const previewWindow: CalWindow = window;
previewWindow.Cal!("init", {
origin: WEBAPP_URL,
});
const searchParams = new URL(document.URL).searchParams;
const embedType = searchParams.get("embedType");
const calLink = searchParams.get("calLink");
if (embedType! === "inline") {
previewWindow.Cal!("inline", {
elementOrSelector: "#my-embed",
calLink: calLink,
});
} else if (embedType === "floating-popup") {
previewWindow.Cal!("floatingButton", {
calLink: calLink,
attributes: {
id: "my-floating-button",
},
});
} else if (embedType === "element-click") {
const button = document.createElement("button");
button.setAttribute("data-cal-link", calLink!);
button.innerHTML = "I am a button that exists on your website";
document.body.appendChild(button);
}
previewWindow.addEventListener("message", (e) => {
const data = e.data;
if (data.mode !== "cal:preview") {
return;

View File

@ -1,23 +1,39 @@
require("dotenv").config({ path: "../../../.env" });
process.env.NEXT_PUBLIC_VERCEL_URL = process.env.VERCEL_URL;
const path = require("path");
const { defineConfig } = require("vite");
module.exports = defineConfig({
envPrefix: "NEXT_PUBLIC_",
build: {
minify: "terser",
watch: {
include: ["src/**"],
},
terserOptions: {
format: {
comments: false,
module.exports = defineConfig((configEnv) => {
const config = {
envPrefix: "NEXT_PUBLIC_",
base: "/embed/",
build: {
minify: "terser",
terserOptions: {
format: {
comments: false,
},
},
rollupOptions: {
input: {
preview: path.resolve(__dirname, "preview.html"),
embed: path.resolve(__dirname, "src/embed.ts"),
},
output: {
entryFileNames: "[name].js",
//FIXME: Can't specify UMD as import because preview is an app which doesn't support `format` and this setting apply to both input
//format: "umd",
dir: "../../../apps/web/public/embed",
},
},
},
lib: {
entry: path.resolve(__dirname, "src/embed.ts"),
name: "embed",
fileName: (format) => `embed.${format}.js`,
},
},
};
if (configEnv.mode === "development") {
config.build.watch = {
include: ["src/**"],
};
}
return config;
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

@ -17,7 +17,7 @@ function App() {
</h1>
<Cal
calOrigin="http://localhost:3000"
embedJsUrl="//localhost:3100/dist/embed.umd.js"
embedJsUrl="//localhost:3000/embed/embed.js"
calLink="pro"
config={{
name: "John Doe",

View File

@ -5,6 +5,7 @@ import { defineConfig } from "vite";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
envPrefix: "NEXT_PUBLIC_",
build: {
lib: {
entry: path.resolve(__dirname, "src/index.ts"),

View File

@ -4,6 +4,10 @@
*/
import type { Cal as CalClass, InstructionQueue } from "@calcom/embed-core/src/embed";
const WEBAPP_URL = import.meta.env.NEXT_PUBLIC_WEBAPP_URL || `https://${import.meta.env.NEXT_PUBLIC_VERCEL_URL}`;
const EMBED_LIB_URL = import.meta.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
export interface GlobalCal {
(methodName: string, arg?: any): void;
/** Marks that the embed.js is loaded. Avoids re-downloading it. */
@ -15,13 +19,14 @@ export interface GlobalCal {
instance?: CalClass;
__css?: string;
fingerprint?: string;
__logQueue?: any[];
}
export interface CalWindow extends Window {
Cal?: GlobalCal;
}
export default function EmbedSnippet(url = "https://cal.com/embed.js") {
export default function EmbedSnippet(url = EMBED_LIB_URL) {
(function (C: CalWindow, A, L) {
let p = function (a: any, ar: any) {
a.q.push(ar);
@ -35,6 +40,7 @@ export default function EmbedSnippet(url = "https://cal.com/embed.js") {
if (!cal.loaded) {
cal.ns = {};
cal.q = cal.q || [];
//@ts-ignore
d.head.appendChild(d.createElement("script")).src = A;
cal.loaded = true;
}

View File

@ -1,8 +1,13 @@
require("dotenv").config({ path: "../../../.env" });
const path = require("path");
const { defineConfig } = require("vite");
process.env.NEXT_PUBLIC_VERCEL_URL = process.env.VERCEL_URL;
module.exports = defineConfig({
build: {
envPrefix: "NEXT_PUBLIC_",
lib: {
entry: path.resolve(__dirname, "src", "index.ts"),
name: "snippet",

View File

@ -5,6 +5,7 @@ export const WEBSITE_URL = process.env.NEXT_PUBLIC_WEBSITE_URL || "https://cal.c
export const CONSOLE_URL = WEBAPP_URL.startsWith("http://localhost")
? "http://localhost:3004"
: `https://console.cal.${process.env.VERCEL_ENV === "production" ? "com" : "dev"}`;
export const EMBED_LIB_URL = process.env.NEXT_PUBLIC_EMBED_LIB_URL || `${WEBAPP_URL}/embed/embed.js`;
export const IS_PRODUCTION = process.env.NODE_ENV === "production";
export const TRIAL_LIMIT_DAYS = 14;
export const HOSTED_CAL_FEATURES = process.env.HOSTED_CAL_FEATURES || BASE_URL === "https://app.cal.com";

View File

@ -75,6 +75,9 @@
"@calcom/web#start": {
"dependsOn": ["@calcom/prisma#db-deploy"]
},
"@calcom/embed-core#build": {
"cache": false
},
"@calcom/website#build": {
"dependsOn": [
"$NEXT_PUBLIC_INTERCOM_APP_ID",
@ -100,7 +103,7 @@
"db-seed": {},
"deploy": {
"cache": false,
"dependsOn": ["@calcom/web#build"],
"dependsOn": ["@calcom/web#build", "@calcom/embed-core#build"],
"outputs": []
},
"clean": {