diff --git a/src/CortiEmbedded.ts b/src/CortiEmbedded.ts
index fb4bdcb..27c1a38 100644
--- a/src/CortiEmbedded.ts
+++ b/src/CortiEmbedded.ts
@@ -30,6 +30,9 @@ import {
type PostMessageHandlerCallbacks,
} from './utils/PostMessageHandler.js';
+const IFRAME_SANDBOX_POLICY =
+ 'allow-forms allow-modals allow-scripts allow-same-origin';
+
export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI {
static styles = [baseStyles, containerStyles];
@@ -43,6 +46,7 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI {
private normalizedBaseURL: string | null = null;
+ // eslint-disable-next-line class-methods-use-this
private getIframeAllowPolicy(normalizedBaseURL?: string | null): string {
const permissionTarget = normalizedBaseURL
? new URL(normalizedBaseURL).origin
@@ -566,13 +570,13 @@ export class CortiEmbedded extends LitElement implements CortiEmbeddedAPI {
`;
}
diff --git a/src/types/api.ts b/src/types/api.ts
index 51b6c8a..33c6bac 100644
--- a/src/types/api.ts
+++ b/src/types/api.ts
@@ -176,7 +176,7 @@ export interface CortiEmbeddedAPI {
* @param credentials Authentication credentials to store
* @returns Promise that resolves when credentials are set
*/
- setCredentials(credentials: { password: string }): Promise;
+ setCredentials(credentials: SetCredentialsPayload): Promise;
/**
* Show the embedded UI
diff --git a/src/utils/PostMessageHandler.ts b/src/utils/PostMessageHandler.ts
index 6e256b3..0d457cb 100644
--- a/src/utils/PostMessageHandler.ts
+++ b/src/utils/PostMessageHandler.ts
@@ -14,11 +14,41 @@ export interface PostMessageHandlerCallbacks {
requestTimeout?: number;
}
+interface PostMessageHandlerError {
+ message: string;
+ code?: string;
+ details?: unknown;
+}
+
+interface PendingRequest {
+ resolve: (value: EmbeddedResponse) => void;
+ reject: (reason: PostMessageHandlerError) => void;
+}
+
+function isRecord(value: unknown): value is Record {
+ 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'
+ );
+}
+
+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'
+ );
+}
+
export class PostMessageHandler {
- private pendingRequests = new Map<
- string,
- { resolve: (value: any) => void; reject: (reason: any) => void }
- >();
+ private pendingRequests = new Map();
private messageListener: ((event: MessageEvent) => void) | null = null;
@@ -60,13 +90,16 @@ export class PostMessageHandler {
const { data } = event;
// Check for Corti embedded events
- if (data?.type === 'CORTI_EMBEDDED_EVENT') {
+ if (isEmbeddedEventMessage(data)) {
this.handleEvent(data);
return;
}
// Check if this is a response to a pending request
- if (data.requestId && this.pendingRequests.has(data.requestId)) {
+ if (
+ isEmbeddedResponseMessage(data) &&
+ this.pendingRequests.has(data.requestId)
+ ) {
this.handleResponse(data);
}
};
@@ -75,7 +108,7 @@ export class PostMessageHandler {
}
private handleEvent(eventData: AnyEvent): void {
- const eventType = (eventData as any).event;
+ const eventType = eventData.event;
const { payload } = eventData;
// Only 'embedded.ready' signals that the iframe is ready to receive messages
@@ -83,7 +116,10 @@ export class PostMessageHandler {
this.isReady = true;
// Store and validate the protocol version from the ready payload
- const version = (payload as any)?.version;
+ const version =
+ isRecord(payload) && typeof payload.version === 'string'
+ ? payload.version
+ : undefined;
if (typeof version === 'string') {
this._protocolVersion = version;
if (version !== PostMessageHandler.SUPPORTED_PROTOCOL_VERSION) {
@@ -126,7 +162,7 @@ export class PostMessageHandler {
});
}
- private handleResponse(data: any): void {
+ private handleResponse(data: EmbeddedResponse): void {
const pendingRequest = this.pendingRequests.get(data.requestId);
if (pendingRequest) {
const { resolve, reject } = pendingRequest;
@@ -245,11 +281,11 @@ export class PostMessageHandler {
}, effectiveTimeout);
this.pendingRequests.set(requestId, {
- resolve: (value: any) => {
+ resolve: value => {
clearTimeout(timeoutId);
resolve(value);
},
- reject: (reason: any) => {
+ reject: reason => {
clearTimeout(timeoutId);
reject(reason);
},
diff --git a/src/utils/errorFormatter.ts b/src/utils/errorFormatter.ts
index 6fe7b74..d947963 100644
--- a/src/utils/errorFormatter.ts
+++ b/src/utils/errorFormatter.ts
@@ -5,12 +5,34 @@ interface ValidationError {
message?: string;
}
+type ValidationErrorLike =
+ | ({ expected: unknown } & Record)
+ | ({ code: unknown } & Record)
+ | ({ path: unknown } & Record)
+ | ({ message: unknown } & Record);
+
interface FormattedError {
message: string;
code?: string;
details?: unknown;
}
+function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null;
+}
+
+function toStringIfFiniteNumberOrString(value: unknown): string | undefined {
+ if (typeof value === 'string') {
+ return value;
+ }
+
+ if (typeof value === 'number' && Number.isFinite(value)) {
+ return String(value);
+ }
+
+ return undefined;
+}
+
/**
* Attempts to parse a JSON string safely
*/
@@ -34,7 +56,7 @@ function extractStatusCode(message: string): string | undefined {
/**
* Checks if an object looks like a validation error
*/
-function isValidationError(obj: unknown): obj is ValidationError {
+function isValidationError(obj: unknown): obj is ValidationErrorLike {
return (
typeof obj === 'object' &&
obj !== null &&
@@ -138,13 +160,17 @@ export function formatError(
}
// Handle error objects
- if (typeof error === 'object') {
- const errorObj = error as any;
+ if (isRecord(error)) {
+ const errorObj = error;
+ const objectMessage =
+ typeof errorObj.message === 'string' ? errorObj.message : undefined;
+ const objectCode = toStringIfFiniteNumberOrString(errorObj.code);
+ const objectStatus = toStringIfFiniteNumberOrString(errorObj.status);
// Handle objects with message and details structure (your original case)
- if (errorObj.message && typeof errorObj.message === 'string') {
- let formattedMessage = errorObj.message;
- let { code } = errorObj;
+ if (objectMessage) {
+ let formattedMessage = objectMessage;
+ let code = objectCode;
// If no code is provided, try to extract it from the message
if (!code) {
@@ -153,7 +179,7 @@ export function formatError(
// Try to enhance the message with parsed details
if (
- errorObj.details?.message &&
+ isRecord(errorObj.details) &&
typeof errorObj.details.message === 'string'
) {
const parsedDetails = tryParseJson(errorObj.details.message);
@@ -178,21 +204,32 @@ export function formatError(
// Handle single validation error objects
if (isValidationError(errorObj)) {
+ const validationError: ValidationError = {
+ expected:
+ typeof errorObj.expected === 'string' ? errorObj.expected : undefined,
+ code: objectCode,
+ path:
+ Array.isArray(errorObj.path) &&
+ errorObj.path.every(pathPart => typeof pathPart === 'string')
+ ? errorObj.path
+ : undefined,
+ message: objectMessage,
+ };
return {
- message: formatValidationError(errorObj),
- code: errorObj.code,
+ message: formatValidationError(validationError),
+ code: validationError.code,
details: error,
};
}
// Handle objects that might have useful error info
- if (errorObj.error || errorObj.message || errorObj.detail) {
- const message = errorObj.error || errorObj.message || errorObj.detail;
+ if (errorObj.error || errorObj.detail || objectStatus || objectCode) {
+ const message = errorObj.error ?? objectMessage ?? errorObj.detail;
const messageStr =
typeof message === 'string' ? message : fallbackMessage;
// Try to get code from various properties, or extract from message
- let code = errorObj.code || errorObj.status;
+ let code = objectCode ?? objectStatus;
if (!code && typeof message === 'string') {
code = extractStatusCode(message);
}
diff --git a/test/error-formatter.test.ts b/test/error-formatter.test.ts
new file mode 100644
index 0000000..667fb5d
--- /dev/null
+++ b/test/error-formatter.test.ts
@@ -0,0 +1,41 @@
+import { expect } from '@open-wc/testing';
+import { formatError } from '../src/utils/errorFormatter.js';
+
+describe('formatError', () => {
+ it('formats object errors without any-based narrowing', () => {
+ const result = formatError({
+ message: '400 Bad Request',
+ details: {
+ message: JSON.stringify([
+ { path: ['payload', 'path'], expected: 'string' },
+ ]),
+ },
+ });
+
+ expect(result).to.deep.equal({
+ message: 'Invalid payload.path: expected string',
+ code: '400',
+ details: {
+ message: JSON.stringify([
+ { path: ['payload', 'path'], expected: 'string' },
+ ]),
+ },
+ });
+ });
+
+ it('formats status codes from generic object errors', () => {
+ const result = formatError({
+ error: 'Unauthorized',
+ status: 401,
+ });
+
+ expect(result).to.deep.equal({
+ message: 'Unauthorized',
+ code: '401',
+ details: {
+ error: 'Unauthorized',
+ status: 401,
+ },
+ });
+ });
+});