Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
run: npm ci

- name: Build
run: npm run prerelease
run: npm run build:metadata && npm run prerelease
Comment thread
barklund marked this conversation as resolved.
Outdated

publish:
needs: [typecheck, test, edge-integration, build]
Expand All @@ -116,14 +116,14 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Build
run: npm run prerelease

- name: Set version from tag
run: |
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
npm version --no-git-tag-version "$TAG_VERSION"

- name: Build
run: npm run prerelease

Comment thread
barklund marked this conversation as resolved.
- name: Publish to npm
run: |
if [[ ${GITHUB_REF} == *alpha* ]]; then
Expand Down
7 changes: 5 additions & 2 deletions demo/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,14 @@ window.updateStatus = () => {

if (component) {
const baseURL = component.getAttribute("baseURL");
const debugStatus = component.getDebugStatus?.();

statusElement.innerHTML = `
<strong>Current Status:</strong><br>
Base URL: ${baseURL}<br>
Component Ready: ${component.show && component.hide ? "Yes" : "No"}<br>
Custom element loaded: ${component.show && component.hide ? "Yes" : "No"}<br>
Message bridge created: ${debugStatus?.postMessageHandlerExists ? "Yes" : "No"}<br>
Assistant ready for postMessage: ${debugStatus?.postMessageHandlerReady ? "Yes" : "No"}<br>
`;
} else {
statusElement.innerHTML = "Status: Component not found";
Expand Down Expand Up @@ -450,7 +453,7 @@ document.addEventListener("DOMContentLoaded", () => {
// Initialize when component is defined
customElements.whenDefined("corti-embedded").then(() => {
window.updateStatus();
window.addLogEntry("Corti component loaded and ready", "success");
window.addLogEntry("Corti custom element loaded", "success");
component.addEventListener("error", event => {
console.log(event.detail);
});
Expand Down
14 changes: 12 additions & 2 deletions demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,21 @@ export const updateStatus = (): void => {

if (component) {
const baseURL = component.getAttribute("baseURL");
const debugStatus = (
component as unknown as {
getDebugStatus?: () => {
postMessageHandlerExists: boolean;
postMessageHandlerReady: boolean;
};
}
).getDebugStatus?.();

statusElement.innerHTML = `
<strong>Current Status:</strong><br>
Base URL: ${baseURL}<br>
Component Ready: ${typeof component.show === "function" && typeof component.hide === "function" ? "Yes" : "No"}<br>
Custom element loaded: ${typeof component.show === "function" && typeof component.hide === "function" ? "Yes" : "No"}<br>
Message bridge created: ${debugStatus?.postMessageHandlerExists ? "Yes" : "No"}<br>
Assistant ready for postMessage: ${debugStatus?.postMessageHandlerReady ? "Yes" : "No"}<br>
`;
} else {
statusElement.innerHTML = "Status: Component not found";
Expand Down Expand Up @@ -626,7 +636,7 @@ document.addEventListener("DOMContentLoaded", () => {
// Initialize when component is defined
customElements.whenDefined("corti-embedded").then(() => {
updateStatus();
addLogEntry("Corti component loaded and ready", "success");
addLogEntry("Corti custom element loaded", "success");

component?.addEventListener("event", (event: Event) => {
const { detail } = event as CustomEvent<CortiEmbeddedEventDetail>;
Expand Down
9 changes: 6 additions & 3 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ <h1>Corti Embedded Web Component Demo</h1>
}</textarea
>
</details>
<button class="postmessage-btn" id="device-link-qr-btn">Send</button>
<button class="postmessage-btn" id="device-link-qr-btn">
Send
</button>
</div>
</div>

Expand Down Expand Up @@ -280,7 +282,7 @@ <h1>Corti Embedded Web Component Demo</h1>
<div class="component-container">
<corti-embedded
id="corti-component"
baseUrl="https://assistant.eu.corti.app"
baseUrl="http://localhost:5173"
title="true"
></corti-embedded>
Comment thread
barklund marked this conversation as resolved.
</div>
Expand All @@ -296,13 +298,14 @@ <h1>Corti Embedded Web Component Demo</h1>
<div id="log"></div>
</div>
</div>
</div>

<!-- External JavaScript -->
<script src="demo.js"></script>

<!-- Component Import -->
<script type="module">
import '../dist/web-bundle.js';
import "../dist/web-bundle.js";
</script>
</body>
</html>
11 changes: 11 additions & 0 deletions demo/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,17 @@ h1 {
overflow-y: auto;
}

.auth-payload-section input {
width: 100%;
box-sizing: border-box;
padding: 6px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: monospace;
font-size: 11px;
margin-bottom: 8px;
}

.auth-payload-section label {
display: block;
margin-bottom: 8px;
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"scripts": {
"clean": "node -e \"const fs=require('fs');for(const p of ['dist','.tmp/test-dist','.tmp/tsconfig.tsbuildinfo','.tmp/tsconfig.test.tsbuildinfo'])fs.rmSync(p,{recursive:true,force:true});\"",
"analyze": "cem analyze --litelement",
"build:metadata": "node scripts/write-package-metadata.mjs",
"build:ts": "npm run clean && tsc",
"build": "npm run build:ts && npm run build:bundle && npm run build:web-bundle && npm run analyze -- --exclude dist",
"build:test-react-bundle": "esbuild test/vendor/react-test-bundle-entry.ts --bundle --format=esm --platform=browser --outfile=.tmp/test-dist/test/vendor/react-test-bundle.js --define:process.env.NODE_ENV='\"development\"'",
Expand All @@ -58,7 +59,7 @@
"prerelease": "npm run build",
"start": "npm run build && npm run build:demo && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"esbuild demo/react-demo.tsx --bundle --outfile=demo/react-demo-bundle.js --format=esm --platform=browser --jsx=automatic --loader:.tsx=tsx --watch\" \"web-dev-server\"",
"start:simple": "npm run build && concurrently -k -r \"tsc --watch --preserveWatchOutput\" \"web-dev-server\"",
"prepublish": "tsc && npm run analyze -- --exclude dist",
"prepublish": "npm run build:metadata && tsc && npm run analyze -- --exclude dist",
"lint": "eslint --ext .ts,.tsx src --ignore-path .gitignore && prettier \"src/**/*.{ts,tsx}\" --check --ignore-path .gitignore",
Comment thread
barklund marked this conversation as resolved.
Outdated
"format": "eslint --ext .ts,.tsx src --fix --ignore-path .gitignore && prettier \"src/**/*.{ts,tsx}\" --write --ignore-path .gitignore",
"test": "tsc -p tsconfig.test.json && npm run build:test-react-bundle && wtr --coverage && node scripts/print-coverage-summary.mjs",
Expand Down
14 changes: 14 additions & 0 deletions scripts/write-package-metadata.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { readFileSync, writeFileSync } from "node:fs";

const packageJsonUrl = new URL("../package.json", import.meta.url);
const metadataUrl = new URL("../src/packageMetadata.ts", import.meta.url);
const packageJson = JSON.parse(readFileSync(packageJsonUrl, "utf8"));

if (typeof packageJson.name !== "string" || typeof packageJson.version !== "string") {
throw new Error("package.json must include string name and version fields");
}

writeFileSync(
metadataUrl,
`// This file is generated by scripts/write-package-metadata.mjs.\nexport const EMBEDDED_WEB_PACKAGE_NAME = ${JSON.stringify(packageJson.name)};\nexport const EMBEDDED_WEB_PACKAGE_VERSION = ${JSON.stringify(packageJson.version)};\n`,
);
15 changes: 15 additions & 0 deletions src/CortiEmbedded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import type {
} from "./public-types.js";
import { baseStyles } from "./styles/base.js";
import { containerStyles } from "./styles/container-styles.js";
import {
EMBEDDED_WEB_PACKAGE_NAME,
EMBEDDED_WEB_PACKAGE_VERSION,
} from "./packageMetadata.js";
import { validateAndNormalizeBaseURL } from "./utils/baseUrl.js";
import { buildEmbeddedUrl, isRealEmbeddedLoad } from "./utils/embedUrl.js";
import { formatError } from "./utils/errorFormatter.js";
Expand Down Expand Up @@ -166,6 +170,17 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI {

if (iframe?.contentWindow) {
const callbacks: PostMessageHandlerCallbacks = {
onReady: async () => {
await this.postMessageHandler?.postMessage({
type: "CORTI_EMBEDDED",
version: "v1",
action: "_init",
payload: {
web_component: EMBEDDED_WEB_PACKAGE_NAME,
web_component_version: EMBEDDED_WEB_PACKAGE_VERSION,
},
});
},
onEvent: event => {
this.dispatchEmbeddedEvent(event.name, event.payload);
},
Expand Down
3 changes: 3 additions & 0 deletions src/packageMetadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// This file is generated by scripts/write-package-metadata.mjs.
export const EMBEDDED_WEB_PACKAGE_NAME = "@corti/embedded-web";
export const EMBEDDED_WEB_PACKAGE_VERSION = "0.0.1";
16 changes: 14 additions & 2 deletions src/types/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

export type APIVersion = "v1";

export type MessageType = "CORTI_EMBEDDED" | "CORTI_EMBEDDED_RESPONSE" | "CORTI_EMBEDDED_EVENT";
export type MessageType =
| "CORTI_EMBEDDED"
| "CORTI_EMBEDDED_RESPONSE"
| "CORTI_EMBEDDED_EVENT";

export type DefaultMode = "virtual" | "in-person";

export type EmbeddedAction =
| "_init"
| "auth"
Comment thread
barklund marked this conversation as resolved.
| "createInteraction"
| "addFacts"
Expand Down Expand Up @@ -80,6 +84,10 @@ export interface AuthRequest extends EmbeddedRequest {
action: "auth";
}

export interface InitRequest extends EmbeddedRequest {
action: "_init";
}

export interface CreateInteractionRequest extends EmbeddedRequest {
action: "createInteraction";
}
Expand Down Expand Up @@ -179,6 +187,7 @@ export interface UsageEvent extends DeprecatedEmbeddedEventMessage {

// Request/Response/Event type unions
export type AnyEmbeddedRequest =
| InitRequest
| AuthRequest
| CreateInteractionRequest
| AddFactsRequest
Expand Down Expand Up @@ -211,4 +220,7 @@ export type AnyDeprecatedEmbeddedEvent =

export type AnyEvent = EmbeddedEventMessage | AnyDeprecatedEmbeddedEvent;

export type AnyEmbeddedMessage = AnyEmbeddedRequest | AnyEmbeddedResponse | AnyEvent;
export type AnyEmbeddedMessage =
| AnyEmbeddedRequest
| AnyEmbeddedResponse
| AnyEvent;
26 changes: 24 additions & 2 deletions src/utils/PostMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AnyEvent, EmbeddedRequest, EmbeddedResponse } from "../types";

export interface PostMessageHandlerCallbacks {
onEvent?: (event: { name: string; payload: unknown }) => void;
onReady?: () => Promise<void> | void;
onError?: (error: {
message: string;
code?: string;
Expand Down Expand Up @@ -91,7 +92,15 @@ export class PostMessageHandler {

// Check for Corti embedded events
if (isEmbeddedEventMessage(data)) {
this.handleEvent(data);
this.handleEvent(data).catch(error => {
this.callbacks.onError?.({
message:
error instanceof Error
? error.message
: "Embedded event handling failed",
details: error,
});
});
return;
}

Expand All @@ -107,7 +116,7 @@ export class PostMessageHandler {
window.addEventListener("message", this.messageListener);
}

private handleEvent(eventData: AnyEvent): void {
private async handleEvent(eventData: AnyEvent): Promise<void> {
const eventType = eventData.event;
const { payload } = eventData;

Expand All @@ -128,6 +137,19 @@ export class PostMessageHandler {
});
}
}

try {
await this.callbacks.onReady?.();
} catch (error) {
this.callbacks.onError?.({
message:
error instanceof Error
? error.message
: "Embedded initialization failed",
details: error,
});
return;
}
}

if (eventType === "error.triggered") {
Expand Down
21 changes: 19 additions & 2 deletions src/utils/baseUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,25 @@ export function validateAndNormalizeBaseURL(url: string): string {
} catch {
throw new Error("Invalid baseURL: not a parseable URL");
}
if (parsed.protocol !== "https:") {
throw new Error("Invalid baseURL: must use https");
const isLocalhost = ["localhost", "127.0.0.1", "[::1]"].includes(
parsed.hostname.toLowerCase(),
);
if (
parsed.protocol !== "https:" &&
!(isLocalhost && parsed.protocol === "http:")
) {
throw new Error(
"Invalid baseURL: must use https unless using localhost for development",
);
}
if (isLocalhost) {
if (parsed.username || parsed.password) {
throw new Error("Invalid baseURL: must not include credentials");
}
if (parsed.pathname && parsed.pathname !== "/" && parsed.pathname !== "") {
throw new Error("Invalid baseURL: must not include a path");
}
return parsed.origin.replace(/\/+$/, "");
}
const host = parsed.host.toLowerCase();
const pattern = /^assistant\.[a-z0-9-]+\.corti\.app$/i;
Expand Down
Loading
Loading