Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 4 additions & 2 deletions src/locales/en/services/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@
"sendingCodeSuggestionAccepted": "Sending code suggestion accepted for file {{fileName}}, operation: {{operation}}",
"sendingCodeSuggestionRejected": "Sending code suggestion rejected for file {{fileName}}, operation: {{operation}}, reason: {{reason}}",
"sendingDatadogSessionId": "Sending Datadog session ID: {{sessionId}}",
"sendingDatadogViewId": "Sending Datadog view ID: {{viewId}}"
"sendingDatadogViewId": "Sending Datadog view ID: {{viewId}}",
"errorParsingIframeOrigin": "Error parsing iframe origin URL: {{error}}"
}
},
"errors": {
Expand All @@ -159,7 +160,8 @@
"errorImportingStoreForDiagramDisplayHandling": "Error importing store for diagram display handling: {{error}}",
"serviceDestroyedDuringNavigation": "Service destroyed during navigation",
"serviceResetDuringNavigation": "Service reset during navigation",
"errorImportingDatadogUtils": "Error importing Datadog utils: {{error}}"
"errorImportingDatadogUtils": "Error importing Datadog utils: {{error}}",
"errorNavigatingToBilling": "Error navigating to billing: {{error}}"
}
}
}
139 changes: 63 additions & 76 deletions src/services/iframeComm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,17 @@ export const CONFIG = {
class IframeCommService {
private readonly expectedOrigin: string = ((): string => {
try {
if (aiChatbotOrigin && aiChatbotOrigin.startsWith("http")) {
if (aiChatbotOrigin && (aiChatbotOrigin.startsWith("http") || aiChatbotOrigin.startsWith("https"))) {
return new URL(aiChatbotOrigin).origin;
}

if (aiChatbotUrl && aiChatbotUrl.startsWith("http")) {
return new URL(aiChatbotUrl).origin;
}

return aiChatbotOrigin?.replace(/\/$/, "") || "";
} catch (error) {
// eslint-disable-next-line no-console
console.error("Failed to parse aiChatbotOrigin or aiChatbotUrl:", error);
return aiChatbotOrigin || "";
LoggerService.debug(
namespaces.iframeCommService,
`Failed to parse aiChatbotOrigin or aiChatbotUrl: ${aiChatbotOrigin} ${aiChatbotUrl}: ${error}`
);
return "";
}
})();
private listeners: MessageListener[] = [];
Expand Down Expand Up @@ -121,14 +119,6 @@ class IframeCommService {
}

this.iframeRef = iframe;

if (this.messageQueue.length > 0) {
LoggerService.debug(
namespaces.iframeCommService,
`Iframe ref set - processing ${this.messageQueue.length} queued messages`
);
void this.processMessageQueue();
}
}

public destroy(): void {
Expand Down Expand Up @@ -194,7 +184,7 @@ class IframeCommService {
t("debug.iframeComm.handshakeMessageSent", { ns: "services" })
);
} catch (error) {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.failedToSendHandshakeMessage", { ns: "services", error })
);
Expand Down Expand Up @@ -279,15 +269,16 @@ class IframeCommService {
}

if (this.iframeRef.contentWindow) {
const targetOrigin = this.getTargetOrigin();
LoggerService.debug(
namespaces.iframeCommService,
t("debug.iframeComm.postingMessageToChatbot", {
ns: "services",
origin: this.expectedOrigin || aiChatbotOrigin,
origin: targetOrigin,
message: JSON.stringify(messageToSend),
})
);
this.iframeRef.contentWindow.postMessage(messageToSend, this.expectedOrigin || aiChatbotOrigin);
this.iframeRef.contentWindow.postMessage(messageToSend, targetOrigin);
} else {
throw new Error(t("errors.iframeComm.iframeContentWindowNotAvailable", { ns: "services" }));
}
Expand All @@ -300,7 +291,7 @@ class IframeCommService {

this.queueProcessCount++;
if (this.queueProcessCount > this.maxQueueProcessAttempts) {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.queueProcessingExceeded", {
ns: "services",
Expand All @@ -316,6 +307,7 @@ class IframeCommService {
try {
const batchSize = 5;
let processed = 0;
const targetOrigin = this.getTargetOrigin();

while (this.messageQueue.length > 0 && processed < batchSize) {
if (!this.isConnected) {
Expand All @@ -325,7 +317,7 @@ class IframeCommService {
const message = this.messageQueue.shift();
if (message) {
if (this.iframeRef?.contentWindow) {
this.iframeRef.contentWindow.postMessage(message, this.expectedOrigin || aiChatbotOrigin);
this.iframeRef.contentWindow.postMessage(message, targetOrigin);
}
processed++;
}
Expand Down Expand Up @@ -491,7 +483,7 @@ class IframeCommService {
return undefined;
})
.catch((error) => {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.errorImportingDatadogUtils", {
ns: "services",
Expand Down Expand Up @@ -608,30 +600,41 @@ class IframeCommService {
}
}

private isValidOrigin(origin: string): boolean {
const expectedOrigin = this.expectedOrigin || aiChatbotOrigin;

if (origin === expectedOrigin) {
return true;
}

private getTargetOrigin(): string {
if (this.iframeRef && this.iframeRef.src) {
try {
const iframeOrigin = new URL(this.iframeRef.src).origin;
if (origin === iframeOrigin) {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return iframeOrigin;
} catch (error) {
// Ignore errors when extracting iframe origin
LoggerService.debug(
namespaces.iframeCommService,
t("debug.iframeComm.errorParsingIframeOrigin", {
ns: "services",
error: (error as Error).message,
iframeRefSrc: this.iframeRef.src,
})
);
}
}

if (origin.includes("localhost") || origin.includes("127.0.0.1")) {
return true;
}
return this.expectedOrigin;
}

return false;
private isValidOrigin(origin: string): boolean {
try {
const url = new URL(origin);
const hostname = url.hostname;
const validHostnames = ["localhost", "127.0.0.1"];

return (
validHostnames.includes(hostname) ||
hostname === "autokitteh.cloud" ||
hostname.endsWith(".autokitteh.cloud")
);
} catch (error) {
LoggerService.debug(namespaces.iframeCommService, `Failed to parse origin URL: ${origin}: ${error}`);
return false;
}
}

private isValidMessage(message: unknown): message is AkbotMessage {
Expand All @@ -658,6 +661,19 @@ class IframeCommService {

private async handleIncomingMessages(event: MessageEvent): Promise<void> {
try {
if (!this.iframeRef || !document.contains(this.iframeRef)) {
return;
}
const origin = this.getTargetOrigin();
if (event.origin === origin && !this.isConnected) {
this.isConnected = true;
if (this.connectionResolve) {
this.connectionResolve();
this.connectionResolve = null;
}
this.sendDatadogContext();
}

if (!this.isValidOrigin(event.origin)) {
LoggerService.debug(
namespaces.iframeCommService,
Expand All @@ -676,21 +692,12 @@ class IframeCommService {
} catch (error) {
LoggerService.debug(
namespaces.iframeCommService,
`ered message due to origin mismatch: error extracting type/source ${error}`
`Filtered message due to origin mismatch: error extracting type/source ${error}`
);
}
return;
}

if (event.origin === (this.expectedOrigin || aiChatbotOrigin) && !this.isConnected) {
this.isConnected = true;
if (this.connectionResolve) {
this.connectionResolve();
this.connectionResolve = null;
}
this.sendDatadogContext();
}

const message = event.data as AkbotMessage;

if (!Object.values(MessageTypes).includes(message?.type)) {
Expand Down Expand Up @@ -720,19 +727,6 @@ class IframeCommService {
await this.processMessageQueue();
}

if (!this.iframeRef || !document.contains(this.iframeRef)) {
if (message.type !== MessageTypes.HANDSHAKE && message.type !== MessageTypes.HANDSHAKE_ACK) {
LoggerService.debug(
namespaces.iframeCommService,
`Received ${message.type} but iframe ref not ready - adding to queue`
);
if (this.messageQueue.length < this.maxQueueSize) {
this.messageQueue.push(message);
}
return;
}
}

switch (message.type) {
case MessageTypes.HANDSHAKE: {
const handshakeAckMessage: HandshakeAckMessage = {
Expand All @@ -742,14 +736,7 @@ class IframeCommService {
version: CONFIG.APP_VERSION,
},
};
if (this.iframeRef && document.contains(this.iframeRef)) {
this.sendMessage(handshakeAckMessage);
} else {
LoggerService.debug(
namespaces.iframeCommService,
"Received HANDSHAKE but iframe ref not ready yet - waiting for iframe to be set"
);
}
this.sendMessage(handshakeAckMessage);
break;
}
case MessageTypes.HANDSHAKE_ACK:
Expand Down Expand Up @@ -792,7 +779,7 @@ class IframeCommService {
listener.callback(message);
});
} catch (error) {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.errorProcessingIncomingMessage", {
ns: "services",
Expand Down Expand Up @@ -830,7 +817,7 @@ class IframeCommService {
return true;
})
.catch((error) => {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.errorImportingStoreForDiagramDisplayHandling", {
ns: "services",
Expand All @@ -852,7 +839,7 @@ class IframeCommService {
return true;
})
.catch((error) => {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.errorImportingStoreForVarUpdatedHandling", {
ns: "services",
Expand All @@ -874,7 +861,7 @@ class IframeCommService {
try {
window.location.href = "/organization-settings/billing";
} catch (error) {
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.errorNavigatingToBilling", {
ns: "services",
Expand Down Expand Up @@ -969,7 +956,7 @@ class IframeCommService {
} catch (error) {
operationResult.success = false;
failureCount++;
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
`Failed to apply ${operation} operation for file ${fileName}: ${(error as Error).message}`
);
Expand Down Expand Up @@ -1145,7 +1132,7 @@ class IframeCommService {
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";

LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.failedToDownloadFile", {
ns: "services",
Expand Down Expand Up @@ -1185,7 +1172,7 @@ class IframeCommService {
URL.revokeObjectURL(url);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error";
LoggerService.error(
LoggerService.debug(
namespaces.iframeCommService,
t("errors.iframeComm.failedToDownloadChatFile", {
ns: "services",
Expand Down