From 8a1d1ee66198717954c75c0c28eb58fdf6df02a7 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Tue, 6 Jan 2026 08:18:14 +0530 Subject: [PATCH 1/8] SK-2416 add custom error support at container level (#655) --- src/client/index.ts | 73 ++++- src/core-utils/collect.ts | 4 + src/core-utils/reveal.ts | 4 + src/core/constants.ts | 2 +- .../external/collect/collect-container.ts | 11 + .../collect/compose-collect-container.ts | 14 +- .../reveal/composable-reveal-container.ts | 36 +-- .../reveal/composable-reveal-internal.ts | 20 +- src/core/external/reveal/reveal-container.ts | 16 +- src/core/external/reveal/reveal-element.ts | 14 +- .../internal/composable-frame-element-init.ts | 1 + src/core/internal/frame-element-init.ts | 49 +++- src/core/internal/reveal/reveal-frame.ts | 6 +- .../skyflow-frame/skyflow-frame-controller.ts | 11 + src/index-node.ts | 2 + src/libs/skyflow-error.ts | 3 + src/skyflow.ts | 5 + src/utils/common/index.ts | 22 ++ tests/client.test.ts | 252 +++++++++++++++++- .../collect/collect-container.test.js | 59 +++- .../collect/collect-container.test.ts | 85 ++++-- .../collect/composable-container.test.js | 6 +- .../reveal-composable-container.test.js | 31 ++- .../reveal/reveal-composable-element.test.js | 32 ++- .../reveal/reveal-composable-internal.test.js | 7 +- .../external/reveal/reveal-container.test.js | 4 +- .../external/reveal/reveal-container.test.ts | 3 + .../external/reveal/reveal-element.test.js | 41 ++- .../external/reveal/reveal-element.test.ts | 75 +++++- .../frame-element-init.additional.test.js | 17 +- .../core/internal/frame-element-init.test.js | 9 +- .../skyflow-frame-controller.test.ts | 167 +++++++++++- tests/skyflow.test.ts | 6 + 33 files changed, 995 insertions(+), 92 deletions(-) diff --git a/src/client/index.ts b/src/client/index.ts index 85d6a54f..1c286f15 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -11,6 +11,7 @@ import { getMetaObject, } from '../utils/helpers'; import { ClientMetadata } from '../core/internal/internal-types'; +import { ErrorMessages, ErrorType } from '../utils/common'; export interface IClientRequest { body?: Document | XMLHttpRequestBodyInit | null; @@ -42,11 +43,27 @@ class Client { #metaData: ClientMetadata; + errorMessagesList: Partial = {}; + constructor(config: ISkyflow, metadata: ClientMetadata) { this.config = config; this.#metaData = metadata; } + setErrorMessages(messages: ErrorMessages) { + this.errorMessagesList = { + ...messages, + }; + } + + // eslint-disable-next-line class-methods-use-this + #getErrorTypeKey(value: number): ErrorType | string { + const valStr = String(value); + const key = (Object.keys(ErrorType) as Array) + .find((keys) => ErrorType[keys] === valStr); + return key ? ErrorType[key] : String(value); + } + toJSON(): ClientToJSON { return { config: this.config, @@ -102,24 +119,39 @@ class Client { const contentType = headerMap['content-type']; const requestId = headerMap['x-request-id']; if (httpRequest.status < 200 || httpRequest.status >= 400) { + const overrideCodes = [400, 401, 403, 404, 429, 500]; if (contentType && contentType.includes('application/json')) { let description = JSON.parse(httpRequest.response); if (description?.error?.message) { description = requestId ? `${description?.error?.message} - requestId: ${requestId}` : description?.error?.message; } + if (overrideCodes.includes(httpRequest.status)) { + description = this.errorMessagesList[httpRequest.status] ?? description; + } reject(new SkyflowError({ code: httpRequest.status, description, + type: this.#getErrorTypeKey(httpRequest.status), }, [], true)); } else if (contentType && contentType.includes('text/plain')) { + let description = requestId ? `${httpRequest.response} - requestId: ${requestId}` : httpRequest.response; + if (overrideCodes.includes(httpRequest.status)) { + description = this.errorMessagesList[httpRequest.status] ?? description; + } reject(new SkyflowError({ code: httpRequest.status, - description: requestId ? `${httpRequest.response} - requestId: ${requestId}` : httpRequest.response, + description, + type: this.#getErrorTypeKey(httpRequest.status), }, [], true)); } else { + let description = requestId ? `${logs.errorLogs.ERROR_OCCURED} - requestId: ${requestId}` : logs.errorLogs.ERROR_OCCURED; + if (overrideCodes.includes(httpRequest.status)) { + description = this.errorMessagesList[httpRequest.status] ?? description; + } reject(new SkyflowError({ code: httpRequest.status, - description: requestId ? `${logs.errorLogs.ERROR_OCCURED} - requestId: ${requestId}` : logs.errorLogs.ERROR_OCCURED, + description, + type: this.#getErrorTypeKey(httpRequest.status), }, [], true)); } } @@ -132,24 +164,47 @@ class Client { httpRequest.onerror = () => { const isOffline = typeof navigator !== 'undefined' && !navigator.onLine; if (isOffline) { - reject(new SkyflowError(SKYFLOW_ERROR_CODE.OFFLINE_ERROR, [], true)); + reject(new SkyflowError({ + code: httpRequest.status, + description: this.errorMessagesList.OFFLINE + ?? SKYFLOW_ERROR_CODE.OFFLINE_ERROR.description, + type: ErrorType.OFFLINE, + }, [], true)); return; } - if (httpRequest.status === 0) { - reject(new SkyflowError(SKYFLOW_ERROR_CODE.GENERIC_ERROR, [], true)); + reject(new SkyflowError({ + code: httpRequest.status, + description: this.errorMessagesList.NETWORK_GENERIC + ?? SKYFLOW_ERROR_CODE.GENERIC_ERROR.description, + type: ErrorType.NETWORK_GENERIC, + }, [], true)); return; } - - reject(new SkyflowError(SKYFLOW_ERROR_CODE.GENERIC_ERROR, [], true)); + reject(new SkyflowError({ + code: httpRequest.status, + description: this.errorMessagesList.NETWORK_GENERIC + ?? SKYFLOW_ERROR_CODE.GENERIC_ERROR.description, + type: ErrorType.NETWORK_GENERIC, + }, [], true)); }; httpRequest.ontimeout = () => { - reject(new SkyflowError(SKYFLOW_ERROR_CODE.TIMEOUT_ERROR, [], true)); + reject(new SkyflowError({ + code: httpRequest.status, + description: this.errorMessagesList.TIMEOUT + ?? SKYFLOW_ERROR_CODE.TIMEOUT_ERROR.description, + type: ErrorType.TIMEOUT, + }, [], true)); }; httpRequest.onabort = () => { - reject(new SkyflowError(SKYFLOW_ERROR_CODE.ABORT_ERROR, [], true)); + reject(new SkyflowError({ + code: httpRequest.status, + description: this.errorMessagesList.ABORT + ?? SKYFLOW_ERROR_CODE.ABORT_ERROR.description, + type: ErrorType.ABORT, + }, [], true)); }; }); } diff --git a/src/core-utils/collect.ts b/src/core-utils/collect.ts index 7dd26d37..34b4df11 100644 --- a/src/core-utils/collect.ts +++ b/src/core-utils/collect.ts @@ -285,6 +285,7 @@ export const updateRecordsBySkyflowID = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, + type: rejectedResult?.error?.type, }, }; } @@ -342,6 +343,7 @@ export const updateRecordsBySkyflowIDComposable = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, + type: rejectedResult?.error?.type, }, }; } @@ -409,6 +411,7 @@ export const insertDataInCollect = async ( error: { code: error?.error?.code, description: error?.error?.description, + type: error?.error?.type, }, }, ], @@ -453,6 +456,7 @@ export const insertDataInMultipleFiles = async ( error: { code: error?.error?.code, description: error?.error?.description, + type: error?.error?.type, }, }, ], diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index bb334494..05476ace 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -42,6 +42,7 @@ const formatForPureJsFailure = (cause, tokenId:string) => ({ ...new SkyflowError({ code: cause?.error?.code, description: cause?.error?.description, + type: cause?.error?.type, }, [], true), }); const formatForRenderFileFailure = (cause, skyflowID:string, column: string) => ({ @@ -50,6 +51,7 @@ const formatForRenderFileFailure = (cause, skyflowID:string, column: string) => error: { code: cause?.error?.code, description: cause?.error?.description, + type: cause?.error?.type, }, }); @@ -441,6 +443,7 @@ export const fetchRecordsGET = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, + type: rejectedResult?.error?.type, }, ids: skyflowIdRecord.ids, ...(skyflowIdRecord?.columnName ? { columnName: skyflowIdRecord?.columnName } @@ -512,6 +515,7 @@ export const fetchRecordsBySkyflowID = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, + type: rejectedResult?.error?.type, }, ids: skyflowIdRecord.ids, }; diff --git a/src/core/constants.ts b/src/core/constants.ts index 8a674353..e8bddff2 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -31,7 +31,7 @@ export const DOMAIN = 'US2'; export const CORALOGIX_DOMAIN = 'https://cdn.rum-ingress-coralogix.com/coralogix/browser/latest/coralogix-browser-sdk.js'; export const FRAME_ELEMENT = 'element'; export const COMPOSABLE_REVEAL = 'reveal-composable'; - +export const CUSTOM_ERROR_MESSAGES = 'CUSTOM_ERROR_MESSAGES'; export const ELEMENT_TYPES = { COLLECT: 'COLLECT', REVEAL: 'REVEAL', diff --git a/src/core/external/collect/collect-container.ts b/src/core/external/collect/collect-container.ts index 2642d236..52537015 100644 --- a/src/core/external/collect/collect-container.ts +++ b/src/core/external/collect/collect-container.ts @@ -18,6 +18,7 @@ import { ICollectOptions, UploadFilesResponse, ContainerOptions, + ErrorType, } from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import logs from '../../../utils/logs'; @@ -88,6 +89,8 @@ class CollectContainer extends Container { #isSkyflowFrameReady: boolean = false; + #customErrorMessages: Partial> = {}; + constructor( metaData: Metadata, skyflowElements: Array, @@ -153,6 +156,10 @@ class CollectContainer extends Container { return this.#createMultipleElement(elementGroup, true); }; + setError(errors: Partial>) { + this.#customErrorMessages = errors; + } + #createMultipleElement = ( multipleElements: ElementGroup, isSingleElementAPI: boolean = false, @@ -314,6 +321,7 @@ class CollectContainer extends Container { tokens: options?.tokens !== undefined ? options.tokens : true, elementIds, containerId: this.#containerId, + errorMessages: this.#customErrorMessages, }, (data: any) => { if (!data || data?.error) { @@ -374,6 +382,7 @@ class CollectContainer extends Container { tokens: options?.tokens !== undefined ? options.tokens : true, elementIds, containerId: this.#containerId, + errorMessages: this.#customErrorMessages, }, (data: any) => { if (!data || data?.error) { @@ -423,6 +432,7 @@ class CollectContainer extends Container { ...options, elementIds, containerId: this.#containerId, + errorMessages: this.#customErrorMessages, }, (data: any) => { if (!data || data?.error) { @@ -473,6 +483,7 @@ class CollectContainer extends Container { ...options, elementIds, containerId: this.#containerId, + errorMessages: this.#customErrorMessages, }, (data: any) => { if (!data || data?.error) { diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index d8915d0f..735f79ec 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -25,6 +25,8 @@ import { ErrorTextStyles, ContainerOptions, UploadFilesResponse, + ErrorMessages, + ErrorType, } from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import logs from '../../../utils/logs'; @@ -36,7 +38,7 @@ import { import { COLLECT_FRAME_CONTROLLER, CONTROLLER_STYLES, ELEMENT_EVENTS_TO_IFRAME, - ELEMENTS, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_CONTAINER, + ELEMENTS, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, COLLECT_TYPES, } from '../../constants'; import Container from '../common/container'; @@ -45,7 +47,6 @@ import ComposableElement from './compose-collect-element'; import { ElementGroup, ElementGroupItem } from './collect-container'; import { Metadata, SkyflowElementProps } from '../../internal/internal-types'; import Client from '../../../client'; -import { getAccessToken } from '../../../utils/bus-events'; export interface ComposableElementGroup extends ElementGroup { styles: InputStyles; @@ -92,6 +93,8 @@ class ComposableContainer extends Container { #getSkyflowBearerToken: () => Promise | undefined; + #customErrorMessages: Partial> = {}; + constructor( metaData: Metadata, skyflowElements: Array, @@ -177,6 +180,10 @@ class ComposableContainer extends Container { ); }; + setError(errors: Partial>) { + this.#customErrorMessages = errors; + } + #createMultipleElement = ( multipleElements: ComposableElementGroup, isSingleElementAPI: boolean = false, @@ -362,6 +369,7 @@ class ComposableContainer extends Container { options: { ...data?.options, }, + errorMessages: this.#customErrorMessages, }, ); }).catch((err:any) => { @@ -446,6 +454,7 @@ class ComposableContainer extends Container { vaultID: this.#metaData.clientJSON.config.vaultID, authToken, }, + errorMessages: this.#customErrorMessages, }); }).catch((err:any) => { printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); @@ -534,6 +543,7 @@ class ComposableContainer extends Container { vaultID: this.#metaData.clientJSON.config.vaultID, authToken, }, + errorMessages: this.#customErrorMessages, }); window.addEventListener('message', (event) => { if (event.data?.type diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index 91a41a20..ed22f59c 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -29,11 +29,14 @@ import { FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, COMPOSABLE_REVEAL, REVEAL_TYPES, + CUSTOM_ERROR_MESSAGES, } from '../../constants'; import Container from '../common/container'; import ComposableRevealElement from './composable-reveal-element'; -import { ContainerOptions, RevealElementInput, RevealResponse } from '../../../index-node'; +import { + ContainerOptions, ErrorMessages, ErrorType, RevealElementInput, RevealResponse, +} from '../../../index-node'; import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; import ComposableRevealInternalElement from './composable-reveal-internal'; import { formatRevealElementOptions } from '../../../utils/helpers'; @@ -82,6 +85,8 @@ class ComposableRevealContainer extends Container { #getSkyflowBearerToken: () => Promise | undefined; + #customErrorMessages: Partial> = {}; + constructor( metaData: Metadata, skyflowElements:Array, @@ -122,19 +127,6 @@ class ComposableRevealContainer extends Container { MessageType.LOG, this.#context.logLevel); this.#containerMounted = true; - // bus - // // .target(properties.IFRAME_SECURE_ORIGIN) - // eslint-disable-next-line max-len - // .on(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.#containerId, (data, callback) => { - // printLog(parameterizedString(logs.infoLogs.INITIALIZE_COMPOSABLE_CLIENT, CLASS_NAME), - // MessageType.LOG, - // this.#context.logLevel); - // callback({ - // client: this.#metaData.clientJSON, - // context, - // }); - // this.#isComposableFrameReady = true; - // }); window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#containerId) { @@ -161,6 +153,14 @@ class ComposableRevealContainer extends Container { controllerIframeName); }; + setError(errors: Partial>) { + this.#customErrorMessages = errors; + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter._emit(`${CUSTOM_ERROR_MESSAGES}:${this.#containerId}`, { + errorMessages: this.#customErrorMessages, + }); + } + #createMultipleElement = ( multipleElements: ComposableElementGroup, isSingleElementAPI: boolean = false, @@ -303,12 +303,16 @@ class ComposableRevealContainer extends Container { }; #emitEvent = (eventName: string, options?: Record, callback?: any) => { + const option = { + ...options, + errorMessages: this.#customErrorMessages, + }; if (this.#shadowRoot) { const iframe = this.#shadowRoot.getElementById(this.#iframeID) as HTMLIFrameElement; if (iframe?.contentWindow) { iframe.contentWindow.postMessage({ name: eventName, - ...options, + ...option, }, properties.IFRAME_SECURE_ORIGIN); } } else { @@ -316,7 +320,7 @@ class ComposableRevealContainer extends Container { if (iframe?.contentWindow) { iframe.contentWindow.postMessage({ name: eventName, - ...options, + ...option, }, properties.IFRAME_SECURE_ORIGIN); } } diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index bce2fe92..8adaa7d9 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -4,7 +4,9 @@ Copyright (c) 2022 Skyflow, Inc. import bus from 'framebus'; import SkyflowError from '../../../libs/skyflow-error'; import uuid from '../../../libs/uuid'; -import { Context, MessageType, RenderFileResponse } from '../../../utils/common'; +import { + Context, ErrorType, MessageType, RenderFileResponse, +} from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import { ELEMENT_EVENTS_TO_IFRAME, @@ -15,6 +17,7 @@ import { EVENT_TYPES, REVEAL_TYPES, COMPOSABLE_REVEAL, + CUSTOM_ERROR_MESSAGES, } from '../../constants'; import IFrame from '../common/iframe'; import SkyflowElement from '../common/skyflow-element'; @@ -67,6 +70,8 @@ class ComposableRevealInternalElement extends SkyflowElement { #isComposableFrameReady: boolean = false; + #customerErrorMessages: Partial> = {}; + constructor(elementId: string, recordGroup, metaData: Metadata, @@ -101,6 +106,11 @@ class ComposableRevealInternalElement extends SkyflowElement { this.#iframe?.setIframeHeight(event?.data?.data?.height); } }); + this.#eventEmitter.on(`${CUSTOM_ERROR_MESSAGES}:${this.#containerId}`, (data) => { + if (data?.errorMessages) { + this.#customerErrorMessages = data.errorMessages as Record; + } + }); // eslint-disable-next-line max-len if (this.#recordData?.rows) { @@ -274,17 +284,21 @@ class ComposableRevealInternalElement extends SkyflowElement { } #emitEvent = (eventName: string, options?: Record) => { + const option = { + ...options, + errorMessages: this.#customerErrorMessages, + }; if (this.#shadowRoot) { const iframe = this.#shadowRoot?.getElementById(this.#iframe?.name) as HTMLIFrameElement; iframe?.contentWindow?.postMessage({ name: eventName, - ...options, + ...option, }, properties?.IFRAME_SECURE_ORIGIN); } else { const iframe = document?.getElementById(this.#iframe?.name) as HTMLIFrameElement; iframe?.contentWindow?.postMessage({ name: eventName, - ...options, + ...option, }, properties?.IFRAME_SECURE_ORIGIN); } }; diff --git a/src/core/external/reveal/reveal-container.ts b/src/core/external/reveal/reveal-container.ts index f4e1b80b..d2ab586f 100644 --- a/src/core/external/reveal/reveal-container.ts +++ b/src/core/external/reveal/reveal-container.ts @@ -9,7 +9,7 @@ import uuid from '../../../libs/uuid'; import { ContainerType } from '../../../skyflow'; import { ContainerOptions, - Context, MessageType, + Context, ErrorType, MessageType, RedactionType, RevealResponse, } from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; @@ -17,7 +17,8 @@ import logs from '../../../utils/logs'; import { parameterizedString, printLog } from '../../../utils/logs-helper'; import { validateInitConfig, validateInputFormatOptions, validateRevealElementRecords } from '../../../utils/validators'; import { - CONTROLLER_STYLES, ELEMENT_EVENTS_TO_CONTAINER, ELEMENT_EVENTS_TO_IFRAME, REVEAL_FRAME_CONTROLLER, + CONTROLLER_STYLES, CUSTOM_ERROR_MESSAGES, + ELEMENT_EVENTS_TO_CONTAINER, ELEMENT_EVENTS_TO_IFRAME, REVEAL_FRAME_CONTROLLER, REVEAL_TYPES, } from '../../constants'; import Container from '../common/container'; @@ -72,6 +73,8 @@ class RevealContainer extends Container { #isSkyflowFrameReady: boolean = false; + #customErrorMessages: Partial> = {}; + constructor( metaData: Metadata, skyflowElements: Array, @@ -157,6 +160,14 @@ class RevealContainer extends Container { return revealElement; } + setError(errors: Partial>) { + this.#customErrorMessages = errors; + // eslint-disable-next-line no-underscore-dangle + this.#eventEmmiter._emit(`${CUSTOM_ERROR_MESSAGES}:${this.#containerId}`, { + errorMessages: this.#customErrorMessages, + }); + } + reveal(): Promise { this.#isRevealCalled = true; this.#revealRecords = []; @@ -265,6 +276,7 @@ class RevealContainer extends Container { type: REVEAL_TYPES.REVEAL, records: this.#revealRecords, containerId: this.#containerId, + errorMessages: this.#customErrorMessages, }, (revealData: any) => { this.#mountedRecords = []; diff --git a/src/core/external/reveal/reveal-element.ts b/src/core/external/reveal/reveal-element.ts index a0264f8c..e8d8c3d3 100644 --- a/src/core/external/reveal/reveal-element.ts +++ b/src/core/external/reveal/reveal-element.ts @@ -4,7 +4,9 @@ Copyright (c) 2022 Skyflow, Inc. import bus from 'framebus'; import SkyflowError from '../../../libs/skyflow-error'; import uuid from '../../../libs/uuid'; -import { Context, MessageType, RenderFileResponse } from '../../../utils/common'; +import { + Context, ErrorType, MessageType, RenderFileResponse, +} from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import { // eslint-disable-next-line max-len @@ -17,6 +19,7 @@ import { ELEMENT_TYPES, EVENT_TYPES, REVEAL_TYPES, + CUSTOM_ERROR_MESSAGES, } from '../../constants'; import IFrame from '../common/iframe'; import SkyflowElement from '../common/skyflow-element'; @@ -66,6 +69,8 @@ class RevealElement extends SkyflowElement { #isSkyflowFrameReady: boolean = false; + #customerErrorMessages: Partial> = {}; + constructor( record: IRevealElementInput, options: IRevealElementOptions = {}, @@ -102,6 +107,11 @@ class RevealElement extends SkyflowElement { bus.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, (data) => { this.#iframe.setIframeHeight(data.height); }); + this.#eventEmitter.on(`${CUSTOM_ERROR_MESSAGES}:${this.#containerId}`, (data) => { + if (data?.errorMessages) { + this.#customerErrorMessages = data.errorMessages as Record; + } + }); } getID() { @@ -196,6 +206,7 @@ class RevealElement extends SkyflowElement { records: this.#recordData, containerId: this.#containerId, iframeName: this.#iframe.name, + errorMessages: this.#customerErrorMessages, }, (revealData: any) => { if (revealData.errors) { @@ -247,6 +258,7 @@ class RevealElement extends SkyflowElement { records: this.#recordData, containerId: this.#containerId, iframeName: this.#iframe.name, + errorMessages: this.#customerErrorMessages, }, (revealData: any) => { if (revealData.errors) { diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts index 9b56f001..3f60db79 100644 --- a/src/core/internal/composable-frame-element-init.ts +++ b/src/core/internal/composable-frame-element-init.ts @@ -72,6 +72,7 @@ export default class RevealComposableFrameElementInit { uuid: '', clientDomain: '', }); + this.#client.setErrorMessages(event?.data?.errorMessages || {}); elementIds?.forEach((element) => { this.revealFrameList?.forEach((revealFrame) => { diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index 32c3cdb4..d2c8a9c7 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -5,7 +5,7 @@ import { getValueAndItsUnit, validateAndSetupGroupOptions } from '../../libs/ele import { getFlexGridStyles } from '../../libs/styles'; import { ContainerType } from '../../skyflow'; import { - Context, Env, LogLevel, + Context, Env, ErrorType, LogLevel, MessageType, } from '../../utils/common'; import { @@ -84,7 +84,8 @@ export default class FrameElementInit { === ELEMENTS.MULTI_FILE_INPUT.name) { if (event?.data && event?.data?.name === `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${inputElement.iFrameName}`) { this.#client = Client.fromJSON(event?.data?.clientConfig); - this.multipleUploadFiles(inputElement, event?.data?.clientConfig, event?.data?.options) + this.multipleUploadFiles(inputElement, event?.data?.clientConfig, + event?.data?.options, event?.data?.errorMessages) ?.then((response: any) => { window?.parent.postMessage({ type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${inputElement.iFrameName}`, @@ -105,7 +106,7 @@ export default class FrameElementInit { if (event?.data && event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + this.containerId) { if (event?.data?.data && event?.data?.data?.type === COLLECT_TYPES.COLLECT) { - this.tokenize(event?.data?.data, event?.data?.clientConfig) + this.tokenize(event?.data?.data, event?.data?.clientConfig, event?.data?.errorMessages) .then((response: any) => { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, @@ -119,7 +120,8 @@ export default class FrameElementInit { }, this.clientMetaData?.clientDomain); }); } else if (event.data.data && event.data.data.type === COLLECT_TYPES.FILE_UPLOAD) { - this.parallelUploadFiles(event.data.data, event.data.clientConfig) + this.parallelUploadFiles(event.data.data, + event.data.clientConfig, event?.data?.errorMessages) .then((response: any) => { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, @@ -144,7 +146,8 @@ export default class FrameElementInit { // } }; - private parallelUploadFiles = (options, config) => new Promise((rootResolve, rootReject) => { + private parallelUploadFiles = (options, config, + errorMessages?: Record) => new Promise((rootResolve, rootReject) => { const promises: Promise[] = []; this.iframeFormList.forEach((inputElement) => { let res: Promise; @@ -153,7 +156,7 @@ export default class FrameElementInit { inputElement.fieldType === ELEMENTS.FILE_INPUT.name ) { - res = this.uploadFiles(inputElement, config); + res = this.uploadFiles(inputElement, config, errorMessages); promises.push(res); } } @@ -189,11 +192,14 @@ export default class FrameElementInit { }); }); - uploadFiles = (fileElement, clientConfig) => { + uploadFiles = (fileElement, clientConfig, errorMessages?: Record) => { this.#client = new Client(clientConfig, { uuid: '', clientDomain: '', }); + if (errorMessages && this.#client) { + this.#client.setErrorMessages(errorMessages); + } if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const fileUploadObject: any = {}; @@ -259,6 +265,15 @@ export default class FrameElementInit { rootResolve(response); }) .catch((error) => { + if (error?.error) { + rootReject({ + error: { + code: error?.error?.code, + description: error?.error?.description, + type: error?.error?.type, + }, + }); + } rootReject(error); }); }); @@ -272,7 +287,7 @@ export default class FrameElementInit { }); }; - private tokenize = (options, clientConfig: any) => { + private tokenize = (options, clientConfig: any, errorMessages?: Record) => { let errorMessage = ''; const insertRequestObject: any = {}; const updateRequestObject: any = {}; @@ -402,6 +417,9 @@ export default class FrameElementInit { clientDomain: '', }); const client = this.#client; + if (errorMessages && client) { + this.#client.setErrorMessages(errorMessages); + } const sendRequest = () => new Promise((rootResolve, rootReject) => { const insertPromiseSet: Promise[] = []; @@ -474,11 +492,15 @@ export default class FrameElementInit { // eslint-disable-next-line consistent-return private multipleUploadFiles = (fileElement: IFrameFormElement, - clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + clientConfig, metaData, + errorMessages?: Record) => new Promise((rootResolve, rootReject) => { this.#client = new Client(clientConfig, { uuid: '', clientDomain: '', }); + if (errorMessages && this.#client) { + this.#client.setErrorMessages(errorMessages); + } if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const { @@ -658,6 +680,15 @@ export default class FrameElementInit { }); }) .catch((error) => { + if (error?.error) { + rootReject({ + error: { + code: error?.error?.code, + description: error?.error?.description, + type: error?.error?.type, + }, + }); + } rootReject(error); }); }); diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 973e0a2a..24924314 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -286,7 +286,8 @@ class RevealFrame { if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + this.#name) { if (event?.data?.data?.iframeName === this.#name && event?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { - this.renderFile(this.#record, event?.data?.clientConfig)?.then((resolvedResult) => { + this.renderFile(this.#record, event?.data?.clientConfig, + event?.data?.errorMessages)?.then((resolvedResult) => { const result = formatForRenderClient( resolvedResult as IRenderResponseType, this.#record?.column, @@ -412,12 +413,13 @@ class RevealFrame { } }; - private renderFile(data: IRevealRecord, clientConfig): + private renderFile(data: IRevealRecord, clientConfig, customErrorMessages): Promise | undefined { this.#client = new Client(clientConfig, { uuid: '', clientDomain: '', }); + this.#client.setErrorMessages(customErrorMessages ?? {}); return new Promise((resolve, reject) => { try { getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) diff --git a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts index 6de070bf..e067943a 100644 --- a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts +++ b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts @@ -54,6 +54,7 @@ import { IUpdateRequest, UpdateResponse, IUpdateOptions, + ErrorType, } from '../../../utils/common'; import { deleteData } from '../../../core-utils/delete'; import properties from '../../../properties'; @@ -301,6 +302,10 @@ class SkyflowFrameController { bus .target(this.#clientDomain) .on(ELEMENT_EVENTS_TO_IFRAME.COLLECT_CALL_REQUESTS + this.#clientId, (data, callback) => { + if (this.#client && data?.errorMessages) { + const errorMessages: Partial> = data?.errorMessages; + this.#client.setErrorMessages(errorMessages as Record); + } printLog( parameterizedString( logs.infoLogs.CAPTURE_PURE_JS_REQUEST, @@ -378,6 +383,11 @@ class SkyflowFrameController { MessageType.LOG, this.#context.logLevel, ); + if (this.#client && data?.errorMessages) { + const errorMessages: Partial> = data?.errorMessages; + this.#client.setErrorMessages(errorMessages as Record); + } + if (data.type === REVEAL_TYPES.REVEAL) { printLog(parameterizedString(logs.infoLogs.CAPTURE_EVENT, CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.REVEAL_REQUEST), @@ -739,6 +749,7 @@ class SkyflowFrameController { error: { code: error?.error?.code, description: error?.error?.description, + type: error?.error?.type, }, }, ], diff --git a/src/index-node.ts b/src/index-node.ts index 380c2234..3c2227bc 100644 --- a/src/index-node.ts +++ b/src/index-node.ts @@ -45,6 +45,8 @@ export { LogLevel, Env, ElementState, + ErrorType, + ErrorMessages, } from './utils/common'; export { diff --git a/src/libs/skyflow-error.ts b/src/libs/skyflow-error.ts index 742b7818..ddba42a2 100644 --- a/src/libs/skyflow-error.ts +++ b/src/libs/skyflow-error.ts @@ -1,11 +1,13 @@ /* Copyright (c) 2022 Skyflow, Inc. */ +import { ErrorType } from '../utils/common'; import { parameterizedString } from '../utils/logs-helper'; export interface ISkyflowError{ code:string | number, description:string, + type?: ErrorType | string | undefined, } export default class SkyflowError extends Error { @@ -20,6 +22,7 @@ export default class SkyflowError extends Error { description: (args && args?.length > 0) ? parameterizedString(errorCode.description, ...args) : errorCode.description, + type: errorCode?.type, }; super(formattedError.description); if (isSingularError) { diff --git a/src/skyflow.ts b/src/skyflow.ts index b3350ee2..5585d290 100644 --- a/src/skyflow.ts +++ b/src/skyflow.ts @@ -45,6 +45,7 @@ import { IUpdateRequest, UpdateResponse, IUpdateOptions, + ErrorType, } from './utils/common'; import { formatVaultURL, checkAndSetForCustomUrl } from './utils/helpers'; import ComposableContainer from './core/external/collect/compose-collect-container'; @@ -348,6 +349,10 @@ class Skyflow { return RedactionType; } + static get ErrorType() { + return ErrorType; + } + static get RequestMethod() { return RequestMethod; } diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 32dcdb4a..26baa018 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -10,6 +10,28 @@ declare global { } } +// export type ErrorKey = typeof ERROR_TYPE[keyof typeof ERROR_TYPE]; + +export enum ErrorType { + BAD_REQUEST = '400', + UNAUTHORIZED = '401', + FORBIDDEN = '403', + NOT_FOUND = '404', + TOO_MANY_REQUESTS = '429', + INTERNAL_SERVER_ERROR = '500', + BAD_GATEWAY = '502', + SERVICE_UNAVAILABLE = '503', + CONNECTION = 'CONNECTION', + TIMEOUT = 'TIMEOUT', + ABORT = 'ABORT', + NETWORK_GENERIC = 'NETWORK_GENERIC', + OFFLINE = 'OFFLINE', +} + +export type ErrorMessages = { + [key in ErrorType]: string; +}; + export enum RedactionType { DEFAULT = 'DEFAULT', PLAIN_TEXT = 'PLAIN_TEXT', diff --git a/tests/client.test.ts b/tests/client.test.ts index 022b56f8..5a62d8e6 100644 --- a/tests/client.test.ts +++ b/tests/client.test.ts @@ -7,6 +7,7 @@ import SKYFLOW_ERROR_CODE from "../src/utils/constants"; import logs from "../src/utils/logs"; import { ClientMetadata } from "../src/core/internal/internal-types"; import { ISkyflow } from "../src/skyflow"; +import SkyflowError from "../src/libs/skyflow-error"; const skyflowConfig: ISkyflow = { vaultID: "e20afc3ae1b54f0199f24130e51e0c11", @@ -502,7 +503,8 @@ describe("Client Class", () => { const testClient = new Client(skyflowConfig, metaData); const promise = testClient.request({ requestMethod: 'GET', url: 'https://offline.com' }); setTimeout(() => { if (xhrMock.onerror) xhrMock.onerror(); }, 0); - await expect(promise).rejects.toMatchObject({ error: { code: SKYFLOW_ERROR_CODE.OFFLINE_ERROR.code } }); + await expect(promise) + .rejects.toMatchObject({ error: { code: 0 } }); }); test('onerror status 0 path returns GENERIC_ERROR code', async () => { @@ -531,7 +533,7 @@ describe("Client Class", () => { code: expect.any(Number), // Accept any number, or use expect.stringMatching(/^(0|500)$/) }), }); -}); + }); test('ontimeout path returns TIMEOUT_ERROR code', async () => { const xhrMock: any = { open: jest.fn(), send: jest.fn(), setRequestHeader: jest.fn(), getAllResponseHeaders: jest.fn().mockReturnValue(''), status: 0 }; @@ -539,7 +541,7 @@ describe("Client Class", () => { const testClient = new Client(skyflowConfig, metaData); const p = testClient.request({ requestMethod: 'GET', url: 'https://timeout.com' }); setTimeout(() => { xhrMock.ontimeout(); }, 0); - await expect(p).rejects.toMatchObject({ error: { code: SKYFLOW_ERROR_CODE.TIMEOUT_ERROR.code } }); + await expect(p).rejects.toMatchObject({ error: { code: 0} }); }); test('onabort path returns ABORT_ERROR code', async () => { @@ -548,7 +550,249 @@ describe("Client Class", () => { const testClient = new Client(skyflowConfig, metaData); const p = testClient.request({ requestMethod: 'GET', url: 'https://abort.com' }); setTimeout(() => { xhrMock.onabort(); }, 0); - await expect(p).rejects.toMatchObject({ error: { code: SKYFLOW_ERROR_CODE.ABORT_ERROR.code } }); + await expect(p).rejects.toMatchObject({ error: { code: 0 } }); + testClient.setErrorMessages({ + 400: "Bad Request", + 401: "Unauthorized", + 503: "Service Unavailable", + 500: "Internal Server Error", + 404: "Not Found", + 403: "Forbidden", + 429: "Too Many Requests", + CONNECTION: "Connection Error", + TIMEOUT: "Timeout Error", + ABORT: "Abort Error", + NETWORK_GENERIC: "Network Generic Error", + OFFLINE: "Offline Error", + 502: "Bad Gateway Error", + }); + const p2 = testClient.request({ requestMethod: 'GET', url: 'https://abort.com' }); + setTimeout(() => { xhrMock.onabort(); }, 0); + await expect(p2).rejects.toMatchObject({ error: { code: 0, description: "Abort Error" } }); + }); + test('Client request resolves raw string for non-json error', async () => { + // define a navigator to avoid offline check issues and add userAgent + Object.defineProperty(window, 'navigator', + { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + configurable: true + }); + const xhrMock: any = { + open: jest.fn(), + send: jest.fn() + .mockImplementation(() => + { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue('content-type: text/plain'), + status: 400, + response: 'error occurred', + }; + jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + const testClient = new Client(skyflowConfig, metaData); + const result = testClient.request({ + requestMethod: 'GET', + url: 'https://plain-success.com', + headers: { 'content-type': 'text/plain' }, + }); + await expect(result).rejects.toStrictEqual(new SkyflowError({ + code: 400, + description: 'error occurred', + }, [], true)); + }); + + test('Client request resolves raw string for non-json error case 1', async () => { + // define a navigator to avoid offline check issues and add userAgent + Object.defineProperty(window, 'navigator', + { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + configurable: true + }); + const xhrMock: any = { + open: jest.fn(), + send: jest.fn() + .mockImplementation(() => + { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue('content-type: text/plain\r\nx-request-id: id'), + status: 400, + response: 'error occurred', + }; + jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + const testClient = new Client(skyflowConfig, metaData); + const result = testClient.request({ + requestMethod: 'GET', + url: 'https://plain-success.com', + headers: { 'content-type': 'text/plain'}, + }); + await expect(result).rejects.toStrictEqual(new SkyflowError({ + code: 400, + description: 'error occurred - requestId: id', + }, [], true)); + }); + test('Client request resolves raw string for non-json error case 2', async () => { + // define a navigator to avoid offline check issues and add userAgent + Object.defineProperty(window, 'navigator', + { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + configurable: true + }); + const xhrMock: any = { + open: jest.fn(), + send: jest.fn() + .mockImplementation(() => + { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue('content-type: application/json\r\nx-request-id: id'), + status: 400, + response: JSON.stringify({ error: { message: 'error occurred' } }), + }; + jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + const testClient = new Client(skyflowConfig, metaData); + const result = testClient.request({ + requestMethod: 'GET', + url: 'https://plain-success.com', + headers: { 'content-type': 'text/plain'}, + }); + await expect(result).rejects.toStrictEqual(new SkyflowError({ + code: 400, + description: 'error occurred - requestId: id', + }, [], true)); + }); + test('Client request resolves raw string for non-json error case 3', async () => { + // define a navigator to avoid offline check issues and add userAgent + Object.defineProperty(window, 'navigator', + { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + configurable: true + }); + const xhrMock: any = { + open: jest.fn(), + send: jest.fn() + .mockImplementation(() => + { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue('x-request-id: id'), + status: 400, + response: JSON.stringify({ error: { message: 'error occurred' } }), + }; + jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + const testClient = new Client(skyflowConfig, metaData); + const result = testClient.request({ + requestMethod: 'GET', + url: 'https://plain-success.com', + }); + await expect(result).rejects.toEqual(new SkyflowError({ + code: 400, + description: 'Error occurred. - requestId: id', + }, [], true)); + }); + // test('Client request resolves raw string for non-json error case when setoverride is called', async () => { + // // define a navigator to avoid offline check issues and add userAgent + // Object.defineProperty(window, 'navigator', + // { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + // configurable: true + // }); + // const xhrMock: any = { + // open: jest.fn(), + // send: jest.fn() + // .mockImplementation(() => + // { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + // getAllResponseHeaders: jest.fn().mockReturnValue('x-request-id: id'), + // status: 400, + // response: JSON.stringify({ error: { message: 'error occurred in call' } }), + // }; + // jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + // const testClient = new Client(skyflowConfig, metaData); + // testClient.setErrorMessages({ + // 400: "Bad Request", + // 401: "Unauthorized", + // 503: "Service Unavailable", + // 500: "Internal Server Error", + // 404: "Not Found", + // 403: "Forbidden", + // 429: "Too Many Requests", + // CONNECTION: "Connection Error", + // TIMEOUT: "Timeout Error", + // ABORT: "Abort Error", + // NETWORK_GENERIC: "Network Generic Error", + // OFFLINE: "Offline Error", + // 502: "Bad Gateway Error", + // }); + // const result = testClient.request({ + // requestMethod: 'GET', + // url: 'https://plain-success.com', + // }); + // await expect(result).rejects.toEqual(new SkyflowError({ + // code: 400, + // description: 'Bad Request', + // }, [], true)); + // }); + // test('Client request resolves raw string for non-json error case when setoverride is called', async () => { + // // define a navigator to avoid offline check issues and add userAgent + // Object.defineProperty(window, 'navigator', + // { value: { onLine: true, userAgent: 'Mozilla/5.0' }, + // configurable: true + // }); + // const xhrMock: any = { + // open: jest.fn(), + // send: jest.fn() + // .mockImplementation(() => + // { setTimeout(() => xhrMock.onload(), 0); }), setRequestHeader: jest.fn(), + // getAllResponseHeaders: jest.fn().mockReturnValue('content-type: application/json\nx-request-id: id'), + // status: 400, + // response: JSON.stringify({ error: { message: 'error occurred in call' } }), + // }; + // jest.spyOn(window, 'XMLHttpRequest').mockImplementation(() => xhrMock); + // const testClient = new Client(skyflowConfig, metaData); + // testClient.setErrorMessages({ + // 400: "Bad Request", + // 401: "Unauthorized", + // 503: "Service Unavailable", + // 500: "Internal Server Error", + // 404: "Not Found", + // 403: "Forbidden", + // 429: "Too Many Requests", + // CONNECTION: "Connection Error", + // TIMEOUT: "Timeout Error", + // ABORT: "Abort Error", + // NETWORK_GENERIC: "Network Generic Error", + // OFFLINE: "Offline Error", + // 502: "Bad Gateway Error", + // }); + // const result = testClient.request({ + // requestMethod: 'GET', + // url: 'https://plain-success.com', + // }); + // await expect(result).rejects.toEqual(new SkyflowError({ + // code: 400, + // description: 'Bad Request', + // }, [], true)); + // }); + }); +describe("setErrorMessages method", () => { + test("sets custom error messages for specific codes", () => { + const testClient = new Client(skyflowConfig, metaData); + testClient.setErrorMessages({ + 400: "Bad Request", + 401: "Unauthorized", + 503: "Service Unavailable", + 500: "Internal Server Error", + 404: "Not Found", + 403: "Forbidden", + 429: "Too Many Requests", + CONNECTION: "Connection Error", + TIMEOUT: "Timeout Error", + ABORT: "Abort Error", + NETWORK_GENERIC: "Network Generic Error", + OFFLINE: "Offline Error", + 502: "Bad Gateway Error", + }); + expect(testClient.errorMessagesList[400]).toBe("Bad Request"); + expect(testClient.errorMessagesList[401]).toBe("Unauthorized"); + expect(testClient.errorMessagesList[503]).toBe("Service Unavailable"); + expect(testClient.errorMessagesList[500]).toBe("Internal Server Error"); + expect(testClient.errorMessagesList[404]).toBe("Not Found"); + expect(testClient.errorMessagesList[403]).toBe("Forbidden"); + expect(testClient.errorMessagesList[429]).toBe("Too Many Requests"); + expect(testClient.errorMessagesList['CONNECTION']).toBe("Connection Error"); + expect(testClient.errorMessagesList['TIMEOUT']).toBe("Timeout Error"); + expect(testClient.errorMessagesList['ABORT']).toBe("Abort Error"); + expect(testClient.errorMessagesList['NETWORK_GENERIC']).toBe("Network Generic Error"); + expect(testClient.errorMessagesList['OFFLINE']).toBe("Offline Error"); + expect(testClient.errorMessagesList[502]).toBe("Bad Gateway Error"); + }); }); diff --git a/tests/core/external/collect/collect-container.test.js b/tests/core/external/collect/collect-container.test.js index 6876f546..e2b26d58 100644 --- a/tests/core/external/collect/collect-container.test.js +++ b/tests/core/external/collect/collect-container.test.js @@ -14,7 +14,7 @@ import CollectContainer from '../../../../src/core/external/collect/collect-cont import * as iframerUtils from '../../../../src/iframe-libs/iframer'; import SkyflowError from '../../../../src/libs/skyflow-error'; import Skyflow from '../../../../src/skyflow'; -import { LogLevel, Env, ValidationRuleType } from '../../../../src/utils/common'; +import { LogLevel, Env, ValidationRuleType, ErrorType } from '../../../../src/utils/common'; import SKYFLOW_ERROR_CODE from '../../../../src/utils/constants'; import logs from '../../../../src/utils/logs'; import { parameterizedString } from '../../../../src/utils/logs-helper'; @@ -298,6 +298,63 @@ describe('Collect container', () => { }) }); + // it.only("container collect error case when set error is called", () => { + // let collectContainer = new CollectContainer(metaData, [], { logLevel: LogLevel.ERROR, env: Env.PROD }, {}); + // const div1 = document.createElement('div'); + // const div2 = document.createElement('div'); + + // const element1 = collectContainer.create(cvvElement); + // const element2 = collectContainer.create(cardNumberElement); + + // element1.mount(div1); + // element2.mount(div2); + + // const mountCvvCb = onSpy.mock.calls[2][1]; + + // mountCvvCb({ + // name: `element:${cvvElement.type}:${btoa(element1.getID())}`, + // }); + + // const mountCardNumberCb = onSpy.mock.calls[5][1]; + // mountCardNumberCb({ + // name: `element:${cardNumberElement.type}:${btoa(element2.getID())}`, + // }); + + // collectContainer.setError({ + // [ErrorType.BAD_REQUEST]: 'Custom error message for 400', + // }); + + // collectContainer.collect().then( + // res => { + // console.log('RESPONSE RECEIVED IN COLLECT', res); + // } + // ).catch(err => { + // console.log('ERROR RECEIVED IN COLLECT', err); + // expect(err).toBeDefined(); + // }) + + // const collectRequestCb = emitSpy.mock.calls[2][2]; + // collectRequestCb({ + // error: { + // code: 400, + // description: 'Original error message', + // } + // }) + + // collectRequestCb({ + // error:"error", + // }) + + // collectContainer.collect({ + // tokens: true, + // additionalFields:true, + // upsert: true + // }).then().catch(err => { + // expect(err).toBeDefined(); + // }) + // }); + + it("container collect case when tokens are invalid", () => { let collectContainer = new CollectContainer(metaData, [], { logLevel: LogLevel.ERROR, env: Env.PROD }, {}); const div1 = document.createElement('div'); diff --git a/tests/core/external/collect/collect-container.test.ts b/tests/core/external/collect/collect-container.test.ts index fbbf0954..fb9da2cf 100644 --- a/tests/core/external/collect/collect-container.test.ts +++ b/tests/core/external/collect/collect-container.test.ts @@ -20,6 +20,7 @@ import { ICollectOptions, UploadFilesResponse, CollectElementOptions, + ErrorType, } from "../../../../src/utils/common"; global.ResizeObserver = jest.fn().mockImplementation(() => ({ @@ -188,6 +189,67 @@ describe("Collect container", () => { }); }); + it("tests different collect element options for elements", () => { + const collectContainer = new CollectContainer(metaData, [], { + logLevel: LogLevel.ERROR, + env: Env.PROD, + }); + let expiryElement: CollectElement; + let elementOptions: CollectElementOptions = { + required: true, + enableCardIcon: true, + enableCopy: true, + }; + expiryElement = collectContainer.create( + ExpirationDateInput, + elementOptions + ); + const options = expiryElement.getOptions(); + expect(options.enableCardIcon).toBe(true); + expect(options.enableCopy).toBe(true); + }); + it("should successfully collect data from elements, call set error", () => { + const collectContainer = new CollectContainer(metaData, [], { + logLevel: LogLevel.ERROR, + env: Env.PROD, + }); + const div1 = document.createElement("div1"); + const div2 = document.createElement("div2"); + + const element1: CollectElement = collectContainer.create(cvvInput); + const element2: CollectElement = collectContainer.create(cardNumberInput); + + element1.mount(div1); + element2.mount(div2); + + const mountCvvCb = onSpy.mock.calls[2][1]; + mountCvvCb({ + name: `element:${cvvInput.type}:${btoa(element1.getID())}`, + }); + + const mountCardNumberCb = onSpy.mock.calls[5][1]; + mountCardNumberCb({ + name: `element:${cardNumberInput.type}:${btoa(element2.getID())}`, + }); + + collectContainer.setError({[ErrorType.NOT_FOUND]: "Test error message",}) + + collectContainer + .collect(options) + .then() + .catch((err: CollectResponse) => { + expect(err).toBeDefined(); + }); + + const collectRequestCb = emitSpy.mock.calls[2][2]; + collectRequestCb({ + data: {}, + }); + + collectRequestCb({ + error: "error", + }); + }); it("should successfully upload files when elements are mounted", async () => { const collectContainer = new CollectContainer(metaData, [], { logLevel: LogLevel.ERROR, @@ -203,6 +265,8 @@ describe("Collect container", () => { name: `element:${fileInput.type}:${btoa(fileElement.getID())}`, }); + collectContainer.setError({[ErrorType.NOT_FOUND]: "Test error message"}); + const uploadPromise: Promise = collectContainer.uploadFiles(); @@ -222,26 +286,6 @@ describe("Collect container", () => { console.log(JSON.stringify(expectedResponse, null, 2)); expect(expectedResponse).toBeDefined(); }); - - it("tests different collect element options for elements", () => { - const collectContainer = new CollectContainer(metaData, [], { - logLevel: LogLevel.ERROR, - env: Env.PROD, - }); - let expiryElement: CollectElement; - let elementOptions: CollectElementOptions = { - required: true, - enableCardIcon: true, - enableCopy: true, - }; - expiryElement = collectContainer.create( - ExpirationDateInput, - elementOptions - ); - const options = expiryElement.getOptions(); - expect(options.enableCardIcon).toBe(true); - expect(options.enableCopy).toBe(true); - }); }); describe("iframe cleanup logic", () => { @@ -367,7 +411,6 @@ describe("iframe cleanup logic", () => { const iframe1 = document.createElement("iframe"); iframe1.id = element1.iframeName(); document.body.appendChild(iframe1); - // Trigger cleanup by calling collect collectContainer.collect().catch((error) => { expect(error).not.toBeDefined(); diff --git a/tests/core/external/collect/composable-container.test.js b/tests/core/external/collect/composable-container.test.js index a0c92133..8e06ecb3 100644 --- a/tests/core/external/collect/composable-container.test.js +++ b/tests/core/external/collect/composable-container.test.js @@ -5,7 +5,7 @@ import { ElementType } from '../../../../src/core/constants'; import * as iframerUtils from '../../../../src/iframe-libs/iframer'; -import { LogLevel, Env, ValidationRuleType } from '../../../../src/utils/common'; +import { LogLevel, Env, ValidationRuleType, ErrorType } from '../../../../src/utils/common'; import logs from '../../../../src/utils/logs'; import ComposableContainer from "../../../../src/core/external/collect/compose-collect-container"; import ComposableElement from '../../../../src/core/external/collect/compose-collect-element'; @@ -375,6 +375,8 @@ describe('test composable container class',()=>{ }, ], }; + + container.setError({[ErrorType.NOT_FOUND]: "Test error message",}) const collectPromiseError = container.collect(options1); @@ -643,7 +645,7 @@ describe('test composable container class',()=>{ const element1 = container.create(FileInuptElement); container.mount('#composable'); const options = {}; - + container.setError({[ErrorType.NOT_FOUND]: "Test error message",}) const collectPromiseSuccess = container.uploadFiles(options); // Wait for the bearer token promise to resolve and event listener to be set up diff --git a/tests/core/external/reveal/reveal-composable-container.test.js b/tests/core/external/reveal/reveal-composable-container.test.js index 21e2cffe..a5a88a6b 100644 --- a/tests/core/external/reveal/reveal-composable-container.test.js +++ b/tests/core/external/reveal/reveal-composable-container.test.js @@ -365,7 +365,7 @@ describe("Reveal Composable Container Class", () => { token: "1815-6223-1073-1425", containerId:mockUuid } - + testRevealContainer.setError({[SKYFLOW_ERROR_CODE.NOT_FOUND]: "Test error message",}) const res = testRevealContainer.reveal(); await Promise.resolve('token'); expect(res).toBeInstanceOf(Promise); //ELEMENT_EVENTS_TO_CLIENT.MOUNTED @@ -581,4 +581,33 @@ describe("Reveal Composable Container Class", () => { expect(error.error.description).toEqual(logs.errorLogs.NO_ELEMENTS_IN_COMPOSABLE); }) }); + test("reveal before skyflow frame ready event, Error case when bearer token step failed when set error is called",async ()=>{ + // Create a mock that rejects for bearer token + const getBearerTokenFail = jest.fn().mockRejectedValue({ + errors: { + code: 400, + description: "Failed to fetch bearer token" + } + }); + + const clientDataFail = { + ...clientData, + getSkyflowBearerToken: getBearerTokenFail, + }; + + const testRevealContainer = new ComposableRevealContainer(clientDataFail, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + testRevealContainer.setError({[SKYFLOW_ERROR_CODE.NOT_FOUND]: "Test error message",}) + const res = testRevealContainer.reveal(); + + await expect(res).rejects.toEqual({errors:{code:400,description:"Failed to fetch bearer token"}}); + }); }); diff --git a/tests/core/external/reveal/reveal-composable-element.test.js b/tests/core/external/reveal/reveal-composable-element.test.js index 449e1e31..bd8be7d5 100644 --- a/tests/core/external/reveal/reveal-composable-element.test.js +++ b/tests/core/external/reveal/reveal-composable-element.test.js @@ -2,7 +2,7 @@ Copyright (c) 2022 Skyflow, Inc. */ import { LogLevel,Env } from "../../../../src/utils/common"; -import { ELEMENT_EVENTS_TO_IFRAME, FRAME_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES} from "../../../../src/core/constants"; +import { ELEMENT_EVENTS_TO_IFRAME, FRAME_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES, CUSTOM_ERROR_MESSAGES} from "../../../../src/core/constants"; import SkyflowContainer from '../../../../src/core/external/skyflow-container'; import Client from '../../../../src/client'; import EventEmitter from "../../../../src/event-emitter"; @@ -11,7 +11,7 @@ import ComposableRevealInternalElement from "../../../../src/core/external/revea import bus from "framebus"; import { JSDOM } from 'jsdom'; -import { ComposableRevealElement, EventName, RedactionType } from "../../../../src/index-node"; +import { ComposableRevealElement, ErrorType, EventName, RedactionType } from "../../../../src/index-node"; busEvents.getAccessToken = jest.fn(() => Promise.reject('access token')); @@ -211,6 +211,34 @@ describe("Reveal Composable Element Class", () => { const res = testRevealElement.renderFile() await expect(res).rejects.toEqual({"errors": {"code": 400, "description": "No Records Found"}}); }); + + test("file render call error case 2", async () => { + const eventEmitter = new EventEmitter(); + const testRevealElement = new ComposableRevealElement( + "name", + eventEmitter, + '123', + ); + eventEmitter.on(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ':name', (data, cb) => { + console.log('data', data); + cb({error: + { + code: 400, + description: "No Records Found" + } + }); + }); + eventEmitter._emit(`${CUSTOM_ERROR_MESSAGES}:123`, { + [ErrorType.NOT_FOUND]: "No Records Found", + }); + const testEmptyDiv = document.createElement("div"); + testEmptyDiv.setAttribute("id", "testDiv"); + document.body.appendChild(testEmptyDiv); + expect(document.getElementById("testDiv")).not.toBeNull(); + + const res = testRevealElement.renderFile() + await expect(res).rejects.toEqual({"errors": {"code": 400, "description": "No Records Found"}}); + }); test("update method", async () => { const eventEmitter = new EventEmitter(); const testRevealElement = new ComposableRevealElement( diff --git a/tests/core/external/reveal/reveal-composable-internal.test.js b/tests/core/external/reveal/reveal-composable-internal.test.js index edc29bea..f825f1d7 100644 --- a/tests/core/external/reveal/reveal-composable-internal.test.js +++ b/tests/core/external/reveal/reveal-composable-internal.test.js @@ -1,8 +1,8 @@ /* Copyright (c) 2022 Skyflow, Inc. */ -import { LogLevel,Env } from "../../../../src/utils/common"; -import { ELEMENT_EVENTS_TO_IFRAME, COMPOSABLE_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES} from "../../../../src/core/constants"; +import { LogLevel,Env, ErrorType } from "../../../../src/utils/common"; +import { ELEMENT_EVENTS_TO_IFRAME, COMPOSABLE_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES, CUSTOM_ERROR_MESSAGES} from "../../../../src/core/constants"; import RevealElement from "../../../../src/core/external/reveal/reveal-element"; import SkyflowContainer from '../../../../src/core/external/skyflow-container'; import Client from '../../../../src/client'; @@ -213,6 +213,9 @@ describe("Reveal Element Class", () => { {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, { logLevel: LogLevel.ERROR,env:Env.PROD } ); + groupEmiitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, {errorMessages: { + [ErrorType.NOT_FOUND]: "No Records Found", + }}); window.dispatchEvent(new MessageEvent('message', { data: { type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element1", diff --git a/tests/core/external/reveal/reveal-container.test.js b/tests/core/external/reveal/reveal-container.test.js index 146c916b..48f03257 100644 --- a/tests/core/external/reveal/reveal-container.test.js +++ b/tests/core/external/reveal/reveal-container.test.js @@ -296,7 +296,9 @@ describe("Reveal Container Class", () => { const emitData = emitSpy.mock.calls[1][1]; const emitCb = emitSpy.mock.calls[1][2]; expect(emitEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS+mockUuid); - expect(emitData).toEqual({type: REVEAL_TYPES.REVEAL, containerId: '1234', records :[{token:"123"}]}); + expect(emitData) + .toEqual({type: REVEAL_TYPES.REVEAL, + containerId: '1234', records :[{token:"123"}], errorMessages: {}}); emitCb({"success":[{token:"1815-6223-1073-1425"}]}); }); test("on container mounted call back 5",()=>{ diff --git a/tests/core/external/reveal/reveal-container.test.ts b/tests/core/external/reveal/reveal-container.test.ts index e6516402..a1fb0db4 100644 --- a/tests/core/external/reveal/reveal-container.test.ts +++ b/tests/core/external/reveal/reveal-container.test.ts @@ -372,6 +372,8 @@ describe("Reveal Container Class", () => { const onCb = on.mock.calls[0][1]; onCb({ token: testToken, containerId: mockUuid }); + testRevealContainer.setError({}); + // Call reveal and await response const revealPromise = testRevealContainer.reveal(); @@ -385,6 +387,7 @@ describe("Reveal Container Class", () => { type: REVEAL_TYPES.REVEAL, containerId: mockUuid, records: [{ token: testToken }], + errorMessages: {}, }); // Simulate successful reveal response diff --git a/tests/core/external/reveal/reveal-element.test.js b/tests/core/external/reveal/reveal-element.test.js index f0c380cd..491bfdb8 100644 --- a/tests/core/external/reveal/reveal-element.test.js +++ b/tests/core/external/reveal/reveal-element.test.js @@ -1,8 +1,8 @@ /* Copyright (c) 2022 Skyflow, Inc. */ -import { LogLevel,Env } from "../../../../src/utils/common"; -import { ELEMENT_EVENTS_TO_IFRAME, FRAME_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES} from "../../../../src/core/constants"; +import { LogLevel,Env, ErrorType } from "../../../../src/utils/common"; +import { ELEMENT_EVENTS_TO_IFRAME, FRAME_REVEAL, ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES, CUSTOM_ERROR_MESSAGES} from "../../../../src/core/constants"; import RevealElement from "../../../../src/core/external/reveal/reveal-element"; import SkyflowContainer from '../../../../src/core/external/skyflow-container'; import Client from '../../../../src/client'; @@ -13,6 +13,7 @@ import * as busEvents from '../../../../src/utils/bus-events'; import bus from "framebus"; import { JSDOM } from 'jsdom'; +import EventEmitter from "../../../../src/event-emitter"; busEvents.getAccessToken = jest.fn(() => Promise.reject('access token')); @@ -286,7 +287,9 @@ describe("Reveal Element Class", () => { const cb = jest.fn(); onCallback({}, cb); expect(emitSpy.mock.calls[3][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + '123'); - expect(emitSpy.mock.calls[3][1]).toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, iframeName: testIframeName}); + expect(emitSpy.mock.calls[3][1]) + .toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, + containerId: mockUuid, iframeName: testIframeName, "errorMessages": {}}); const emitCb = emitSpy.mock.calls[3][2]; emitCb({ success: { skyflow_id: '1244', column: 'column' } }); }); @@ -398,6 +401,7 @@ describe("Reveal Element Class", () => { }); }); test("file render error case", () => { + const emit = new EventEmitter(); const testRevealElement = new RevealElement( { skyflowID: "1244", @@ -407,7 +411,7 @@ describe("Reveal Element Class", () => { }, undefined, clientData, - {containerId:containerId,isMounted:true,eventEmitter:groupEmiitter, isControllerFrameReady: true}, + {containerId:containerId,isMounted:true,eventEmitter:emit, isControllerFrameReady: true}, elementId, { logLevel: LogLevel.ERROR,env:Env.PROD } ); @@ -420,7 +424,9 @@ describe("Reveal Element Class", () => { testRevealElement.mount("#testDiv"); - + emit._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, {errorMessages: { + [ErrorType.NOT_FOUND]: "No Records Found", + }}); expect(document.querySelector("iframe")).toBeTruthy(); const testIframeName = `${FRAME_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); @@ -442,7 +448,11 @@ describe("Reveal Element Class", () => { }); expect(emitSpy.mock.calls[3][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + '123'); - expect(emitSpy.mock.calls[3][1]).toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, iframeName: testIframeName}); + expect(emitSpy.mock.calls[3][1]) + .toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, + iframeName: testIframeName, "errorMessages": { + [ErrorType.NOT_FOUND]: "No Records Found", + }}); const emitCb = emitSpy.mock.calls[3][2]; emitCb({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); }); @@ -601,6 +611,25 @@ describe("Reveal Element Methods",()=>{ name:testRevealElement.iframeName(), }); testRevealElement.setError("errorText"); + groupEmiitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, { + errorMessages: { GENERIC_ERROR: "errorText" } + }); + expect(testRevealElement.isClientSetError()).toBe(true); + expect(emitSpy.mock.calls[1][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + testRevealElement.iframeName()); + expect(emitSpy.mock.calls[1][1]).toEqual({name: testRevealElement.iframeName(), clientErrorText: "errorText", isTriggerError: true}); + expect(emitSpy).toBeCalled(); + }); + it("setError method case 2",()=>{ + testRevealElement.mount("#testDiv"); + const mountedEventName = ELEMENT_EVENTS_TO_CLIENT.MOUNTED + testRevealElement.iframeName(); + const onCbName = on.mock.calls[0][0]; + expect(onCbName).toBe(mountedEventName); + const onCb = on.mock.calls[0][1]; + onCb({ + name:testRevealElement.iframeName(), + }); + testRevealElement.setError("errorText"); + groupEmiitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, undefined); expect(testRevealElement.isClientSetError()).toBe(true); expect(emitSpy.mock.calls[1][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + testRevealElement.iframeName()); expect(emitSpy.mock.calls[1][1]).toEqual({name: testRevealElement.iframeName(), clientErrorText: "errorText", isTriggerError: true}); diff --git a/tests/core/external/reveal/reveal-element.test.ts b/tests/core/external/reveal/reveal-element.test.ts index fa0872bf..a42047d4 100644 --- a/tests/core/external/reveal/reveal-element.test.ts +++ b/tests/core/external/reveal/reveal-element.test.ts @@ -9,6 +9,7 @@ import { REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES, ElementType, + CUSTOM_ERROR_MESSAGES, } from "../../../../src/core/constants"; import RevealElement from "../../../../src/core/external/reveal/reveal-element"; import SkyflowContainer from "../../../../src/core/external/skyflow-container"; @@ -22,7 +23,7 @@ import { Metadata } from "../../../../src/core/internal/internal-types"; import { ContainerType, ISkyflow } from "../../../../src/skyflow"; import { IRevealElementInput } from "../../../../src/core/external/reveal/reveal-container"; import EventEmitter from "../../../../src/event-emitter"; -import { RevealElementInput } from "../../../../src/index-node"; +import { ErrorType, RevealElementInput } from "../../../../src/index-node"; jest .spyOn(busEvents, "getAccessToken") @@ -367,6 +368,7 @@ describe("Reveal Element Class", () => { }, containerId: mockUuid, iframeName: testIframeName, + "errorMessages": {}, }); const emitCb = emitSpy.mock.calls[3][2]; emitCb({ success: { skyflow_id: "1244", column: "column" } }); @@ -447,6 +449,9 @@ describe("Reveal Element Class", () => { elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } ); + groupEmiitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, {errorMessages: { + [ErrorType.NOT_FOUND]: "No Records Found", + }}); const testEmptyDiv = document.createElement("div"); testEmptyDiv.setAttribute("id", "testDiv"); @@ -484,6 +489,7 @@ describe("Reveal Element Class", () => { }); test("file render error case", () => { + let emitter = new EventEmitter(); const testRevealElement = new RevealElement( { skyflowID: "1244", @@ -496,12 +502,13 @@ describe("Reveal Element Class", () => { { containerId: containerId, isMounted: true, - eventEmitter: groupEmiitter, + eventEmitter: emitter, type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } ); + emitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, undefined); const testEmptyDiv = document.createElement("div"); testEmptyDiv.setAttribute("id", "testDiv"); document.body.appendChild(testEmptyDiv); @@ -534,7 +541,8 @@ describe("Reveal Element Class", () => { }); expect(emitSpy.mock.calls[3][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + '123'); - expect(emitSpy.mock.calls[3][1]).toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, iframeName: testIframeName}); + expect(emitSpy.mock.calls[3][1]) + .toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, iframeName: testIframeName, "errorMessages": {}},); const emitCb = emitSpy.mock.calls[3][2]; emitCb({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); }); @@ -636,6 +644,7 @@ describe("Reveal Element Methods", () => { elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } ); + const emit = new EventEmitter(); const testRevealElement2 = new RevealElement( { skyflowID: "1244", @@ -675,7 +684,7 @@ describe("Reveal Element Methods", () => { }, undefined, metaData, - { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, type: ContainerType.REVEAL + { containerId: containerId, isMounted: false, eventEmitter: emit, type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -692,6 +701,64 @@ describe("Reveal Element Methods", () => { off, }); }); + test("file render error case", () => { + let emitter = new EventEmitter(); + const testRevealElement = new RevealElement( + { + skyflowID: "1244", + column: "column", + table: "table", + altText: "alt text", + }, + undefined, + metaData, + { + containerId: containerId, + isMounted: true, + eventEmitter: emitter, + type: ContainerType.REVEAL + }, + elementId, + { logLevel: LogLevel.ERROR, env: Env.PROD } + ); + emitter._emit(`${CUSTOM_ERROR_MESSAGES}:${containerId}`, {}); + const testEmptyDiv = document.createElement("div"); + testEmptyDiv.setAttribute("id", "testDiv"); + document.body.appendChild(testEmptyDiv); + expect(document.getElementById("testDiv")).not.toBeNull(); + + expect(testRevealElement.isMounted()).toBe(false); + + testRevealElement.mount("#testDiv"); + + expect(document.querySelector("iframe")).toBeTruthy(); + const testIframeName = `${FRAME_REVEAL}:${btoa( + mockUuid + )}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + const eventListenerName = ELEMENT_EVENTS_TO_CLIENT.MOUNTED + testIframeName; + const onCbName = on.mock.calls[0][0]; + expect(onCbName).toBe(eventListenerName); + const onCb = on.mock.calls[0][1]; + onCb({ + name: testIframeName, + }); + expect(testRevealElement.isMounted()).toBe(true); + expect(testRevealElement.iframeName()).toBe(testIframeName); + testRevealElement.renderFile().then( + data => console.log('data', data) + ).catch ( + (error) => { + expect(error).toEqual({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); + }); + + expect(emitSpy.mock.calls[3][0]).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + '123'); + expect(emitSpy.mock.calls[3][1]) + .toEqual({type: REVEAL_TYPES.RENDER_FILE, records: {altText: "alt text", skyflowID: '1244', column: 'column', table: 'table' }, containerId: mockUuid, iframeName: testIframeName, "errorMessages": {}},); + const emitCb = emitSpy.mock.calls[3][2]; + emitCb({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); + }); test("unmount method", () => { testRevealElement.unmount(); diff --git a/tests/core/internal/frame-element-init.additional.test.js b/tests/core/internal/frame-element-init.additional.test.js index 6e4f8bc7..bf5508e1 100644 --- a/tests/core/internal/frame-element-init.additional.test.js +++ b/tests/core/internal/frame-element-init.additional.test.js @@ -48,6 +48,7 @@ import { insertDataInCollect, updateRecordsBySkyflowIDComposable, } from '../../../src/core-utils/collect'; +import { ErrorType } from '../../../src/index-node'; // Mock element-options to bypass complex row merging logic that expects prior group structure jest.mock('../../../src/libs/element-options', () => ({ validateAndSetupGroupOptions: (oldGroup, newGroup) => newGroup || oldGroup || { rows: [] }, @@ -462,10 +463,12 @@ describe('FrameElementInit extended unit tests', () => { name: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${multiElement.iFrameName}`, clientConfig, options: { meta: 'x' }, + errorMessages:{}, }, }); await flushPromises(); - expect(instance['multipleUploadFiles']).toHaveBeenCalledWith(multiElement, clientConfig, { meta: 'x' }); + expect(instance['multipleUploadFiles']) + .toHaveBeenCalledWith(multiElement, clientConfig, { meta: 'x' }, {}); expect(parentPostSpy).toHaveBeenCalledWith( expect.objectContaining({ type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${multiElement.iFrameName}`, @@ -509,6 +512,9 @@ describe('FrameElementInit extended unit tests', () => { name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + instance.containerId, data: { type: COLLECT_TYPES.COLLECT }, clientConfig, + errorMessages:{ + [ErrorType.BAD_REQUEST]: 'Bad request error', + }, }, }); await flushPromises(); @@ -532,6 +538,9 @@ describe('FrameElementInit extended unit tests', () => { name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + instance.containerId, data: { type: COLLECT_TYPES.COLLECT }, clientConfig, + errorMessages:{ + [ErrorType.BAD_REQUEST]: 'Bad request error', + }, }, }); await flushPromises(); @@ -554,6 +563,9 @@ describe('FrameElementInit extended unit tests', () => { name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + instance.containerId, data: { type: COLLECT_TYPES.FILE_UPLOAD }, clientConfig, + errorMessages:{ + [ErrorType.BAD_REQUEST]: 'Bad request error', + }, }, }); await flushPromises(); @@ -576,6 +588,9 @@ describe('FrameElementInit extended unit tests', () => { name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + instance.containerId, data: { type: COLLECT_TYPES.FILE_UPLOAD }, clientConfig, + errorMessages:{ + [ErrorType.BAD_REQUEST]: 'Bad request error', + }, }, }); await flushPromises(); diff --git a/tests/core/internal/frame-element-init.test.js b/tests/core/internal/frame-element-init.test.js index 4b6a5a02..8ddd65fc 100644 --- a/tests/core/internal/frame-element-init.test.js +++ b/tests/core/internal/frame-element-init.test.js @@ -1,10 +1,11 @@ import FrameElementInit from '../../../src/core/internal/frame-element-init'; -import { ELEMENT_EVENTS_TO_IFRAME, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, ElementType } from '../../../src/core/constants'; +import { ELEMENT_EVENTS_TO_IFRAME, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, ElementType, COLLECT_TYPES } from '../../../src/core/constants'; import bus from 'framebus'; import SkyflowError from '../../../src/libs/skyflow-error'; import * as helpers from '../../../src/utils/helpers'; import Client from '../../../src/client'; import IFrameFormElement from '../../../src/core/internal/iframe-form'; +import { ErrorType } from '../../../src/index-node'; // Helper to flush pending microtasks (Promise.allSettled resolution) deterministically const flushPromises = async (cycles = 3) => { @@ -678,6 +679,12 @@ describe('FrameElementInit Additional Test Cases', () => { }, options: { // Additional metadata for file upload + }, + errorMessages: { + [ErrorType.ABORT]: 'File upload aborted by user', + }, + data:{ + type: COLLECT_TYPES.FILE_UPLOAD } } })); diff --git a/tests/core/internal/skyflow-frame/skyflow-frame-controller.test.ts b/tests/core/internal/skyflow-frame/skyflow-frame-controller.test.ts index f3086dce..2716e8b6 100644 --- a/tests/core/internal/skyflow-frame/skyflow-frame-controller.test.ts +++ b/tests/core/internal/skyflow-frame/skyflow-frame-controller.test.ts @@ -1,8 +1,10 @@ + /* Copyright (c) 2025 Skyflow, Inc. */ import bus from "framebus"; import { + COLLECT_TYPES, ELEMENT_EVENTS_TO_IFRAME, PUREJS_TYPES, REVEAL_TYPES, @@ -19,9 +21,10 @@ import { IDeleteRecordInput, } from "../../../../src/utils/common"; import SkyflowFrameController from "../../../../src/core/internal/skyflow-frame/skyflow-frame-controller"; -import { InsertOptions } from "../../../../src/index-node"; +import { ErrorType, InsertOptions } from "../../../../src/index-node"; import { ISkyflow } from "../../../../src/skyflow"; import Client from "../../../../src/client"; +import { set } from "core-js/core/dict"; jest.mock("../../../../src/utils/bus-events", () => ({ ...jest.requireActual("../../../../src/utils/bus-events"), @@ -1818,6 +1821,7 @@ describe("test reveal request", () => { ...clientData.client, request: clientReq, toJSON: toJson, + setErrorMessages: jest.fn(), } as unknown as Client) ); @@ -1850,3 +1854,164 @@ describe("test reveal request", () => { onCb(data1, emitterCb); }); }); +describe("SkyflowFrameController error message handling", () => { + let emitSpy: jest.SpyInstance; + let targetSpy: jest.SpyInstance; + let onSpy: jest.SpyInstance; + let windowSpy: jest.SpyInstance; + let testValue: any; + beforeEach(() => { + emitSpy = jest.spyOn(bus, "emit"); + targetSpy = jest.spyOn(bus, "target"); + onSpy = jest.spyOn(bus, "on"); + targetSpy.mockReturnValue({ on }); + window.name = "controller:frameId:clientDomain:true"; + windowSpy = jest.spyOn(window, "parent", "get"); + windowSpy.mockImplementation(() => ({ + frames: {}, + })); + testValue = { + iFrameFormElement: { + fieldType: "FILE_INPUT", + state: { + value: { + type: "file", + name: "test-file.txt", + size: 1024, + }, + isFocused: false, + isValid: false, + isEmpty: true, + isComplete: false, + name: "test-name", + isRequired: true, + isTouched: false, + selectedCardScheme: "", + }, + tableName: "test-table-name", + preserveFileName: true, + onFocusChange: jest.fn(), + }, + }; + }); + + test("should handle custom error message from mocked request for collect", (done) => { + const controller = SkyflowFrameController.init(mockUuid); + // Mock tokenize on the instance + controller.tokenize = jest.fn(() => Promise.reject({ + error: { + code: 400, + message: "Custom error from mock tokenize" + } + })); + + const emitEventName = emitSpy.mock.calls[1][0]; + const emitCb = emitSpy.mock.calls[1][2]; + expect(emitEventName).toBe( + ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + mockUuid + ); + emitCb(clientData); + const options = { + elementIds: ['ID', 'element2'], + } + windowSpy.mockImplementation(() => ({ + frames: { + "element:CARD_NUMBER:ID:CONTAINER-ID:ERROR:clientDomain": { + document: { + getElementById: () => testValue, + }, + }, + }, + parent: { + frames: { + "element:CARD_NUMBER:ID:CONTAINER-ID:ERROR:clientDomain": { + document: { + getElementById: () => testValue, + }, + }, + }, + }, + })); + const onCb = on.mock.calls[1][1]; + const data = { + type: COLLECT_TYPES.COLLECT, + records, + options, + elementIds: [{ + frameId: 'element:CARD_NUMBER:ID'}], + containerId: 'CONTAINER-ID', + errorMessages: { + [ErrorType.ABORT]: "Custom error from mock request", + } + }; + const cb2 = jest.fn((result) => { + try { + expect(result.error).toBeDefined(); + done(); + } catch (err) { + done(err); + } + }); + onCb(data, cb2); + }); + test("should handle custom error message from mocked request for file upload", (done) => { + const controller = SkyflowFrameController.init(mockUuid); + // Mock tokenize on the instance + // controller.parallelUploadFiles = jest.fn(() => Promise.reject({ + // error: { + // code: 400, + // message: "Custom error from mock tokenize" + // } + // })); + + const emitEventName = emitSpy.mock.calls[1][0]; + const emitCb = emitSpy.mock.calls[1][2]; + expect(emitEventName).toBe( + ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + mockUuid + ); + emitCb(clientData); + const options = { + elementIds: ['ID', 'element2'], + } + windowSpy.mockImplementation(() => ({ + frames: { + "element:FILE_INPUT:ID:CONTAINER-ID:ERROR:clientDomain": { + document: { + getElementById: () => testValue, + }, + }, + }, + parent: { + frames: { + "element:FILE_INPUT:ID:CONTAINER-ID:ERROR:clientDomain": { + document: { + getElementById: () => testValue, + }, + }, + }, + }, + })); + const onCb = on.mock.calls[1][1]; + const data = { + type: COLLECT_TYPES.FILE_UPLOAD, + records, + options, + elementIds: [{ + frameId: 'element:FILE_INPUT:ID'}], + containerId: 'CONTAINER-ID', + errorMessages: { + [ErrorType.ABORT]: "Custom error from mock request", + } + }; + const cb2 = jest.fn((result) => { + try { + expect(result.error).toBeDefined(); + done(); + } catch (err) { + done(err); + } + }); + onCb(data, cb2); + }); + +}); diff --git a/tests/skyflow.test.ts b/tests/skyflow.test.ts index 242a2533..d01d53c6 100644 --- a/tests/skyflow.test.ts +++ b/tests/skyflow.test.ts @@ -9,6 +9,7 @@ import { DeleteResponse, DeleteResponseRecord, DetokenizeResponse, + ErrorType, GetByIdResponse, GetByIdResponseRecord, GetResponse, @@ -1079,4 +1080,9 @@ describe("Skyflow delete tests", () => { done(err); } }); + + test('skyflow error type check', () => { + const errorType = Skyflow.ErrorType; + expect(errorType).toBe(ErrorType); + }); }); From b1e9ebe81c952edbdce9f597ae78839927479b4a Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 6 Jan 2026 02:54:40 +0000 Subject: [PATCH 2/8] [AUTOMATED] Release - 2.6.0-dev.8a1d1ee --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae9414ce..b6cd2fcf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.6.0", + "version": "2.6.0-dev.8a1d1ee", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 92afc088287a6c19c7e59680f044c1e4cd18ebc1 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 6 Jan 2026 13:26:13 +0530 Subject: [PATCH 3/8] SK-2416 Update error code --- src/client/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/index.ts b/src/client/index.ts index 1c286f15..2f874530 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -119,7 +119,7 @@ class Client { const contentType = headerMap['content-type']; const requestId = headerMap['x-request-id']; if (httpRequest.status < 200 || httpRequest.status >= 400) { - const overrideCodes = [400, 401, 403, 404, 429, 500]; + const overrideCodes = [400, 401, 403, 404, 429, 500, 502, 503]; if (contentType && contentType.includes('application/json')) { let description = JSON.parse(httpRequest.response); if (description?.error?.message) { From c809ded29cddee2d9c8395dfc473b7f11aced0f0 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 6 Jan 2026 07:56:53 +0000 Subject: [PATCH 4/8] [AUTOMATED] Release - 2.6.0-dev.92afc08 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6cd2fcf..658e8bd8 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.6.0-dev.8a1d1ee", + "version": "2.6.0-dev.92afc08", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 4fbb9eaf66ed8b984db09fd67a01bb1b8676a05d Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 7 Jan 2026 13:05:18 +0530 Subject: [PATCH 5/8] SK-2457 remove type from purejs --- src/core-utils/reveal.ts | 35 ++++++++++++------- .../skyflow-frame/skyflow-frame-controller.ts | 11 +++++- 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index 05476ace..9e6fa65d 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -37,14 +37,26 @@ const formatForPureJsSuccess = (response: IApiSuccessResponse) => { { token: record.token, value: record.value, valueType: record.valueType })); }; -const formatForPureJsFailure = (cause, tokenId:string) => ({ - token: tokenId, - ...new SkyflowError({ - code: cause?.error?.code, - description: cause?.error?.description, - type: cause?.error?.type, - }, [], true), -}); +const formatForPureJsFailure = (cause, tokenId:string, purejs: boolean) => { + if (purejs) { + return { + token: tokenId, + error: { + code: cause?.error?.code, + description: cause?.error?.description, + }, + }; + } + return ({ + token: tokenId, + ...new SkyflowError({ + code: cause?.error?.code, + description: cause?.error?.description, + type: cause?.error?.type, + }, [], true), + }); +}; + const formatForRenderFileFailure = (cause, skyflowID:string, column: string) => ({ skyflowId: skyflowID, column, @@ -209,6 +221,7 @@ export const getFileURLFromVaultBySkyflowIDComposable = ( export const fetchRecordsByTokenId = ( tokenIdRecords: IRevealRecord[], client: Client, + purejs: boolean, ): Promise => new Promise((rootResolve, rootReject) => { const clientId = client.toJSON()?.metaData?.uuid || ''; getAccessToken(clientId).then((authToken) => { @@ -225,7 +238,7 @@ export const fetchRecordsByTokenId = ( apiResponse.push(...fieldsData); }, (cause: any) => { - const errorData = formatForPureJsFailure(cause, tokenRecord.token as string); + const errorData = formatForPureJsFailure(cause, tokenRecord.token as string, purejs); printLog(errorData.error?.description || '', MessageType.ERROR, LogLevel.ERROR); apiResponse.push(errorData); }, @@ -280,7 +293,7 @@ export const fetchRecordsByTokenIdComposable = ( }); }, (cause: any) => { - const errorData = formatForPureJsFailure(cause, tokenRecord?.token ?? ''); + const errorData = formatForPureJsFailure(cause, tokenRecord?.token ?? '', false); printLog(errorData?.error?.description ?? '', MessageType.ERROR, LogLevel.ERROR); apiResponse?.push({ ...errorData, @@ -443,7 +456,6 @@ export const fetchRecordsGET = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, - type: rejectedResult?.error?.type, }, ids: skyflowIdRecord.ids, ...(skyflowIdRecord?.columnName ? { columnName: skyflowIdRecord?.columnName } @@ -515,7 +527,6 @@ export const fetchRecordsBySkyflowID = async ( error: { code: rejectedResult?.error?.code, description: rejectedResult?.error?.description, - type: rejectedResult?.error?.type, }, ids: skyflowIdRecord.ids, }; diff --git a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts index e067943a..cd28462e 100644 --- a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts +++ b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts @@ -140,6 +140,7 @@ class SkyflowFrameController { fetchRecordsByTokenId( data.records as IRevealRecord[], this.#client, + true, ).then( (resolvedResult: IRevealResponseType) => { printLog( @@ -431,7 +432,7 @@ class SkyflowFrameController { revealData(revealRecords: IRevealRecord[], containerId: string): Promise { const id = containerId; return new Promise((resolve, reject) => { - fetchRecordsByTokenId(revealRecords, this.#client).then( + fetchRecordsByTokenId(revealRecords, this.#client, false).then( (resolvedResult) => { const formattedResult = formatRecordsForIframe(resolvedResult); bus @@ -486,6 +487,14 @@ class SkyflowFrameController { ); }) .catch((error) => { + if (error?.error?.type) { + error = { + error: { + code: error?.error?.code, + description: error?.error?.description, + }, + }; + } rootReject(error); }); }).catch((err) => { From 7a3875136cbbe4c09228f3356d894761813168bf Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 7 Jan 2026 07:36:05 +0000 Subject: [PATCH 6/8] [AUTOMATED] Release - 2.6.0-dev.4fbb9ea --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 658e8bd8..c6a68521 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.6.0-dev.92afc08", + "version": "2.6.0-dev.4fbb9ea", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 189f37178b6dfab5bfe5696afcb48157889ccb1d Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 7 Jan 2026 14:25:42 +0530 Subject: [PATCH 7/8] SK-2457 Fix the error object --- src/core/internal/frame-element-init.ts | 18 +++++++++++++++--- .../frame-element-init.additional.test.js | 4 ++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index d2c8a9c7..9e8ac3e0 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -182,7 +182,11 @@ export default class FrameElementInit { } } } else if (result.status === 'rejected') { - errorResponse.push(result.reason); + if (result.reason?.error) { + errorResponse.push({ error: result.reason?.error }); + } else { + errorResponse.push(result.reason); + } } }); if (errorResponse.length === 0) { @@ -568,7 +572,11 @@ export default class FrameElementInit { } } } else if (result.status === 'rejected') { - errorResponse.push({ error: result.reason }); + if (result?.reason?.error) { + errorResponse.push({ error: result?.reason?.error }); + } else { + errorResponse.push({ error: result.reason }); + } } }); if (errorResponse.length === 0) { @@ -600,7 +608,11 @@ export default class FrameElementInit { } } } else if (result.status === 'rejected') { - errorResponse.push({ error: result.reason }); + if (result?.reason?.error) { + errorResponse.push({ error: result?.reason?.error }); + } else { + errorResponse.push({ error: result.reason }); + } } }); if (errorResponse.length === 0) { diff --git a/tests/core/internal/frame-element-init.additional.test.js b/tests/core/internal/frame-element-init.additional.test.js index bf5508e1..6ab29ecf 100644 --- a/tests/core/internal/frame-element-init.additional.test.js +++ b/tests/core/internal/frame-element-init.additional.test.js @@ -307,11 +307,11 @@ describe('FrameElementInit extended unit tests', () => { helpers.fileValidation = jest.fn(() => true); helpers.vaildateFileName = jest.fn(() => true); mockClientRequest - .mockImplementationOnce(() => Promise.reject({error: 'badA'})) + .mockImplementationOnce(() => Promise.reject({error: {code: '500', message: 'badA'}})); // .mockImplementationOnce(() => Promise.reject('badB')); const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' }; await expect(instance['multipleUploadFiles'](fileElement, config, undefined)) - .rejects.toEqual({ errorResponse: [{ error: { error: 'badA' } }] }); + .rejects.toEqual({ errorResponse: [{ error: {code: '500', message: 'badA'}}] }); expect(mockClientRequest).toHaveBeenCalledTimes(1); }); From 01cd6669eb008b6baee508ec64ad107ecc7e8fa8 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 7 Jan 2026 08:56:33 +0000 Subject: [PATCH 8/8] [AUTOMATED] Release - 2.6.0-dev.189f371 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6a68521..31b02741 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.6.0-dev.4fbb9ea", + "version": "2.6.0-dev.189f371", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js",