From 97275f057b7d5ebf8783dfecf48a4bc1b103cf97 Mon Sep 17 00:00:00 2001 From: Zoltan Hricz Date: Tue, 17 Mar 2026 10:43:37 +0100 Subject: [PATCH 1/2] chore: move element registration to main entry point and enforece double quotes --- package.json | 2 +- src/CortiEmbedded.ts | 182 ++++++++++++++++---------------- src/corti-embedded.ts | 6 +- src/index.ts | 30 +++++- src/react/CortiEmbeddedReact.ts | 96 ++++++++--------- src/react/index.ts | 4 +- src/styles/base.ts | 2 +- src/styles/container-styles.ts | 4 +- src/styles/theme.ts | 4 +- src/types/api.ts | 49 +++------ src/types/payloads.ts | 22 ++-- src/types/protocol.ts | 104 +++++++++--------- src/utils/PostMessageHandler.ts | 64 +++++------ src/utils/baseUrl.ts | 16 +-- src/utils/embedUrl.ts | 6 +- src/utils/errorFormatter.ts | 32 +++--- src/web-index.ts | 6 +- 17 files changed, 315 insertions(+), 314 deletions(-) diff --git a/package.json b/package.json index c8d0cba..78f7fbc 100644 --- a/package.json +++ b/package.json @@ -132,7 +132,7 @@ } }, "prettier": { - "singleQuote": true, + "singleQuote": false, "arrowParens": "avoid" } } diff --git a/src/CortiEmbedded.ts b/src/CortiEmbedded.ts index 27c1a38..4c633af 100644 --- a/src/CortiEmbedded.ts +++ b/src/CortiEmbedded.ts @@ -1,7 +1,7 @@ /* eslint-disable no-console */ -import type { Corti } from '@corti/sdk'; -import { html, LitElement, type PropertyValues } from 'lit'; -import { property } from 'lit/decorators.js'; +import type { Corti } from "@corti/sdk"; +import { html, LitElement, type PropertyValues } from "lit"; +import { property } from "lit/decorators.js"; import type { AuthResponse, AddFactsPayload, @@ -19,25 +19,25 @@ import type { GetStatusResponse, GetTemplatesResponse, KeycloakTokenResponse, -} from './types'; -import { baseStyles } from './styles/base.js'; -import { containerStyles } from './styles/container-styles.js'; -import { validateAndNormalizeBaseURL } from './utils/baseUrl.js'; -import { buildEmbeddedUrl, isRealEmbeddedLoad } from './utils/embedUrl.js'; -import { formatError } from './utils/errorFormatter.js'; +} from "./types"; +import { baseStyles } from "./styles/base.js"; +import { containerStyles } from "./styles/container-styles.js"; +import { validateAndNormalizeBaseURL } from "./utils/baseUrl.js"; +import { buildEmbeddedUrl, isRealEmbeddedLoad } from "./utils/embedUrl.js"; +import { formatError } from "./utils/errorFormatter.js"; import { PostMessageHandler, type PostMessageHandlerCallbacks, -} from './utils/PostMessageHandler.js'; +} from "./utils/PostMessageHandler.js"; const IFRAME_SANDBOX_POLICY = - 'allow-forms allow-modals allow-scripts allow-same-origin'; + "allow-forms allow-modals allow-scripts allow-same-origin"; export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { static styles = [baseStyles, containerStyles]; @property({ type: String, reflect: true }) - visibility = 'hidden'; + visibility = "hidden"; @property({ type: String, reflect: true }) baseURL!: string; @@ -50,7 +50,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { private getIframeAllowPolicy(normalizedBaseURL?: string | null): string { const permissionTarget = normalizedBaseURL ? new URL(normalizedBaseURL).origin - : '*'; + : "*"; return [ `microphone ${permissionTarget}`, @@ -58,7 +58,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { `device-capture ${permissionTarget}`, `display-capture ${permissionTarget}`, `clipboard-write ${permissionTarget}`, - ].join('; '); + ].join("; "); } connectedCallback() { @@ -67,7 +67,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { // Ensure baseURL is provided if (!this.baseURL) { this.dispatchErrorEvent({ - message: 'baseURL is required', + message: "baseURL is required", }); return; } @@ -77,7 +77,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { this.normalizedBaseURL = validateAndNormalizeBaseURL(this.baseURL); } catch (error) { this.dispatchErrorEvent({ - message: (error as Error).message || 'Invalid baseURL', + message: (error as Error).message || "Invalid baseURL", }); // Dispatch the error event rather than throwing so consumers can handle it // via the 'error' event listener without wrapping connectedCallback in try/catch. @@ -113,7 +113,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { this.postMessageHandler = new PostMessageHandler(iframe, callbacks); } else { this.dispatchErrorEvent({ - message: 'No iframe or contentWindow available', + message: "No iframe or contentWindow available", }); } } @@ -123,14 +123,14 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } private dispatchEmbeddedEvent(rawEventName: string, payload: unknown) { - if (rawEventName !== 'ready' && rawEventName !== 'loaded') { + if (rawEventName !== "ready" && rawEventName !== "loaded") { // Pass all other events through as raw DOM events for direct listeners. this.dispatchPublicEvent(rawEventName, payload); } // Always forward through the generic 'embedded-event' stream so consumers // can observe the full event feed regardless of event name. - this.dispatchPublicEvent('embedded-event', { + this.dispatchPublicEvent("embedded-event", { name: rawEventName, payload, }); @@ -141,11 +141,11 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { code?: string; details?: unknown; }) { - this.dispatchPublicEvent('error', error); + this.dispatchPublicEvent("error", error); } private isRealIframeLoad(iframe: HTMLIFrameElement): boolean { - const src = iframe.getAttribute('src') || ''; + const src = iframe.getAttribute("src") || ""; if (!this.normalizedBaseURL) return false; return isRealEmbeddedLoad(src, this.normalizedBaseURL); } @@ -169,12 +169,12 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } private getIframe(): HTMLIFrameElement | null { - return this.shadowRoot?.querySelector('iframe') || null; + return this.shadowRoot?.querySelector("iframe") || null; } protected updated(changedProps: PropertyValues) { super.updated(changedProps); - if (changedProps.has('baseURL')) { + if (changedProps.has("baseURL")) { // Tear down the existing handler; the new one is created in handleIframeLoad if (this.postMessageHandler) { this.postMessageHandler.destroy(); @@ -188,10 +188,10 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { this.normalizedBaseURL = null; const iframe = this.getIframe(); if (iframe) { - iframe.setAttribute('src', 'about:blank'); + iframe.setAttribute("src", "about:blank"); } this.dispatchErrorEvent({ - message: (error as Error).message || 'Invalid baseURL', + message: (error as Error).message || "Invalid baseURL", }); return; } @@ -200,9 +200,9 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { const iframe = this.getIframe(); if (iframe) { const expected = buildEmbeddedUrl(this.normalizedBaseURL); - iframe.setAttribute('allow', this.getIframeAllowPolicy(expected)); - if (iframe.getAttribute('src') !== expected) { - iframe.setAttribute('src', expected); + iframe.setAttribute("allow", this.getIframeAllowPolicy(expected)); + if (iframe.getAttribute("src") !== expected) { + iframe.setAttribute("src", expected); } } } @@ -217,7 +217,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async auth(credentials: KeycloakTokenResponse): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { @@ -229,16 +229,16 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { refresh_expires_in: credentials.refresh_expires_in, refresh_token: credentials.refresh_token, id_token: credentials.id_token, - 'not-before-policy': credentials['not-before-policy'], + "not-before-policy": credentials["not-before-policy"], session_state: credentials.session_state, scope: credentials.scope, profile: credentials.profile, }; const response = await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'auth', + type: "CORTI_EMBEDDED", + version: "v1", + action: "auth", payload, }); @@ -247,7 +247,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } throw new Error(response.error); } catch (error) { - const formattedError = formatError(error, 'Authentication failed'); + const formattedError = formatError(error, "Authentication failed"); throw new Error(JSON.stringify(formattedError)); } } @@ -261,14 +261,14 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { encounter: CreateInteractionPayload, ): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { const response = await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'createInteraction', + type: "CORTI_EMBEDDED", + version: "v1", + action: "createInteraction", payload: encounter, }); @@ -281,7 +281,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } throw new Error(response.error); } catch (error) { - const formattedError = formatError(error, 'Failed to create interaction'); + const formattedError = formatError(error, "Failed to create interaction"); throw new Error(JSON.stringify(formattedError)); } } @@ -293,7 +293,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async configureSession(config: SessionConfig): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { @@ -305,13 +305,13 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { }; await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'configureSession', + type: "CORTI_EMBEDDED", + version: "v1", + action: "configureSession", payload, }); } catch (error) { - const formattedError = formatError(error, 'Failed to configure session'); + const formattedError = formatError(error, "Failed to configure session"); throw new Error(JSON.stringify(formattedError)); } } @@ -323,19 +323,19 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async addFacts(facts: Corti.FactsCreateInput[]): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { const payload: AddFactsPayload = { facts }; await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'addFacts', + type: "CORTI_EMBEDDED", + version: "v1", + action: "addFacts", payload, }); } catch (error) { - const formattedError = formatError(error, 'Failed to add facts'); + const formattedError = formatError(error, "Failed to add facts"); throw new Error(JSON.stringify(formattedError)); } } @@ -347,19 +347,19 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async navigate(path: string): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { const payload: NavigatePayload = { path }; await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'navigate', + type: "CORTI_EMBEDDED", + version: "v1", + action: "navigate", payload, }); } catch (error) { - const formattedError = formatError(error, 'Failed to navigate'); + const formattedError = formatError(error, "Failed to navigate"); throw new Error(JSON.stringify(formattedError)); } } @@ -370,18 +370,18 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async startRecording(): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'startRecording', + type: "CORTI_EMBEDDED", + version: "v1", + action: "startRecording", payload: {}, }); } catch (error) { - const formattedError = formatError(error, 'Failed to start recording'); + const formattedError = formatError(error, "Failed to start recording"); throw new Error(JSON.stringify(formattedError)); } } @@ -392,18 +392,18 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async stopRecording(): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'stopRecording', + type: "CORTI_EMBEDDED", + version: "v1", + action: "stopRecording", payload: {}, }); } catch (error) { - const formattedError = formatError(error, 'Failed to stop recording'); + const formattedError = formatError(error, "Failed to stop recording"); throw new Error(JSON.stringify(formattedError)); } } @@ -419,16 +419,16 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { isAuthenticated: false, user: undefined, }, - currentUrl: '', + currentUrl: "", interaction: null, }; } try { const response = await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'getStatus', + type: "CORTI_EMBEDDED", + version: "v1", + action: "getStatus", payload: {}, }); @@ -437,7 +437,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } throw new Error(response.error); } catch (error) { - const formattedError = formatError(error, 'Failed to get status'); + const formattedError = formatError(error, "Failed to get status"); throw new Error(JSON.stringify(formattedError)); } } @@ -449,14 +449,14 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async configure(config: ConfigureAppPayload): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { const response = await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'configure', + type: "CORTI_EMBEDDED", + version: "v1", + action: "configure", payload: config, }); @@ -467,7 +467,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } catch (error) { const formattedError = formatError( error, - 'Failed to configure component', + "Failed to configure component", ); throw new Error(JSON.stringify(formattedError)); } @@ -480,21 +480,21 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async setCredentials(credentials: SetCredentialsPayload): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { if (!credentials.password) { - throw new Error('Password is required'); + throw new Error("Password is required"); } await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'setCredentials', + type: "CORTI_EMBEDDED", + version: "v1", + action: "setCredentials", payload: credentials, }); } catch (error) { - const formattedError = formatError(error, 'Failed to set credentials'); + const formattedError = formatError(error, "Failed to set credentials"); throw new Error(JSON.stringify(formattedError)); } } @@ -503,14 +503,14 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { * Show the embedded UI */ show(): void { - this.visibility = 'visible'; + this.visibility = "visible"; } /** * Hide the embedded UI */ hide(): void { - this.visibility = 'hidden'; + this.visibility = "hidden"; } /** @@ -518,14 +518,14 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { */ async getTemplates(): Promise { if (!this.postMessageHandler) { - throw new Error('Component not ready'); + throw new Error("Component not ready"); } try { const response = await this.postMessageHandler.postMessage({ - type: 'CORTI_EMBEDDED', - version: 'v1', - action: 'getTemplates', + type: "CORTI_EMBEDDED", + version: "v1", + action: "getTemplates", }); if (response.success && response.payload) { @@ -533,7 +533,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { } throw new Error(response.error); } catch (error) { - const formattedError = formatError(error, 'Failed to get templates'); + const formattedError = formatError(error, "Failed to get templates"); throw new Error(JSON.stringify(formattedError)); } } @@ -546,7 +546,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { getDebugStatus() { const iframe = this.getIframe(); return { - ready: iframe?.contentDocument?.readyState === 'complete', + ready: iframe?.contentDocument?.readyState === "complete", iframeExists: !!iframe, iframeSrc: iframe?.src, iframeContentWindow: !!iframe?.contentWindow, @@ -574,9 +574,9 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI { allow=${this.getIframeAllowPolicy(this.normalizedBaseURL)} @load=${(event: Event) => this.handleIframeLoad(event)} @unload=${() => this.postMessageHandler?.destroy()} - style=${this.visibility === 'hidden' - ? 'display: none;' - : 'display: block;'} + style=${this.visibility === "hidden" + ? "display: none;" + : "display: block;"} > `; } diff --git a/src/corti-embedded.ts b/src/corti-embedded.ts index 53dc119..bc95860 100644 --- a/src/corti-embedded.ts +++ b/src/corti-embedded.ts @@ -1,5 +1,5 @@ -import { CortiEmbedded } from './CortiEmbedded.js'; +import { CortiEmbedded } from "./CortiEmbedded.js"; // Register the main component -if (!customElements.get('corti-embedded')) - customElements.define('corti-embedded', CortiEmbedded); +if (!customElements.get("corti-embedded")) + customElements.define("corti-embedded", CortiEmbedded); diff --git a/src/index.ts b/src/index.ts index 7bcbbba..08ff59c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,32 @@ -import './corti-embedded.js'; +import "./corti-embedded.js"; +import { CortiEmbeddedAPI, CortiEmbeddedWindowAPI } from "./types/api.js"; -export { CortiEmbedded } from './CortiEmbedded.js'; +export { CortiEmbedded } from "./CortiEmbedded.js"; // Export React components -export * from './react/index.js'; +export * from "./react/index.js"; // Export clean public types only -export * from './types/index.js'; +export * from "./types/index.js"; // Export PostMessageHandler types for advanced usage -export type { PostMessageHandlerCallbacks } from './utils/PostMessageHandler.js'; +export type { PostMessageHandlerCallbacks } from "./utils/PostMessageHandler.js"; + +/** + * Type representing the corti-embedded custom element in the DOM. + * When this package is installed, tag-name based APIs like + * document.querySelector('corti-embedded') and document.createElement('corti-embedded') + * are automatically typed via HTMLElementTagNameMap. Other lookups such as getElementById + * still return HTMLElement | null and require a cast or narrowing to CortiEmbeddedElement. + */ +export type CortiEmbeddedElement = HTMLElement & CortiEmbeddedAPI; + +// Extend Window interface +declare global { + interface Window { + CortiEmbedded?: CortiEmbeddedWindowAPI; + } + interface HTMLElementTagNameMap { + "corti-embedded": CortiEmbeddedElement; + } +} diff --git a/src/react/CortiEmbeddedReact.ts b/src/react/CortiEmbeddedReact.ts index 8e57b18..2231d8a 100644 --- a/src/react/CortiEmbeddedReact.ts +++ b/src/react/CortiEmbeddedReact.ts @@ -1,7 +1,7 @@ -import * as React from 'react'; -import '../corti-embedded.js'; -import type { CortiEmbedded } from '../CortiEmbedded.js'; -import type { CortiEmbeddedAPI } from '../types'; +import * as React from "react"; +import "../corti-embedded.js"; +import type { CortiEmbedded } from "../CortiEmbedded.js"; +import type { CortiEmbeddedAPI } from "../types"; export interface CortiEmbeddedEventDetail { name: string; @@ -17,7 +17,7 @@ export interface CortiEmbeddedErrorDetail { // Props interface export interface CortiEmbeddedReactProps { baseURL: string; - visibility?: 'visible' | 'hidden'; + visibility?: "visible" | "hidden"; // Event handlers receive the unwrapped detail, not the raw CustomEvent onEvent?: (detail: CortiEmbeddedEventDetail) => void; @@ -32,7 +32,7 @@ export interface CortiEmbeddedReactProps { export type CortiEmbeddedReactRef = CortiEmbedded & CortiEmbeddedAPI; // Export public types -export * from '../types/index.js'; +export * from "../types/index.js"; // Renders the custom element directly so React sets the ref to the actual // CortiEmbedded DOM instance. This avoids the @lit/react wrapper chain that @@ -92,17 +92,17 @@ export const CortiEmbeddedReact = React.forwardRef< (e as CustomEvent).detail, ); - el.addEventListener('embedded-event', handleEvent); - el.addEventListener('embedded.ready', handleReady); - el.addEventListener('error', handleError); + el.addEventListener("embedded-event", handleEvent); + el.addEventListener("embedded.ready", handleReady); + el.addEventListener("error", handleError); return () => { - el.removeEventListener('embedded-event', handleEvent); - el.removeEventListener('embedded.ready', handleReady); - el.removeEventListener('error', handleError); + el.removeEventListener("embedded-event", handleEvent); + el.removeEventListener("embedded.ready", handleReady); + el.removeEventListener("error", handleError); }; }, []); - return React.createElement('corti-embedded', { + return React.createElement("corti-embedded", { ref: internalRef, baseurl: baseURL, ...(visibility !== undefined ? { visibility } : {}), @@ -112,7 +112,7 @@ export const CortiEmbeddedReact = React.forwardRef< }, ); -CortiEmbeddedReact.displayName = 'CortiEmbeddedReact'; +CortiEmbeddedReact.displayName = "CortiEmbeddedReact"; export interface UseCortiEmbeddedStatusOptions { enabled?: boolean; @@ -121,7 +121,7 @@ export interface UseCortiEmbeddedStatusOptions { } export interface UseCortiEmbeddedStatusResult { - status: Awaited> | null; + status: Awaited> | null; isLoading: boolean; error: unknown; lastEvent: CortiEmbeddedEventDetail | null; @@ -136,17 +136,17 @@ export function useCortiEmbeddedStatus( onError, shouldRefreshOnEvent = event => { const normalized = event.name.toLowerCase(); - if (normalized.includes('getstatus')) return false; - if (normalized.includes('statusreturned')) return false; + if (normalized.includes("getstatus")) return false; + if (normalized.includes("statusreturned")) return false; return true; }, } = options; const [status, setStatus] = - React.useState(null); + React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const [error, setError] = React.useState(null); const [lastEvent, setLastEvent] = - React.useState(null); + React.useState(null); const onErrorRef = React.useRef(onError); const shouldRefreshOnEventRef = React.useRef(shouldRefreshOnEvent); const isRefreshingRef = React.useRef(false); @@ -188,8 +188,8 @@ export function useCortiEmbeddedStatus( refresh(); }; - target.addEventListener('embedded-event', handleEvent); - return () => target.removeEventListener('embedded-event', handleEvent); + target.addEventListener("embedded-event", handleEvent); + return () => target.removeEventListener("embedded-event", handleEvent); }, [enabled, ref, refresh]); return { @@ -202,44 +202,44 @@ export function useCortiEmbeddedStatus( export interface UseCortiEmbeddedApiResult { auth: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; createInteraction: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; configureSession: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; addFacts: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; navigate: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; startRecording: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; stopRecording: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; getStatus: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; configure: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; getTemplates: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; setCredentials: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; show: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; hide: ( - ...args: Parameters - ) => ReturnType; + ...args: Parameters + ) => ReturnType; } function getCortiEmbeddedInstanceFromRefOrThrow( @@ -248,7 +248,7 @@ function getCortiEmbeddedInstanceFromRefOrThrow( const instance = ref.current; if (!instance) { throw new Error( - 'No active corti-embedded instance found for this ref. Mount first.', + "No active corti-embedded instance found for this ref. Mount first.", ); } return instance; diff --git a/src/react/index.ts b/src/react/index.ts index 95c27b4..1c07515 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -1,5 +1,5 @@ -export * from './CortiEmbeddedReact.js'; +export * from "./CortiEmbeddedReact.js"; export type { CortiEmbeddedReactProps, CortiEmbeddedReactRef, -} from './CortiEmbeddedReact.js'; +} from "./CortiEmbeddedReact.js"; diff --git a/src/styles/base.ts b/src/styles/base.ts index 06aea38..b5b5d5f 100644 --- a/src/styles/base.ts +++ b/src/styles/base.ts @@ -1,4 +1,4 @@ -import { css } from 'lit'; +import { css } from "lit"; /** * Base styles to be applied to all components within the application. diff --git a/src/styles/container-styles.ts b/src/styles/container-styles.ts index c34cf3b..584d57e 100644 --- a/src/styles/container-styles.ts +++ b/src/styles/container-styles.ts @@ -1,4 +1,4 @@ -import { css } from 'lit'; +import { css } from "lit"; // Main container styles for the Corti Agent component export const containerStyles = css` @@ -20,7 +20,7 @@ export const containerStyles = css` } /* Handle visibility state */ - :host([visibility='hidden']) { + :host([visibility="hidden"]) { display: none; } diff --git a/src/styles/theme.ts b/src/styles/theme.ts index e6c5135..03b2417 100644 --- a/src/styles/theme.ts +++ b/src/styles/theme.ts @@ -1,10 +1,10 @@ -import { css } from 'lit'; +import { css } from "lit"; const ThemeStyles = css` :host { color-scheme: light dark; /* Component Defaults */ - --component-font-family: 'Segoe UI', Roboto, sans-serif; + --component-font-family: "Segoe UI", Roboto, sans-serif; /* Plain (Default) Colors */ --plain-bg-color: light-dark(#f5f5f5, #3a3a3a); diff --git a/src/types/api.ts b/src/types/api.ts index 33c6bac..19ab9aa 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -1,6 +1,6 @@ // Public API types for SDK consumers -import type { ConfigureAppPayload } from './config.js'; +import type { ConfigureAppPayload } from "./config.js"; import type { AuthChangedEventPayload, DocumentEventPayload, @@ -8,7 +8,7 @@ import type { InteractionCreatedEventPayload, NavigationChangedEventPayload, UsageEventPayload, -} from './events.js'; +} from "./events.js"; import type { AddFactsPayload, ConfigureSessionPayload, @@ -17,19 +17,19 @@ import type { KeycloakTokenResponse, NavigatePayload, SetCredentialsPayload, -} from './payloads.js'; -import type { DefaultMode } from './protocol.js'; +} from "./payloads.js"; +import type { DefaultMode } from "./protocol.js"; import type { AuthResponse, ConfigureAppResponse, CreateInteractionResponse, GetStatusResponse, GetTemplatesResponse, -} from './responses.js'; +} from "./responses.js"; -export type { ConfigureAppPayload } from './config.js'; +export type { ConfigureAppPayload } from "./config.js"; // Re-export common types for public API -export type { UserInfo } from './responses.js'; +export type { UserInfo } from "./responses.js"; /** * User information returned from authentication @@ -62,14 +62,14 @@ export interface SessionConfig { */ export interface EmbeddedEventData { ready: undefined; - 'auth-changed': AuthChangedEventPayload; - 'interaction-created': InteractionCreatedEventPayload; - 'recording-started': undefined; - 'recording-stopped': undefined; - 'document-generated': DocumentEventPayload; - 'document-updated': DocumentEventPayload; - 'document-synced': DocumentEventPayload; - 'navigation-changed': NavigationChangedEventPayload; + "auth-changed": AuthChangedEventPayload; + "interaction-created": InteractionCreatedEventPayload; + "recording-started": undefined; + "recording-stopped": undefined; + "document-generated": DocumentEventPayload; + "document-updated": DocumentEventPayload; + "document-synced": DocumentEventPayload; + "navigation-changed": NavigationChangedEventPayload; usage: UsageEventPayload; error: ErrorEventPayload; } @@ -188,22 +188,3 @@ export interface CortiEmbeddedAPI { */ hide(): void; } - -/** - * Type representing the corti-embedded custom element in the DOM. - * When this package is installed, tag-name based APIs like - * document.querySelector('corti-embedded') and document.createElement('corti-embedded') - * are automatically typed via HTMLElementTagNameMap. Other lookups such as getElementById - * still return HTMLElement | null and require a cast or narrowing to CortiEmbeddedElement. - */ -export type CortiEmbeddedElement = HTMLElement & CortiEmbeddedAPI; - -// Extend Window interface -declare global { - interface Window { - CortiEmbedded?: CortiEmbeddedWindowAPI; - } - interface HTMLElementTagNameMap { - 'corti-embedded': CortiEmbeddedElement; - } -} diff --git a/src/types/payloads.ts b/src/types/payloads.ts index ffb107c..9f05fdb 100644 --- a/src/types/payloads.ts +++ b/src/types/payloads.ts @@ -1,7 +1,7 @@ // Payload types for embedded API requests and responses -import type { Corti } from '@corti/sdk'; +import type { Corti } from "@corti/sdk"; -import type { DefaultMode } from './protocol.js'; +import type { DefaultMode } from "./protocol.js"; // Keycloak token structure export interface KeycloakTokenResponse { @@ -12,7 +12,7 @@ export interface KeycloakTokenResponse { refresh_expires_in?: number | null; refresh_token?: string; id_token?: string; - 'not-before-policy'?: number | null; + "not-before-policy"?: number | null; session_state?: string; scope?: string; profile?: { @@ -24,18 +24,18 @@ export interface KeycloakTokenResponse { // Fact structure export interface Fact { - text: Corti.FactsCreateInput['text']; + text: Corti.FactsCreateInput["text"]; group: string; - source?: Corti.FactsCreateInput['source']; + source?: Corti.FactsCreateInput["source"]; } // Create interaction payload export interface CreateInteractionPayload { - assignedUserId?: Corti.InteractionsCreateRequest['assignedUserId'] | null; + assignedUserId?: Corti.InteractionsCreateRequest["assignedUserId"] | null; encounter: { - identifier: Corti.InteractionsCreateRequest['encounter']['identifier']; - status: Corti.InteractionsCreateRequest['encounter']['status']; - type: Corti.InteractionsCreateRequest['encounter']['type']; + identifier: Corti.InteractionsCreateRequest["encounter"]["identifier"]; + status: Corti.InteractionsCreateRequest["encounter"]["status"]; + type: Corti.InteractionsCreateRequest["encounter"]["type"]; period: { startedAt: string; endedAt?: string; @@ -43,10 +43,10 @@ export interface CreateInteractionPayload { title?: string; }; patient?: { - identifier?: Corti.InteractionsPatient['identifier']; + identifier?: Corti.InteractionsPatient["identifier"]; name?: string; birthDate?: string | null; - gender?: Corti.InteractionsPatient['gender']; + gender?: Corti.InteractionsPatient["gender"]; }; } diff --git a/src/types/protocol.ts b/src/types/protocol.ts index c9647bb..b5dc625 100644 --- a/src/types/protocol.ts +++ b/src/types/protocol.ts @@ -1,39 +1,39 @@ // Protocol types for communication between parent applications and embedded Corti Assistant -export type APIVersion = 'v1'; +export type APIVersion = "v1"; export type MessageType = - | 'CORTI_EMBEDDED' - | 'CORTI_EMBEDDED_RESPONSE' - | 'CORTI_EMBEDDED_EVENT'; + | "CORTI_EMBEDDED" + | "CORTI_EMBEDDED_RESPONSE" + | "CORTI_EMBEDDED_EVENT"; -export type DefaultMode = 'virtual' | 'in-person'; +export type DefaultMode = "virtual" | "in-person"; export type EmbeddedAction = - | 'auth' - | 'createInteraction' - | 'addFacts' - | 'configureSession' - | 'navigate' - | 'startRecording' - | 'stopRecording' - | 'getStatus' - | 'getTemplates' - | 'configure' - | 'setCredentials'; + | "auth" + | "createInteraction" + | "addFacts" + | "configureSession" + | "navigate" + | "startRecording" + | "stopRecording" + | "getStatus" + | "getTemplates" + | "configure" + | "setCredentials"; export type DeprecatedEmbeddedEvent = - | 'ready' - | 'loaded' - | 'recordingStarted' - | 'recordingStopped' - | 'documentGenerated' - | 'documentUpdated' - | 'documentSynced' - | 'authChanged' - | 'interactionCreated' - | 'navigationChanged' - | 'usage'; + | "ready" + | "loaded" + | "recordingStarted" + | "recordingStopped" + | "documentGenerated" + | "documentUpdated" + | "documentSynced" + | "authChanged" + | "interactionCreated" + | "navigationChanged" + | "usage"; // Base Message Types export interface BaseMessage { @@ -42,14 +42,14 @@ export interface BaseMessage { } export interface EmbeddedRequest extends BaseMessage { - type: 'CORTI_EMBEDDED'; + type: "CORTI_EMBEDDED"; action: EmbeddedAction; requestId: string; payload?: unknown; } export interface EmbeddedResponse extends BaseMessage { - type: 'CORTI_EMBEDDED_RESPONSE'; + type: "CORTI_EMBEDDED_RESPONSE"; action: EmbeddedAction; requestId: string; success: boolean; @@ -60,7 +60,7 @@ export interface EmbeddedResponse extends BaseMessage { } interface BaseEventMessage extends BaseMessage { - type: 'CORTI_EMBEDDED_EVENT'; + type: "CORTI_EMBEDDED_EVENT"; event: string | DeprecatedEmbeddedEvent; payload?: unknown; } @@ -77,92 +77,92 @@ export interface EmbeddedEventMessage extends BaseEventMessage { // Specific Request Types export interface AuthRequest extends EmbeddedRequest { - action: 'auth'; + action: "auth"; } export interface CreateInteractionRequest extends EmbeddedRequest { - action: 'createInteraction'; + action: "createInteraction"; } export interface AddFactsRequest extends EmbeddedRequest { - action: 'addFacts'; + action: "addFacts"; } export interface ConfigureSessionRequest extends EmbeddedRequest { - action: 'configureSession'; + action: "configureSession"; } export interface NavigateRequest extends EmbeddedRequest { - action: 'navigate'; + action: "navigate"; } export interface StartRecordingRequest extends EmbeddedRequest { - action: 'startRecording'; + action: "startRecording"; } export interface StopRecordingRequest extends EmbeddedRequest { - action: 'stopRecording'; + action: "stopRecording"; } export interface GetStatusRequest extends EmbeddedRequest { - action: 'getStatus'; + action: "getStatus"; } export interface GetTemplatesRequest extends EmbeddedRequest { - action: 'getTemplates'; + action: "getTemplates"; } export interface ConfigureRequest extends EmbeddedRequest { - action: 'configure'; + action: "configure"; } export interface SetCredentialsRequest extends EmbeddedRequest { - action: 'setCredentials'; + action: "setCredentials"; } // Event Types export interface ReadyEvent extends DeprecatedEmbeddedEventMessage { - event: 'ready'; + event: "ready"; } export interface LoadedEvent extends DeprecatedEmbeddedEventMessage { - event: 'loaded'; + event: "loaded"; } export interface RecordingStartedEvent extends DeprecatedEmbeddedEventMessage { - event: 'recordingStarted'; + event: "recordingStarted"; } export interface RecordingStoppedEvent extends DeprecatedEmbeddedEventMessage { - event: 'recordingStopped'; + event: "recordingStopped"; } export interface DocumentGeneratedEvent extends DeprecatedEmbeddedEventMessage { - event: 'documentGenerated'; + event: "documentGenerated"; } export interface DocumentUpdatedEvent extends DeprecatedEmbeddedEventMessage { - event: 'documentUpdated'; + event: "documentUpdated"; } export interface DocumentSyncedEvent extends DeprecatedEmbeddedEventMessage { - event: 'documentSynced'; + event: "documentSynced"; } export interface AuthChangedEvent extends DeprecatedEmbeddedEventMessage { - event: 'authChanged'; + event: "authChanged"; } export interface InteractionCreatedEvent extends DeprecatedEmbeddedEventMessage { - event: 'interactionCreated'; + event: "interactionCreated"; } export interface NavigationChangedEvent extends DeprecatedEmbeddedEventMessage { - event: 'navigationChanged'; + event: "navigationChanged"; } export interface UsageEvent extends DeprecatedEmbeddedEventMessage { - event: 'usage'; + event: "usage"; } // Request/Response/Event type unions diff --git a/src/utils/PostMessageHandler.ts b/src/utils/PostMessageHandler.ts index 0d457cb..635843a 100644 --- a/src/utils/PostMessageHandler.ts +++ b/src/utils/PostMessageHandler.ts @@ -1,4 +1,4 @@ -import type { AnyEvent, EmbeddedRequest, EmbeddedResponse } from '../types'; +import type { AnyEvent, EmbeddedRequest, EmbeddedResponse } from "../types"; export interface PostMessageHandlerCallbacks { onEvent?: (event: { name: string; payload: unknown }) => void; @@ -26,24 +26,24 @@ interface PendingRequest { } function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null; + return typeof value === "object" && value !== null; } function isEmbeddedEventMessage(value: unknown): value is AnyEvent { return ( isRecord(value) && - value.type === 'CORTI_EMBEDDED_EVENT' && - typeof value.event === 'string' + value.type === "CORTI_EMBEDDED_EVENT" && + typeof value.event === "string" ); } function isEmbeddedResponseMessage(value: unknown): value is EmbeddedResponse { return ( isRecord(value) && - value.type === 'CORTI_EMBEDDED_RESPONSE' && - typeof value.action === 'string' && - typeof value.requestId === 'string' && - typeof value.success === 'boolean' + value.type === "CORTI_EMBEDDED_RESPONSE" && + typeof value.action === "string" && + typeof value.requestId === "string" && + typeof value.success === "boolean" ); } @@ -58,7 +58,7 @@ export class PostMessageHandler { private _protocolVersion: string | null = null; - private static readonly SUPPORTED_PROTOCOL_VERSION = 'v1'; + private static readonly SUPPORTED_PROTOCOL_VERSION = "v1"; private readonly requestTimeout: number; @@ -104,7 +104,7 @@ export class PostMessageHandler { } }; - window.addEventListener('message', this.messageListener); + window.addEventListener("message", this.messageListener); } private handleEvent(eventData: AnyEvent): void { @@ -112,15 +112,15 @@ export class PostMessageHandler { const { payload } = eventData; // Only 'embedded.ready' signals that the iframe is ready to receive messages - if (eventType === 'embedded.ready') { + if (eventType === "embedded.ready") { this.isReady = true; // Store and validate the protocol version from the ready payload const version = - isRecord(payload) && typeof payload.version === 'string' + isRecord(payload) && typeof payload.version === "string" ? payload.version : undefined; - if (typeof version === 'string') { + if (typeof version === "string") { this._protocolVersion = version; if (version !== PostMessageHandler.SUPPORTED_PROTOCOL_VERSION) { this.callbacks.onError?.({ @@ -130,26 +130,26 @@ export class PostMessageHandler { } } - if (eventType === 'error.triggered') { + if (eventType === "error.triggered") { const errorPayload = - payload && typeof payload === 'object' + payload && typeof payload === "object" ? (payload as Record) : undefined; const payloadMessage = - errorPayload && typeof errorPayload.message === 'string' + errorPayload && typeof errorPayload.message === "string" ? errorPayload.message : undefined; const payloadCode = - errorPayload && typeof errorPayload.code === 'string' + errorPayload && typeof errorPayload.code === "string" ? errorPayload.code : undefined; this.callbacks.onError?.({ message: payloadMessage || - (typeof payload === 'string' + (typeof payload === "string" ? payload - : 'Embedded event reported an error'), + : "Embedded event reported an error"), code: payloadCode, details: eventData, }); @@ -170,7 +170,7 @@ export class PostMessageHandler { if (data.success === false || data.error) { const error = { - message: data.error || 'Request failed', + message: data.error || "Request failed", code: data.errorCode, details: data.errorDetails, }; @@ -184,7 +184,7 @@ export class PostMessageHandler { destroy() { if (this.messageListener) { - window.removeEventListener('message', this.messageListener); + window.removeEventListener("message", this.messageListener); this.messageListener = null; } this.pendingRequests.clear(); @@ -223,13 +223,13 @@ export class PostMessageHandler { return new Promise((resolve, reject) => { let timeoutId: ReturnType | null = null; - let readyListener: (event: MessageEvent) => void = () => { }; + let readyListener: (event: MessageEvent) => void = () => {}; function cleanup() { if (timeoutId !== null) { clearTimeout(timeoutId); } - window.removeEventListener('message', readyListener); + window.removeEventListener("message", readyListener); } // Create a one-time listener for the ready event @@ -237,8 +237,8 @@ export class PostMessageHandler { if ( event.source === this.iframe.contentWindow && event.origin === this.getTrustedOrigin() && - event.data?.type === 'CORTI_EMBEDDED_EVENT' && - event.data.event === 'embedded.ready' + event.data?.type === "CORTI_EMBEDDED_EVENT" && + event.data.event === "embedded.ready" ) { cleanup(); resolve(); @@ -247,10 +247,10 @@ export class PostMessageHandler { timeoutId = setTimeout(() => { cleanup(); - reject(new Error('Timeout waiting for iframe to be ready')); + reject(new Error("Timeout waiting for iframe to be ready")); }, timeout); - window.addEventListener('message', readyListener); + window.addEventListener("message", readyListener); }); } @@ -260,11 +260,11 @@ export class PostMessageHandler { * @param timeout - Optional timeout in milliseconds. Defaults to the requestTimeout set at construction. */ async postMessage( - message: Omit, + message: Omit, timeout?: number, ): Promise { if (!this.iframe.contentWindow) { - throw new Error('Iframe not ready'); + throw new Error("Iframe not ready"); } // Ensure the iframe has signaled readiness before sending @@ -277,7 +277,7 @@ export class PostMessageHandler { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { this.pendingRequests.delete(requestId); - reject(new Error('Request timeout')); + reject(new Error("Request timeout")); }, effectiveTimeout); this.pendingRequests.set(requestId, { @@ -299,7 +299,7 @@ export class PostMessageHandler { const targetOrigin = this.getTrustedOrigin(); if (!targetOrigin) { this.pendingRequests.delete(requestId); - reject(new Error('Cannot determine trusted origin for postMessage')); + reject(new Error("Cannot determine trusted origin for postMessage")); return; } contentWindow.postMessage(fullMessage, targetOrigin); @@ -316,7 +316,7 @@ export class PostMessageHandler { */ private getTrustedOrigin(): string | null { try { - const src = this.iframe.getAttribute('src') || this.iframe.src; + const src = this.iframe.getAttribute("src") || this.iframe.src; if (!src) return null; const url = new URL(src, window.location.href); return url.origin; diff --git a/src/utils/baseUrl.ts b/src/utils/baseUrl.ts index 20e6487..e74bcef 100644 --- a/src/utils/baseUrl.ts +++ b/src/utils/baseUrl.ts @@ -3,21 +3,21 @@ export function validateAndNormalizeBaseURL(url: string): string { try { parsed = new URL(url); } catch { - throw new Error('Invalid baseURL: not a parseable URL'); + throw new Error("Invalid baseURL: not a parseable URL"); } - if (parsed.protocol !== 'https:') { - throw new Error('Invalid baseURL: must use https'); + if (parsed.protocol !== "https:") { + throw new Error("Invalid baseURL: must use https"); } const host = parsed.host.toLowerCase(); const pattern = /^assistant\.[a-z0-9-]+\.corti\.app$/i; if (!pattern.test(host)) { - throw new Error('Invalid baseURL: host must match assistant.xxx.corti.app'); + throw new Error("Invalid baseURL: host must match assistant.xxx.corti.app"); } - if (parsed.pathname && parsed.pathname !== '/' && parsed.pathname !== '') { - throw new Error('Invalid baseURL: must not include a path'); + if (parsed.pathname && parsed.pathname !== "/" && parsed.pathname !== "") { + throw new Error("Invalid baseURL: must not include a path"); } if (parsed.username || parsed.password) { - throw new Error('Invalid baseURL: must not include credentials'); + throw new Error("Invalid baseURL: must not include credentials"); } - return parsed.origin.replace(/\/+$/, ''); + return parsed.origin.replace(/\/+$/, ""); } diff --git a/src/utils/embedUrl.ts b/src/utils/embedUrl.ts index 6232e25..d190dcf 100644 --- a/src/utils/embedUrl.ts +++ b/src/utils/embedUrl.ts @@ -6,7 +6,7 @@ export function isRealEmbeddedLoad( src: string, normalizedBaseURL: string, ): boolean { - if (!src || src.startsWith('about:')) { + if (!src || src.startsWith("about:")) { return false; } try { @@ -17,8 +17,8 @@ export function isRealEmbeddedLoad( return false; } // Accept /embedded with optional trailing slash; allow query/hash - const normalizedPath = srcUrl.pathname.replace(/\/+$/, ''); - return normalizedPath === '/embedded'; + const normalizedPath = srcUrl.pathname.replace(/\/+$/, ""); + return normalizedPath === "/embedded"; } catch { return false; } diff --git a/src/utils/errorFormatter.ts b/src/utils/errorFormatter.ts index d947963..4d7ddf2 100644 --- a/src/utils/errorFormatter.ts +++ b/src/utils/errorFormatter.ts @@ -18,15 +18,15 @@ interface FormattedError { } function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null; + return typeof value === "object" && value !== null; } function toStringIfFiniteNumberOrString(value: unknown): string | undefined { - if (typeof value === 'string') { + if (typeof value === "string") { return value; } - if (typeof value === 'number' && Number.isFinite(value)) { + if (typeof value === "number" && Number.isFinite(value)) { return String(value); } @@ -58,9 +58,9 @@ function extractStatusCode(message: string): string | undefined { */ function isValidationError(obj: unknown): obj is ValidationErrorLike { return ( - typeof obj === 'object' && + typeof obj === "object" && obj !== null && - ('expected' in obj || 'code' in obj || 'path' in obj || 'message' in obj) + ("expected" in obj || "code" in obj || "path" in obj || "message" in obj) ); } @@ -76,7 +76,7 @@ function formatValidationError(error: ValidationError): string { } // Build a descriptive message from the parts - const pathString = path && path.length > 0 ? path.join('.') : 'field'; + const pathString = path && path.length > 0 ? path.join(".") : "field"; if (expected) { return `Invalid ${pathString}: expected ${expected}`; @@ -94,7 +94,7 @@ function formatValidationError(error: ValidationError): string { */ function formatValidationErrors(errors: ValidationError[]): string { if (errors.length === 0) { - return 'Unknown validation error'; + return "Unknown validation error"; } if (errors.length === 1) { @@ -102,7 +102,7 @@ function formatValidationErrors(errors: ValidationError[]): string { } const errorMessages = errors.map(formatValidationError); - return `Multiple validation errors: ${errorMessages.join('; ')}`; + return `Multiple validation errors: ${errorMessages.join("; ")}`; } /** @@ -110,7 +110,7 @@ function formatValidationErrors(errors: ValidationError[]): string { */ export function formatError( error: unknown, - fallbackMessage = 'An error occurred', + fallbackMessage = "An error occurred", ): FormattedError { // Handle null/undefined if (!error) { @@ -131,7 +131,7 @@ export function formatError( } // Handle string errors (like "400 Bad Request") - if (typeof error === 'string') { + if (typeof error === "string") { const extractedCode = extractStatusCode(error); return { message: error, @@ -163,7 +163,7 @@ export function formatError( if (isRecord(error)) { const errorObj = error; const objectMessage = - typeof errorObj.message === 'string' ? errorObj.message : undefined; + typeof errorObj.message === "string" ? errorObj.message : undefined; const objectCode = toStringIfFiniteNumberOrString(errorObj.code); const objectStatus = toStringIfFiniteNumberOrString(errorObj.status); @@ -180,7 +180,7 @@ export function formatError( // Try to enhance the message with parsed details if ( isRecord(errorObj.details) && - typeof errorObj.details.message === 'string' + typeof errorObj.details.message === "string" ) { const parsedDetails = tryParseJson(errorObj.details.message); @@ -206,11 +206,11 @@ export function formatError( if (isValidationError(errorObj)) { const validationError: ValidationError = { expected: - typeof errorObj.expected === 'string' ? errorObj.expected : undefined, + typeof errorObj.expected === "string" ? errorObj.expected : undefined, code: objectCode, path: Array.isArray(errorObj.path) && - errorObj.path.every(pathPart => typeof pathPart === 'string') + errorObj.path.every(pathPart => typeof pathPart === "string") ? errorObj.path : undefined, message: objectMessage, @@ -226,11 +226,11 @@ export function formatError( if (errorObj.error || errorObj.detail || objectStatus || objectCode) { const message = errorObj.error ?? objectMessage ?? errorObj.detail; const messageStr = - typeof message === 'string' ? message : fallbackMessage; + typeof message === "string" ? message : fallbackMessage; // Try to get code from various properties, or extract from message let code = objectCode ?? objectStatus; - if (!code && typeof message === 'string') { + if (!code && typeof message === "string") { code = extractStatusCode(message); } diff --git a/src/web-index.ts b/src/web-index.ts index 85fb92b..18df731 100644 --- a/src/web-index.ts +++ b/src/web-index.ts @@ -1,6 +1,6 @@ -import './corti-embedded.js'; +import "./corti-embedded.js"; -export { CortiEmbedded } from './CortiEmbedded.js'; +export { CortiEmbedded } from "./CortiEmbedded.js"; // Export clean public types only -export * from './types/index.js'; +export * from "./types/index.js"; From 0b9c12dc9885356c3e4c9d68376e666c62c9b92e Mon Sep 17 00:00:00 2001 From: Zoltan Hricz Date: Tue, 17 Mar 2026 10:54:57 +0100 Subject: [PATCH 2/2] refactor: move element registration to shared module --- src/corti-embedded.ts | 23 ++++++++++++++++++++++- src/index.ts | 20 -------------------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/corti-embedded.ts b/src/corti-embedded.ts index bc95860..9f728bb 100644 --- a/src/corti-embedded.ts +++ b/src/corti-embedded.ts @@ -1,5 +1,26 @@ import { CortiEmbedded } from "./CortiEmbedded.js"; +import type { CortiEmbeddedAPI, CortiEmbeddedWindowAPI } from "./types/api.js"; // Register the main component -if (!customElements.get("corti-embedded")) +if (!customElements.get("corti-embedded")) { customElements.define("corti-embedded", CortiEmbedded); +} + +/** + * Type representing the corti-embedded custom element in the DOM. + * When this package is installed, tag-name based APIs like + * document.querySelector('corti-embedded') and document.createElement('corti-embedded') + * are automatically typed via HTMLElementTagNameMap. Other lookups such as getElementById + * still return HTMLElement | null and require a cast or narrowing to CortiEmbeddedElement. + */ +export type CortiEmbeddedElement = HTMLElement & CortiEmbeddedAPI; + +// Extend Window interface +declare global { + interface Window { + CortiEmbedded?: CortiEmbeddedWindowAPI; + } + interface HTMLElementTagNameMap { + "corti-embedded": CortiEmbeddedElement; + } +} diff --git a/src/index.ts b/src/index.ts index 08ff59c..d16e235 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,4 @@ import "./corti-embedded.js"; -import { CortiEmbeddedAPI, CortiEmbeddedWindowAPI } from "./types/api.js"; export { CortiEmbedded } from "./CortiEmbedded.js"; @@ -11,22 +10,3 @@ export * from "./types/index.js"; // Export PostMessageHandler types for advanced usage export type { PostMessageHandlerCallbacks } from "./utils/PostMessageHandler.js"; - -/** - * Type representing the corti-embedded custom element in the DOM. - * When this package is installed, tag-name based APIs like - * document.querySelector('corti-embedded') and document.createElement('corti-embedded') - * are automatically typed via HTMLElementTagNameMap. Other lookups such as getElementById - * still return HTMLElement | null and require a cast or narrowing to CortiEmbeddedElement. - */ -export type CortiEmbeddedElement = HTMLElement & CortiEmbeddedAPI; - -// Extend Window interface -declare global { - interface Window { - CortiEmbedded?: CortiEmbeddedWindowAPI; - } - interface HTMLElementTagNameMap { - "corti-embedded": CortiEmbeddedElement; - } -}