From dd00ff70f4ba4b7b133bb67f5648ac049ae024e0 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:26:10 +0530 Subject: [PATCH 01/50] Release/25.7.8.2 (#615) --- package.json | 2 +- src/core-utils/collect.ts | 147 +++- src/core-utils/reveal.ts | 109 +++ src/core/constants.ts | 24 + src/core/external/collect/collect-element.ts | 250 ++++--- .../collect/compose-collect-container.ts | 372 +++++++---- .../collect/compose-collect-element.ts | 57 +- src/core/external/common/iframe.ts | 2 +- .../reveal/composable-reveal-container.ts | 474 +++++++++++++ .../reveal/composable-reveal-element.ts | 66 ++ .../reveal/composable-reveal-internal.ts | 610 +++++++++++++++++ .../internal/composable-frame-element-init.ts | 369 +++++++++++ src/core/internal/frame-element-init.ts | 625 +++++++++++++++++- src/core/internal/iframe-form/index.ts | 181 ++++- src/core/internal/index.ts | 39 +- src/core/internal/reveal/reveal-frame.ts | 299 +++++++-- .../skyflow-frame/skyflow-frame-controller.ts | 8 +- src/index-internal.ts | 5 + src/libs/element-options.ts | 4 +- src/skyflow.ts | 62 ++ src/utils/common/index.ts | 12 + src/utils/constants.ts | 6 + src/utils/logs.ts | 6 + 23 files changed, 3443 insertions(+), 286 deletions(-) create mode 100644 src/core/external/reveal/composable-reveal-container.ts create mode 100644 src/core/external/reveal/composable-reveal-element.ts create mode 100644 src/core/external/reveal/composable-reveal-internal.ts create mode 100644 src/core/internal/composable-frame-element-init.ts diff --git a/package.json b/package.json index 8b9a7c65..c7281b16 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.2", + "version": "2.4.2-dev.6c87190", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core-utils/collect.ts b/src/core-utils/collect.ts index e4672d8a..626dab2e 100644 --- a/src/core-utils/collect.ts +++ b/src/core-utils/collect.ts @@ -203,7 +203,7 @@ const updateRecordsInVault = ( options, ) => { const table = skyflowIdRecord.fields.table; - const skyflowID = skyflowIdRecord.skyflowID; + const skyflowID = skyflowIdRecord?.skyflowID; skyflowIdRecord.fields = omit(skyflowIdRecord.fields, 'table'); skyflowIdRecord.fields = omit(skyflowIdRecord.fields, 'skyflowID'); return client.request({ @@ -277,6 +277,151 @@ export const updateRecordsBySkyflowID = async ( }); }); +export const updateRecordsBySkyflowIDComposable = async ( + skyflowIdRecords, + client: Client, + options, + authToken: string, +) => new Promise((rootResolve, rootReject) => { + let updateResponseSet: Promise[]; + // eslint-disable-next-line prefer-const + updateResponseSet = skyflowIdRecords?.updateRecords?.map( + (skyflowIdRecord: IInsertRecord) => new Promise((resolve, reject) => { + updateRecordsInVault(skyflowIdRecord, client, authToken, options) + ?.then((resolvedResult: any) => { + const resp = constructFinalUpdateRecordResponse( + resolvedResult, + options?.tokens, + skyflowIdRecord, + ); + resolve(resp); + }, + (rejectedResult) => { + let errorResponse = rejectedResult; + if (rejectedResult?.error) { + errorResponse = { + error: { + code: rejectedResult?.error?.code, + description: rejectedResult?.error?.description, + }, + }; + } + printLog(rejectedResult?.error?.description ?? '', MessageType.ERROR, LogLevel.ERROR); + reject(errorResponse); + })?.catch((error) => { + reject(error); + }); + }), + ); + Promise.allSettled(updateResponseSet)?.then((resultSet: any) => { + const recordsResponse: any[] = []; + const errorsResponse: any[] = []; + resultSet?.forEach((result: { status: string; value: any; reason?: any; }) => { + if (result?.status === 'fulfilled') { + recordsResponse?.push(result?.value); + } else { + errorsResponse?.push(result?.reason); + } + }); + + if (errorsResponse?.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse?.length === 0) { + rootReject({ errors: errorsResponse }); + } else { + rootReject({ records: recordsResponse, errors: errorsResponse }); + } + }); +}); + +export const insertDataInCollect = async ( + records, + client: Client, + options, + finalInsertRecords, + authToken: string, +) => new Promise((resolve) => { + let insertResponse: any; + let insertErrorResponse: any; + client + ?.request({ + body: { + records, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + ?.then((response: any) => { + insertResponse = constructInsertRecordResponse( + response, + options?.tokens, + finalInsertRecords?.records, + ); + resolve(insertResponse); + }) + ?.catch((error) => { + insertErrorResponse = { + errors: [ + { + error: { + code: error?.error?.code, + description: error?.error?.description, + }, + }, + ], + }; + resolve(insertErrorResponse); + }); +}); + +export const insertDataInMultipleFiles = async ( + records, + client: Client, + options, + finalInsertRecords, + authToken: string, +) => new Promise((resolve) => { + let insertResponse: any; + let insertErrorResponse: any; + client + ?.request({ + body: { + records, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + ?.then((response: any) => { + insertResponse = constructInsertRecordResponse( + response, + options?.tokens, + finalInsertRecords?.records, + ); + resolve(insertResponse); + }) + ?.catch((error) => { + insertErrorResponse = { + errors: [ + { + error: { + code: error?.error?.code, + description: error?.error?.description, + }, + }, + ], + }; + resolve(insertErrorResponse); + }); +}); + export const checkForElementMatchRule = (validations: IValidationRule[]) => { if (!validations) return false; for (let i = 0; i < validations.length; i += 1) { diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index 7285716d..79361634 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -11,6 +11,7 @@ import { IRenderResponseType, IGetOptions, RenderFileResponse, + IRevealRecordComposable, } from '../utils/common'; import { printLog } from '../utils/logs-helper'; import { FILE_DOWNLOAD_URL_PARAM } from '../core/constants'; @@ -177,6 +178,27 @@ export const getFileURLFromVaultBySkyflowID = ( rootReject(err); } }); + +export const getFileURLFromVaultBySkyflowIDComposable = ( + skyflowIdRecord: IRevealRecord, + client: Client, + authToken: string, +): Promise => new Promise((rootResolve, rootReject) => { + try { + getFileURLForRender( + skyflowIdRecord, client, authToken as string, + ).then((resolvedResult: IRenderResponseType) => { + rootResolve(resolvedResult); + }).catch((err: any) => { + const errorData = formatForRenderFileFailure(err, skyflowIdRecord.skyflowID as string, + skyflowIdRecord.column as string); + printLog(errorData.error?.description || '', MessageType.ERROR, LogLevel.ERROR); + rootReject(errorData); + }); + } catch (err) { + rootReject(err); + } +}); export const fetchRecordsByTokenId = ( tokenIdRecords: IRevealRecord[], client: Client, @@ -230,6 +252,65 @@ export const fetchRecordsByTokenId = ( rootReject(err); }); }); + +export const fetchRecordsByTokenIdComposable = ( + tokenIdRecords: IRevealRecordComposable[], + client: Client, + authToken: string, +): Promise => new Promise((rootResolve, rootReject) => { + const vaultResponseSet: Promise[] = tokenIdRecords?.map( + (tokenRecord) => new Promise((resolve) => { + const apiResponse: any = []; + const redaction: RedactionType = tokenRecord?.redaction ?? RedactionType.PLAIN_TEXT; + + getTokenRecordsFromVault(tokenRecord?.token ?? '', redaction, client, authToken) + ?.then( + (response: IApiSuccessResponse) => { + const fieldsData = formatForPureJsSuccess(response); + apiResponse?.push({ + ...fieldsData, + frameId: tokenRecord?.iframeName ?? '', + }); + }, + (cause: any) => { + const errorData = formatForPureJsFailure(cause, tokenRecord?.token ?? ''); + printLog(errorData?.error?.description ?? '', MessageType.ERROR, LogLevel.ERROR); + apiResponse?.push({ + ...errorData, + frameId: tokenRecord?.iframeName ?? '', + }); + }, + ) + ?.finally(() => { + resolve(apiResponse); + }); + }), + ); + + Promise.allSettled(vaultResponseSet)?.then((resultSet) => { + const recordsResponse: Record[] = []; + const errorResponse: Record[] = []; + resultSet?.forEach((result) => { + if (result?.status === 'fulfilled') { + result?.value?.forEach((res: Record) => { + if (Object.prototype.hasOwnProperty.call(res, 'error')) { + errorResponse?.push(res); + } else { + recordsResponse?.push(res); + } + }); + } + }); + if (errorResponse?.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse?.length === 0) { + rootReject({ errors: errorResponse }); + } else { + rootReject({ records: recordsResponse, errors: errorResponse }); + } + }); +}); + export const formatRecordsForIframe = (response: IRevealResponseType) => { const result: Record = {}; if (response.records) { @@ -283,6 +364,34 @@ export const formatRecordsForClient = (response: IRevealResponseType) => { return { errors: response.errors }; }; +export const formatRecordsForClientComposable = (response) => { + let successRecords = []; + let errorRecords = []; + + if (response?.errors && response?.errors?.length > 0) { + errorRecords = response?.errors?.map((errors) => ({ + error: errors?.error ?? {}, + })); + } + + if (response?.records) { + successRecords = response?.records?.map((record) => ({ + token: record?.[0]?.token ?? '', + valueType: record?.[0]?.valueType ?? '', + })); + } + + if (successRecords?.length > 0 && errorRecords?.length > 0) { + return { success: successRecords, errors: errorRecords }; + } + + if (successRecords?.length > 0) { + return { success: successRecords }; + } + + return { errors: errorRecords }; +}; + export const fetchRecordsGET = async ( skyflowIdRecords: IGetRecord[], client: Client, diff --git a/src/core/constants.ts b/src/core/constants.ts index 57196408..7e2a2761 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -30,11 +30,13 @@ export const SDK_IFRAME_EVENT = 'SDK IFRAME EVENT'; 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 ELEMENT_TYPES = { COLLECT: 'COLLECT', REVEAL: 'REVEAL', COMPOSE: 'COMPOSABLE', + REVEAL_COMPOSE: 'REVEAL_COMPOSE', }; export const EVENT_TYPES = { @@ -104,8 +106,19 @@ export const ELEMENT_EVENTS_TO_CLIENT = { }; export const ELEMENT_EVENTS_TO_IFRAME = { + MULTIPLE_UPLOAD_FILES_RESPONSE: 'MULTIPLE_UPLOAD_FILES_RESPONSE', + RENDER_MOUNTED: 'RENDER_MOUNTED', + HEIGHT_CALLBACK: 'HEIGHT_CALLBACK', + HEIGHT_CALLBACK_COMPOSABLE: 'HEIGHT_CALLBACK_COMPOSABLE', + COMPOSABLE_REVEAL: 'COMPOSABLE_REVEAL', + MULTIPLE_UPLOAD_FILES: 'MULTIPLE_UPLOAD_FILES', COLLECT_CALL_REQUESTS: 'COLLECT_CALL_REQUESTS', + COMPOSABLE_CALL_REQUESTS: 'COMPOSABLE_CALL_REQUESTS', + COMPOSABLE_CALL_RESPONSE: 'COMPOSABLE_CALL_RESPONSE', + COMPOSABLE_FILE_CALL_RESPONSE: 'COMPOSABLE_FILE_CALL_RESPONSE', + COMPOSABLE_CONTAINER: 'COMPOSABLE_CONTAINER', REVEAL_CALL_REQUESTS: 'REVEAL_CALL_REQUESTS', + REVEAL_CALL_RESPONSE: 'REVEAL_CALL_RESPONSE', FRAME_READY: 'FRAME_READY', READY_FOR_CLIENT: 'READY_FOR_CLIENT', TOKENIZATION_REQUEST: 'TOKENIZATION_REQUEST', @@ -162,6 +175,7 @@ export enum ElementType { EXPIRATION_MONTH = 'EXPIRATION_MONTH', EXPIRATION_YEAR = 'EXPIRATION_YEAR', FILE_INPUT = 'FILE_INPUT', + MULTI_FILE_INPUT = 'MULTI_FILE_INPUT', } export enum CardType { @@ -324,6 +338,14 @@ export const ELEMENTS = { type: 'file', }, }, + [ElementType.MULTI_FILE_INPUT]: { + name: 'MULTI_FILE_INPUT', + sensitive: true, + attributes: { + type: 'file', + multiple: '', + }, + }, }; export const CARDNUMBER_INPUT_FORMAT = { @@ -637,6 +659,7 @@ export const DEFAULT_ERROR_TEXT_ELEMENT_TYPES = { [ElementType.EXPIRATION_MONTH]: 'Invalid expiration month', [ElementType.EXPIRATION_YEAR]: 'Invalid expiration year', [ElementType.FILE_INPUT]: logs.errorLogs.INVALID_COLLECT_VALUE, + [ElementType.MULTI_FILE_INPUT]: logs.errorLogs.INVALID_COLLECT_VALUE, }; export const DEFAULT_REQUIRED_TEXT_ELEMENT_TYPES = { @@ -649,6 +672,7 @@ export const DEFAULT_REQUIRED_TEXT_ELEMENT_TYPES = { [ElementType.EXPIRATION_MONTH]: 'expiration month is required', [ElementType.EXPIRATION_YEAR]: 'expiration year is required', [ElementType.FILE_INPUT]: logs.errorLogs.DEFAULT_REQUIRED_COLLECT_VALUE, + [ElementType.MULTI_FILE_INPUT]: logs.errorLogs.DEFAULT_REQUIRED_COLLECT_VALUE, }; export const INPUT_KEYBOARD_EVENTS = { diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 298aa5d2..d2aa0ac7 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -119,7 +119,6 @@ class CollectElement extends SkyflowElement { // if (this.#isSingleElementAPI && this.#elements.length > 1) { // throw new SkyflowError(SKYFLOW_ERROR_CODE.UNKNOWN_ERROR, [], true); // } - this.#doesReturnValue = EnvOptions[this.#context.env].doesReturnValue; this.elementType = this.#isSingleElementAPI ? this.#elements[0].elementType @@ -164,18 +163,18 @@ class CollectElement extends SkyflowElement { this.#readyToMount = container.isMounted; if (container.type === ContainerType.COMPOSABLE) { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + + this.#iframe.name) { + this.#iframe.setIframeHeight(event.data.data.height); + } + }); this.#elements.forEach((element) => { - this.#bus.on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED - + formatFrameNameToId(element.elementName), (data) => { - if (data.name === element.elementName) { - updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, `${element.elementType}_${METRIC_TYPES.EVENTS.MOUNTED}`); + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + + formatFrameNameToId(element.elementName)) { element.isMounted = true; this.#mounted = true; - this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT - + this.#iframe.name, - {}, (payload:any) => { - this.#iframe.setIframeHeight(payload.height); - }); } }); }); @@ -199,6 +198,7 @@ class CollectElement extends SkyflowElement { getID = () => this.#elementId; mount = (domElement: HTMLElement | string) => { + this.#mounted = true; if (!domElement) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['CollectElement'], true); } @@ -468,84 +468,168 @@ class CollectElement extends SkyflowElement { } }); this.#elements.forEach((element1) => { - this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT - + element1.elementName, (data: any) => { - if ( - this.#isSingleElementAPI + const isComposableContainer = this.#elements.length > 1; + if (isComposableContainer) { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + + element1.elementName) { + const data = event.data.data; + if (data.name === element1.elementName) { + if ( + this.#isSingleElementAPI && data.event === ELEMENT_EVENTS_TO_CLIENT.READY && data.name === formatFrameNameToId(this.#iframe.name) - ) { - this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); - } else { - const isComposable = this.#elements.length > 1; - this.#elements.forEach((element, index) => { - if (data.name === element.elementName) { - let emitEvent = ''; - switch (data.event) { - case ELEMENT_EVENTS_TO_CLIENT.FOCUS: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; - break; - case ELEMENT_EVENTS_TO_CLIENT.BLUR: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; - break; - case ELEMENT_EVENTS_TO_CLIENT.CHANGE: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; - break; - case ELEMENT_EVENTS_TO_CLIENT.READY: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; - break; - case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: - this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); - return; - // case ELEMENT_EVENTS_TO_CLIENT.CREATED: - // this.#mounted = true; - // return; - // todo: need to implement the below events - // case ELEMENT_EVENTS_TO_CLIENT.ESCAPE: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ESCAPE); - // break; - // case ELEMENT_EVENTS_TO_CLIENT.CLICK: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.CLICK); - // break; - // case ELEMENT_EVENTS_TO_CLIENT.ERROR: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ERROR); - // break; - - default: - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); - } - this.#states[index].isEmpty = data.value.isEmpty; - this.#states[index].isValid = data.value.isValid; - this.#states[index].isComplete = data.value.isComplete; - this.#states[index].isFocused = data.value.isFocused; - this.#states[index].isRequired = data.value.isRequired; - this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; - - if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; - else this.#states[index].value = undefined; - - emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; - - this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT - + this.#iframe.name, - {}, (payload:any) => { - this.#iframe.setIframeHeight(payload.height); - }); - - this.#updateState(); - const emitData = { - ...this.#states[index], - elementType: element.elementType, - }; - if (isComposable && this.#groupEmitter) { - this.#groupEmitter._emit(emitEvent, emitData); + ) { + this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); } else { - this.#eventEmitter._emit(emitEvent, emitData); + const isComposable = this.#elements.length > 1; + this.#elements.forEach((element, index) => { + if (data.name === element.elementName) { + let emitEvent = ''; + switch (data.event) { + case ELEMENT_EVENTS_TO_CLIENT.FOCUS: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; + break; + case ELEMENT_EVENTS_TO_CLIENT.BLUR: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; + break; + case ELEMENT_EVENTS_TO_CLIENT.CHANGE: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; + break; + case ELEMENT_EVENTS_TO_CLIENT.READY: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; + break; + case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); + return; + + default: + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); + } + this.#states[index].isEmpty = data.value.isEmpty; + this.#states[index].isValid = data.value.isValid; + this.#states[index].isComplete = data.value.isComplete; + this.#states[index].isFocused = data.value.isFocused; + this.#states[index].isRequired = data.value.isRequired; + this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; + if (element.elementType === ElementType.MULTI_FILE_INPUT) { + this.#states[index].metaData = data?.value?.metaData || []; + } + if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; + else this.#states[index].value = undefined; + + emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + + this.#updateState(); + const emitData = { + ...this.#states[index], + elementType: element.elementType, + }; + if (isComposable) { + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { + iframeName: this.#iframe.name, + }); + } + if (isComposable && this.#groupEmitter) { + this.#groupEmitter._emit(emitEvent, emitData); + } else { + this.#eventEmitter._emit(emitEvent, emitData); + } + } + }); } } - }); - } - }); + } + }); + } else { + this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + + element1.elementName, (data: any) => { + if ( + this.#isSingleElementAPI + && data.event === ELEMENT_EVENTS_TO_CLIENT.READY + && data.name === formatFrameNameToId(this.#iframe.name) + ) { + this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); + } else { + const isComposable = this.#elements.length > 1; + this.#elements.forEach((element, index) => { + if (data.name === element.elementName) { + let emitEvent = ''; + switch (data.event) { + case ELEMENT_EVENTS_TO_CLIENT.FOCUS: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; + break; + case ELEMENT_EVENTS_TO_CLIENT.BLUR: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; + break; + case ELEMENT_EVENTS_TO_CLIENT.CHANGE: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; + break; + case ELEMENT_EVENTS_TO_CLIENT.READY: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; + break; + case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); + return; + // case ELEMENT_EVENTS_TO_CLIENT.CREATED: + // this.#mounted = true; + // return; + // todo: need to implement the below events + // case ELEMENT_EVENTS_TO_CLIENT.ESCAPE: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ESCAPE); + // break; + // case ELEMENT_EVENTS_TO_CLIENT.CLICK: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.CLICK); + // break; + // case ELEMENT_EVENTS_TO_CLIENT.ERROR: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ERROR); + // break; + + default: + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); + } + this.#states[index].isEmpty = data.value.isEmpty; + this.#states[index].isValid = data.value.isValid; + this.#states[index].isComplete = data.value.isComplete; + this.#states[index].isFocused = data.value.isFocused; + this.#states[index].isRequired = data.value.isRequired; + this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; + + if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; + else this.#states[index].value = undefined; + + emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + + this.#updateState(); + const emitData = { + ...this.#states[index], + elementType: element.elementType, + }; + if (isComposable) { + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { + iframeName: this.#iframe.name, + }); + } + if (isComposable && this.#groupEmitter) { + this.#groupEmitter._emit(emitEvent, emitData); + } else { + this.#eventEmitter._emit(emitEvent, emitData); + } + } + }); + } + }); + } }); }; diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index 4a9f0480..7c584f5d 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -21,6 +21,7 @@ import { CollectElementOptions, ICollectOptions, CollectResponse, + UploadFilesResponse, } from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import logs from '../../../utils/logs'; @@ -38,6 +39,8 @@ import { import Container from '../common/container'; import CollectElement from './collect-element'; import ComposableElement from './compose-collect-element'; +import Client from '../../../client'; +import { getAccessToken } from '../../../utils/bus-events'; const CLASS_NAME = 'CollectContainer'; class ComposableContainer extends Container { @@ -71,7 +74,13 @@ class ComposableContainer extends Container { #clientDomain: string = ''; - #isSkyflowFrameReady: boolean = false; + #isComposableFrameReady: boolean = false; + + #shadowRoot: ShadowRoot | null = null; + + #iframeID: string = ''; + + #getSkyflowBearerToken: () => Promise | undefined; constructor(options, metaData, skyflowElements, context) { super(); @@ -89,8 +98,7 @@ class ComposableContainer extends Container { }, }, }; - this.#isSkyflowFrameReady = metaData.skyflowContainer.isControllerFrameReady; - + this.#getSkyflowBearerToken = metaData.getSkyflowBearerToken; this.#skyflowElements = skyflowElements; this.#context = context; this.#options = options; @@ -110,6 +118,18 @@ class ComposableContainer extends Container { this.#context.logLevel); this.#containerMounted = true; this.#updateListeners(); + bus + // .target(properties.IFRAME_SECURE_ORIGIN) + .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; + }); } create = (input: CollectElementInput, options: CollectElementOptions = { @@ -135,7 +155,11 @@ class ComposableContainer extends Container { elementName, }); const controllerIframeName = `${FRAME_ELEMENT}:group:${btoa(this.#tempElements)}:${this.#containerId}:${this.#context.logLevel}:${btoa(this.#clientDomain)}`; - return new ComposableElement(elementName, this.#eventEmitter, controllerIframeName); + this.#iframeID = controllerIframeName; + return new ComposableElement( + elementName, this.#eventEmitter, controllerIframeName, + { ...this.#metaData, type: input.type }, + ); }; #createMultipleElement = ( @@ -301,156 +325,228 @@ class ComposableContainer extends Container { this.#containerElement.mount(domElement); this.#isMounted = true; } + this.#elementsList.forEach((element) => { + this.#eventEmitter.on(`${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${element.elementName}`, (data, callback) => { + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${element.elementName}`, + { + elementName: element.name, + data: { + type: COLLECT_TYPES.FILE_UPLOAD, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + options: { + ...data.options, + }, + }, + ); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + callback(err); + }); + }); + }); + if (domElement instanceof HTMLElement + && (domElement as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElement.getRootNode() as ShadowRoot; + } else if (typeof domElement === 'string') { + const element = document.getElementById(domElement); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + if (this.#shadowRoot !== null) { + this.#eventEmitter.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, (data) => { + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + data.iframeName, {}); + }); + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframeID, {}); + } }; unmount = () => { this.#containerElement.unmount(); }; - collect = (options: ICollectOptions = { tokens: true }) :Promise => { - this.#isSkyflowFrameReady = this.#metaData.skyflowContainer.isControllerFrameReady; - if (this.#isSkyflowFrameReady) { - return new Promise((resolve, reject) => { - try { - validateInitConfig(this.#metaData.clientJSON.config); - if (!this.#elementsList || this.#elementsList.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); - } - if (!this.#isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); - } - const containerElements = getElements(this.#tempElements); - containerElements.forEach((element:any) => { - if (!element?.isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); - } - }); - const elementIds:{ frameId:string, elementId:string }[] = []; - const collectElements = Object.values(this.#elements); - collectElements.forEach((element) => { - element.isValidElement(); - }); - if (options && options.tokens && typeof options.tokens !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); - } - if (options?.additionalFields) { - validateAdditionalFieldsInCollect(options.additionalFields); - } - if (options?.upsert) { - validateUpsertOptions(options?.upsert); + collect = (options: ICollectOptions = { tokens: true }) : + Promise => new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + if (!this.#isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); + } + const containerElements = getElements(this.#tempElements); + containerElements.forEach((element:any) => { + if (!element?.isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); + } + }); + const elementIds:{ frameId:string, elementId:string }[] = []; + const collectElements = Object.values(this.#elements); + collectElements.forEach((element) => { + element.isValidElement(); + }); + if (options && options.tokens && typeof options.tokens !== 'boolean') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); + } + if (options?.additionalFields) { + validateAdditionalFieldsInCollect(options.additionalFields); + } + if (options?.upsert) { + validateUpsertOptions(options?.upsert); + } + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: this.#tempElements.elementName, + elementId: element.elementName, + }); + }); + const client = Client.fromJSON(this.#metaData.clientJSON.config) as any; + const clientId = client.toJSON()?.metaData?.uuid || ''; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + this.#containerId, { + data: { + type: COLLECT_TYPES.COLLECT, + ...options, + tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + window.addEventListener('message', (event) => { + if (event.data?.type + === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.#containerId) { + const data = event.data.data; + if (!data || data?.error) { + printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); + reject(data?.error); + } else if (data?.records) { + printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(data); + } else { + printLog(`${JSON.stringify(data)}`, MessageType.ERROR, this.#context.logLevel); + reject(data); } - this.#elementsList.forEach((element) => { - elementIds.push({ - frameId: this.#tempElements.elementName, - elementId: element.elementName, - }); - }); - bus - // .target(properties.IFRAME_SECURE_ORIGIN) - .emit( - ELEMENT_EVENTS_TO_IFRAME.COLLECT_CALL_REQUESTS + this.#metaData.uuid, - { - type: COLLECT_TYPES.COLLECT, - ...options, - tokens: options?.tokens !== undefined ? options.tokens : true, - elementIds, - containerId: this.#containerId, - }, - (data: any) => { - if (!data || data?.error) { - printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); - reject(data?.error); - } else { - printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), - MessageType.LOG, - this.#context.logLevel); - - resolve(data); - } - }, - ); - printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, - CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), - MessageType.LOG, this.#context.logLevel); - } catch (err:any) { - printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); - reject(err); } }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), + MessageType.LOG, this.#context.logLevel); + } catch (err:any) { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); } - return new Promise((resolve, reject) => { - try { - validateInitConfig(this.#metaData.clientJSON.config); - if (!this.#elementsList || this.#elementsList.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); - } - if (!this.#isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); - } + }); + + #emitEvent = (eventName: string, options?: Record, callback?: any) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } else { + const iframe = document.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }; - const containerElements = getElements(this.#tempElements); - containerElements.forEach((element:any) => { - if (!element?.isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); - } + uploadFiles = (options: ICollectOptions): + Promise => new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + if (!this.#isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); + } + const elementIds:{ frameId:string, elementId:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: this.#tempElements.elementName, + elementId: element.elementName, }); - const elementIds:{ frameId:string, elementId:string }[] = []; - const collectElements = Object.values(this.#elements); - collectElements.forEach((element) => { - element.isValidElement(); + }); + const client = Client.fromJSON(this.#metaData.clientJSON.config) as any; + const clientId = client.toJSON()?.metaData?.uuid || ''; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + this.#containerId, { + data: { + type: COLLECT_TYPES.FILE_UPLOAD, + ...options, + // tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, }); - - if (options && options.tokens && typeof options.tokens !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); - } - if (options?.additionalFields) { - validateAdditionalFieldsInCollect(options.additionalFields); - } - if (options?.upsert) { - validateUpsertOptions(options?.upsert); - } - this.#elementsList.forEach((element) => { - elementIds.push({ - frameId: this.#tempElements.elementName, - elementId: element.elementName, - }); + window.addEventListener('message', (event) => { + if (event.data?.type + === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.#containerId) { + const data = event.data.data; + if (!data || data?.error) { + printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); + reject(data?.error); + } else if (data?.fileUploadResponse) { + printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(data); + } else { + printLog(`${JSON.stringify(data)}`, MessageType.ERROR, this.#context.logLevel); + reject(data); + } + } }); - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + this.#containerId, () => { - bus - // .target(properties.IFRAME_SECURE_ORIGIN) - .emit( - ELEMENT_EVENTS_TO_IFRAME.COLLECT_CALL_REQUESTS + this.#metaData.uuid, - { - type: COLLECT_TYPES.COLLECT, - ...options, - tokens: options?.tokens !== undefined ? options.tokens : true, - elementIds, - containerId: this.#containerId, - }, - (data: any) => { - if (!data || data?.error) { - printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); - reject(data?.error); - } else { - printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), - MessageType.LOG, - this.#context.logLevel); - resolve(data); - } - }, - ); - }); - printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, - CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), - MessageType.LOG, this.#context.logLevel); - } catch (err:any) { + }).catch((err:any) => { printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); reject(err); - } - }); - }; + }); + } catch (err:any) { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); #updateListeners = () => { this.#eventEmitter.on(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_UPDATE_OPTIONS, (data) => { diff --git a/src/core/external/collect/compose-collect-element.ts b/src/core/external/collect/compose-collect-element.ts index 44a63a22..e0696be3 100644 --- a/src/core/external/collect/compose-collect-element.ts +++ b/src/core/external/collect/compose-collect-element.ts @@ -1,10 +1,15 @@ +import { Context } from 'vm'; import EventEmitter from '../../../event-emitter'; import { formatValidations } from '../../../libs/element-options'; import SkyflowError from '../../../libs/skyflow-error'; import { ContainerType } from '../../../skyflow'; -import { CollectElementUpdateOptions, EventName } from '../../../utils/common'; +import { + CollectElementUpdateOptions, EventName, MessageType, MetaData, +} from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import { ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ElementType } from '../../constants'; +import { printLog } from '../../../utils/logs-helper'; +import logs from '../../../utils/logs'; class ComposableElement { #elementName: string; @@ -19,7 +24,13 @@ class ComposableElement { #isUpdateCalled = false; - constructor(name, eventEmitter, iframeName) { + #metaData: any; + + #context: Context; + + #elementType: ElementType; + + constructor(name, eventEmitter, iframeName, metaData) { this.#elementName = name; this.#iframeName = iframeName; this.#eventEmitter = eventEmitter; @@ -27,6 +38,12 @@ class ComposableElement { this.#eventEmitter.on(`${EventName.READY}:${this.#elementName}`, () => { this.#isMounted = true; }); + this.#metaData = metaData; + this.#context = { + logLevel: this.#metaData.clientJSON?.config?.options?.logLevel, + env: this.#metaData.clientJSON?.config?.options?.env, + }; + this.#elementType = this.#metaData?.type as ElementType; } on(eventName: string, handler: Function) { @@ -98,6 +115,40 @@ class ComposableElement { }); } }; -} + uploadMultipleFiles = (metaData?: MetaData) => new Promise((resolve, reject) => { + try { + if (this.#elementType !== ElementType.MULTI_FILE_INPUT) { + throw new SkyflowError( + SKYFLOW_ERROR_CODE.MULTI_FILE_NOT_SUPPORTED, + [], + true, + ); + } + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter._emit(`${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${this.#elementName}`, { + options: metaData, + }, (response: any) => { + if (response.error) { + reject(response); + } + }); + window.addEventListener('message', (event) => { + if (event?.data?.type === `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${this.#elementName}`) { + if (event?.data?.data?.errorResponse || event?.data?.data?.error) { + printLog(`${event?.data?.data.errorResponse || event?.data?.data.error}`, MessageType.ERROR, this.#context.logLevel); + reject(event?.data?.data); + } else { + printLog(logs.infoLogs.MULTI_UPLOAD_FILES_SUCCESS, + MessageType.LOG, this.#context.logLevel); + resolve(event?.data?.data); + } + } + }); + } catch (error) { + printLog(`${error}`, MessageType.ERROR, this.#context.logLevel); + reject(error); + } + }); +} export default ComposableElement; diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index b59e8bc6..8f7f8a17 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -30,7 +30,7 @@ export default class IFrame { } mount = (domElement, elementId?: string, data?: any) => { - this.unmount(); + // this.unmount(); try { if (typeof domElement === 'string') { this.container = document.querySelector(domElement) || undefined; diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts new file mode 100644 index 00000000..2d829848 --- /dev/null +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -0,0 +1,474 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* +Copyright (c) 2023 Skyflow, Inc. +*/ +import bus from 'framebus'; +import sum from 'lodash/sum'; +import EventEmitter from '../../../event-emitter'; +import iframer, { setAttributes, getIframeSrc, setStyles } from '../../../iframe-libs/iframer'; +import deepClone from '../../../libs/deep-clone'; +import SkyflowError from '../../../libs/skyflow-error'; +import uuid from '../../../libs/uuid'; +import properties from '../../../properties'; +import { ContainerType } from '../../../skyflow'; +import { + Context, MessageType, +} from '../../../utils/common'; +import SKYFLOW_ERROR_CODE from '../../../utils/constants'; +import logs from '../../../utils/logs'; +import { printLog, parameterizedString } from '../../../utils/logs-helper'; +import { + validateInitConfig, + validateInputFormatOptions, + validateRevealElementRecords, +} from '../../../utils/validators'; +import { + COLLECT_FRAME_CONTROLLER, + CONTROLLER_STYLES, ELEMENT_EVENTS_TO_IFRAME, + FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, + COMPOSABLE_REVEAL, + REVEAL_TYPES, +} from '../../constants'; +import Container from '../common/container'; + +import ComposableRevealElement from './composable-reveal-element'; +import { RevealElementInput, RevealResponse } from '../../../index-node'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; +import ComposableRevealInternalElement from './composable-reveal-internal'; +import { formatRevealElementOptions } from '../../../utils/helpers'; + +const CLASS_NAME = 'ComposableRevealContainer'; +class ComposableRevealContainer extends Container { + #containerId: string; + + #elements: Record = {}; + + #metaData: any; + + #elementGroup: any = { rows: [] }; + + #elementsList:any = []; + + #context:Context; + + #skyflowElements:any; + + #eventEmitter: EventEmitter; + + #isMounted: boolean = false; + + #options: any; + + #containerElement:any; + + type:string = ContainerType.COMPOSE_REVEAL; + + #containerMounted: boolean = false; + + #tempElements: any = {}; + + #clientDomain: string = ''; + + #isComposableFrameReady: boolean = false; + + #shadowRoot: ShadowRoot | null = null; + + #iframeID: string = ''; + + #revealRecords: IRevealElementInput[] = []; + + #getSkyflowBearerToken: () => Promise | undefined; + + constructor(options, metaData, skyflowElements, context) { + super(); + this.#containerId = uuid(); + this.#metaData = { + ...metaData, + clientJSON: { + ...metaData.clientJSON, + config: { + ...metaData.clientJSON.config, + options: { + ...metaData.clientJSON.config?.options, + ...options, + }, + }, + }, + }; + this.#getSkyflowBearerToken = metaData.getSkyflowBearerToken; + this.#skyflowElements = skyflowElements; + this.#context = context; + this.#options = options; + this.#eventEmitter = new EventEmitter(); + + this.#clientDomain = this.#metaData.clientDomain || ''; + const iframe = iframer({ + name: `${COLLECT_FRAME_CONTROLLER}:${this.#containerId}:${this.#context.logLevel}:${btoa(this.#clientDomain)}`, + referrer: this.#clientDomain, + }); + setAttributes(iframe, { + src: getIframeSrc(), + }); + setStyles(iframe, { ...CONTROLLER_STYLES }); + printLog(parameterizedString(logs.infoLogs.CREATE_COLLECT_CONTAINER, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#containerMounted = true; + bus + // .target(properties.IFRAME_SECURE_ORIGIN) + .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) { + this.#isComposableFrameReady = true; + } + }); + } + + create = (input: RevealElementInput, options?: IRevealElementOptions) => { + const elementId = uuid(); + validateInputFormatOptions(options); + + const elementName = `${COMPOSABLE_REVEAL}:${btoa(elementId)}`; + this.#elementsList?.push({ + name: elementName, + ...input, + elementName, + elementId, + ...formatRevealElementOptions(options ?? {}), + }); + const controllerIframeName = `${FRAME_ELEMENT}:group:${btoa(this.#tempElements ?? {})}:${this.#containerId}:${this.#context?.logLevel}:${btoa(this.#clientDomain ?? '')}`; + return new ComposableRevealElement(elementName, + this.#eventEmitter, + controllerIframeName); + }; + + #createMultipleElement = ( + multipleElements: any, + isSingleElementAPI: boolean = false, + ) => { + try { + const elements: any[] = []; + this.#tempElements = deepClone(multipleElements); + this.#tempElements?.rows?.forEach((row) => { + row?.elements?.forEach((element) => { + const options = element ?? {}; + const { elementType } = options; + options.isMounted = false; + + options.label = element?.label; + options.skyflowID = element?.skyflowID; + + elements.push(options); + }); + }); + + this.#tempElements.elementName = isSingleElementAPI + ? elements[0].elementName + : `${FRAME_ELEMENT}:group:${btoa(this.#tempElements)}`; + if ( + isSingleElementAPI + && !this.#elements[elements[0].elementName] + && this.#hasElementName(elements[0].name) + ) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.UNIQUE_ELEMENT_NAME, [`${elements[0].name}`], true); + } + + let element = this.#elements[this.#tempElements.elementName]; + if (element) { + if (isSingleElementAPI) { + element.update(elements[0]); + } else { + element.update(this.#tempElements); + } + } else { + const elementId = uuid(); + try { + element = new ComposableRevealInternalElement( + elementId, + this.#tempElements, + this.#metaData, + { + containerId: this.#containerId, + isMounted: this.#containerMounted, + type: this.type, + eventEmitter: this.#eventEmitter, + }, + true, + this.#context, + ); + this.#elements[this.#tempElements.elementName] = element; + this.#skyflowElements[elementId] = element; + } catch (error: any) { + printLog(logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT, + MessageType.ERROR, + this.#context.logLevel); + throw error; + } + } + this.#iframeID = element.iframeName(); + return element; + } catch (error: any) { + printLog(logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT, + MessageType.ERROR, + this.#context.logLevel); + throw error; + } + }; + + #hasElementName = (name: string) => { + const tempElements = Object.keys(this.#elements); + for (let i = 0; i < tempElements.length; i += 1) { + if (atob(tempElements[i].split(':')[2]) === name) { + return true; + } + } + return false; + }; + + mount = (domElement: HTMLElement | string) => { + if (!domElement) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, + ['RevealElement'], true); + } + + const { layout } = this.#options; + if (sum(layout) !== this.#elementsList.length) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.MISMATCH_ELEMENT_COUNT_LAYOUT_SUM, [], true); + } + let count = 0; + layout.forEach((rowCount, index) => { + this.#elementGroup.rows = [ + ...this.#elementGroup.rows, + { elements: [] }, + ]; + for (let i = 0; i < rowCount; i++) { + this.#elementGroup.rows[index].elements.push( + this.#elementsList[count], + ); + count++; + } + }); + if (this.#options.styles) { + this.#elementGroup.styles = { + ...this.#options.styles, + }; + } + if (this.#options.errorTextStyles) { + this.#elementGroup.errorTextStyles = { + ...this.#options.errorTextStyles, + }; + } + if (this.#containerMounted) { + this.#containerElement = this.#createMultipleElement(this.#elementGroup, false); + this.#containerElement.mount(domElement); + this.#isMounted = true; + } + if (domElement instanceof HTMLElement + && (domElement as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElement.getRootNode() as ShadowRoot; + } else if (typeof domElement === 'string') { + const element = document.getElementById(domElement); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + if (this.#shadowRoot !== null) { + this.#eventEmitter.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, (data) => { + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + data.iframeName, {}); + }); + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframeID, {}); + } + }; + + unmount = () => { + this.#containerElement.unmount(); + }; + + #emitEvent = (eventName: string, options?: Record, callback?: any) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } else { + const iframe = document.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }; + + reveal(): Promise { + this.#revealRecords = []; + if (this.#isComposableFrameReady) { + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + printLog(parameterizedString(logs.infoLogs.VALIDATE_REVEAL_RECORDS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#elementsList.forEach((currentElement) => { + // if (currentElement.isClientSetError()) { + // throw new SkyflowError(SKYFLOW_ERROR_CODE.REVEAL_ELEMENT_ERROR_STATE); + // } + if (!currentElement.skyflowID) { + this.#revealRecords.push(currentElement); + } + }); + validateRevealElementRecords(this.#revealRecords); + const elementIds:{ frameId:string, token:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: element.name, + token: element.token, + }); + }); + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + this.#containerId, + { + data: { + type: REVEAL_TYPES.REVEAL, + containerId: this.#containerId, + elementIds, + }, + clientConfig: { + vaultURL: this.#metaData?.clientJSON?.config?.vaultURL, + vaultID: this.#metaData?.clientJSON?.config?.vaultID, + authToken, + }, + context: this.#context, + }, + ); + + window?.addEventListener('message', (event) => { + if (event?.data?.type + === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.#containerId) { + const revealData = event?.data?.data; + if (revealData?.errors) { + printLog( + parameterizedString(logs?.errorLogs?.FAILED_REVEAL), + MessageType.ERROR, + this.#context?.logLevel, + ); + reject(revealData); + } else { + printLog( + parameterizedString(logs?.infoLogs?.REVEAL_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context?.logLevel, + ); + resolve(revealData); + } + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); + } + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + printLog(parameterizedString(logs.infoLogs.VALIDATE_REVEAL_RECORDS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#elementsList.forEach((currentElement) => { + // if (currentElement.isClientSetError()) { + // throw new SkyflowError(SKYFLOW_ERROR_CODE.REVEAL_ELEMENT_ERROR_STATE); + // } + if (!currentElement.skyflowID) { + this.#revealRecords.push(currentElement); + } + }); + validateRevealElementRecords(this.#revealRecords); + const elementIds:{ frameId:string, token:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: element.name, + token: element.token, + }); + }); + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + window.addEventListener('message', (messagEevent) => { + if (messagEevent.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + + this.#containerId) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + this.#containerId, { + data: { + type: REVEAL_TYPES.REVEAL, + containerId: this.#containerId, + elementIds, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + context: this.#context, + }, + ); + window.addEventListener('message', (event) => { + if (event.data.type + === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.#containerId) { + const revealData = event.data.data; + if (revealData.errors) { + printLog(parameterizedString(logs.errorLogs.FAILED_REVEAL), + MessageType.ERROR, this.#context.logLevel); + reject(revealData); + } else { + printLog(parameterizedString(logs.infoLogs.REVEAL_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(revealData); + } + } + }); + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); + } +} +export default ComposableRevealContainer; diff --git a/src/core/external/reveal/composable-reveal-element.ts b/src/core/external/reveal/composable-reveal-element.ts new file mode 100644 index 00000000..0f3f328d --- /dev/null +++ b/src/core/external/reveal/composable-reveal-element.ts @@ -0,0 +1,66 @@ +import EventEmitter from '../../../event-emitter'; +import { ContainerType } from '../../../skyflow'; +import { EventName, RenderFileResponse } from '../../../utils/common'; +import { ELEMENT_EVENTS_TO_IFRAME, REVEAL_ELEMENT_OPTIONS_TYPES } from '../../constants'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; + +class ComposableRevealElement { + #elementName: string; + + #eventEmitter: EventEmitter; + + #iframeName: string; + + type: string = ContainerType?.COMPOSABLE ?? 'COMPOSABLE'; + + #isMounted: boolean = false; + + constructor(name, eventEmitter, iframeName) { + this.#elementName = name ?? ''; + this.#iframeName = iframeName ?? ''; + this.#eventEmitter = eventEmitter; + this.#eventEmitter?.on?.(`${EventName?.READY ?? 'READY'}:${this.#elementName}`, () => { + this.#isMounted = true; + }); + } + + iframeName(): string { + return this.#iframeName ?? ''; + } + + getID(): string { + return this.#elementName ?? ''; + } + + renderFile(): Promise { + return new Promise((resolve, reject) => { + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter?._emit?.( + `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST ?? ''}:${this.#elementName}`, + {}, + (response) => { + if (response?.errors) { + reject(response); + } else if (response?.error) { + reject({ errors: response?.error }); + } else { + resolve(response); + } + }, + ); + }); + } + + update = (options: IRevealElementInput | IRevealElementOptions) => { + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter?._emit?.( + `${ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS}:${this.#elementName}`, + { + options: options as IRevealElementInput | IRevealElementOptions, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + }, + ); + }; +} + +export default ComposableRevealElement; diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts new file mode 100644 index 00000000..a5584755 --- /dev/null +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -0,0 +1,610 @@ +/* +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 SKYFLOW_ERROR_CODE from '../../../utils/constants'; +import { + ELEMENT_EVENTS_TO_IFRAME, + ELEMENT_EVENTS_TO_CONTAINER, + REVEAL_ELEMENT_OPTIONS_TYPES, + METRIC_TYPES, + ELEMENT_EVENTS_TO_CLIENT, + EVENT_TYPES, + REVEAL_TYPES, + COMPOSABLE_REVEAL, +} from '../../constants'; +import IFrame from '../common/iframe'; +import SkyflowElement from '../common/skyflow-element'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; +import { + pushElementEventWithTimeout, + updateMetricObjectValue, +} from '../../../metrics'; +import logs from '../../../utils/logs'; +import { parameterizedString, printLog } from '../../../utils/logs-helper'; +import properties from '../../../properties'; +import { validateInitConfig, validateRenderElementRecord } from '../../../utils/validators'; +import EventEmitter from '../../../event-emitter'; +import { formatRevealElementOptions } from '../../../utils/helpers'; + +const CLASS_NAME = 'RevealElementInteranalElement'; + +export interface RevealComposableGroup{ + record: IRevealElementInput + options: IRevealElementOptions +} + +class ComposableRevealInternalElement extends SkyflowElement { + #iframe: IFrame; + + #metaData: any; + + #recordData: any; + + #containerId: any; + + #isMounted:boolean = false; + + #isClientSetError:boolean = false; + + #context: Context; + + #elementId: string; + + #readyToMount: boolean = false; + + #eventEmitter: EventEmitter; + + #isFrameReady: boolean; + + #domSelecter: string; + + #clientId: string; + + #isSkyflowFrameReady: boolean = false; + + #isSingleElementAPI: boolean; + + #shadowRoot: ShadowRoot | null = null; + + #getSkyflowBearerToken: () => Promise | undefined; + + #composableIframeName!: string; + + #isComposableFrameReady: boolean = false; + + constructor(elementId: string, + recordGroup: RevealComposableGroup[], + metaData: any, container: any, isSingleElementAPI: boolean = false, + context: Context) { + super(); + this.#elementId = elementId; + this.#metaData = metaData; + this.#clientId = this.#metaData?.uuid; + this.#isSingleElementAPI = isSingleElementAPI; + this.#recordData = recordGroup; + this.#containerId = container?.containerId; + this.#readyToMount = container?.isMounted ?? true; + this.#eventEmitter = container?.eventEmitter; + this.#context = context; + + this.#iframe = new IFrame( + `${COMPOSABLE_REVEAL}:${btoa(uuid())}`, + metaData, + this.#containerId, + this.#context?.logLevel, + ); + + this.#domSelecter = ''; + this.#isFrameReady = false; + this.#readyToMount = true; + this.#getSkyflowBearerToken = metaData?.getSkyflowBearerToken; + this.#isSkyflowFrameReady = metaData?.skyflowContainer?.isControllerFrameReady ?? false; + + bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe?.name, (data) => { + this.#iframe?.setIframeHeight(data?.height); + }); + + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#isComposableFrameReady = true; + } + }); + + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + this.#iframe?.name) { + this.#iframe?.setIframeHeight(event?.data?.data?.height); + } + }); + + // eslint-disable-next-line max-len + if (this.#recordData?.rows) { + this.setupRenderFileEventListener(this.getRecordData()?.rows); + } + } + + private setupRenderFileEventListener(rows: any[]): void { + if (!rows?.length) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REVEAL_COMPOSABLE_INPUT, ['COMPOSABLE_REVEAL'], true); + } + + try { + rows?.forEach((row, rowIndex) => { + row?.elements?.forEach((element: any, elementIndex: number) => { + if (!element?.name) return; + this.#eventEmitter?.on( + `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST}:${element?.name}`, + (data, callback) => { + this.renderFile(element)?.then((response) => { + callback?.(response); + })?.catch((error) => { + callback?.({ error }); + }); + }, + ); + this.#eventEmitter?.on( + `${ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS}:${element?.name}`, + (data) => { + if (data.updateType === REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS) { + // make this change in original elememt that is inside rows + const updatedElement = { + ...element, + ...data.options, + ...formatRevealElementOptions(data.options as IRevealElementOptions), + }; + + // Update element in this.#recordData.rows structure + if (this.#recordData?.rows?.[rowIndex]?.elements?.[elementIndex]) { + this.#recordData.rows[rowIndex].elements[elementIndex] = updatedElement; + } + + // Update local element reference + element = updatedElement; + + // Call update method + this.update(data.options, element); + } + }, + ); + }); + }); + } catch (error) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REVEAL_COMPOSABLE_INPUT, ['COMPOSABLE_REVEAL'], true); + } + } + + getID() { + return this.#elementId; + } + + mount(domElementSelector: HTMLElement | string) { + if (!domElementSelector) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['RevealElement'], true); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElementSelector); + if ( + this.#metaData?.clientJSON?.config?.options?.trackMetrics + && this.#metaData.clientJSON.config?.options?.trackingKey + ) { + pushElementEventWithTimeout(this.#elementId); + } + + this.#readyToMount = true; + if (this.#readyToMount) { + this.#iframe.mount(domElementSelector, undefined, { + record: JSON.stringify({ + ...this.#metaData, + record: this.#recordData, + context: this.#context, + containerId: this.#containerId, + }), + }); + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + if (this.#recordData.skyflowID) { + bus + // .target(location.origin) + .emit( + ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED + this.#containerId, + { + skyflowID: this.#recordData.skyflowID, + containerId: this.#containerId, + }, + ); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_END_TIME, Date.now()); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.MOUNTED); + } else { + bus + // .target(location.origin) + .emit( + ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED + this.#containerId, + { + id: this.#recordData.token, + containerId: this.#containerId, + }, + ); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_END_TIME, Date.now()); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.MOUNTED); + } + if (Object.prototype.hasOwnProperty.call(this.#recordData, 'skyflowID')) { + bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + } + }); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.READY); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_START_TIME, Date.now()); + } + if (domElementSelector instanceof HTMLElement + && (domElementSelector as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElementSelector.getRootNode() as ShadowRoot; + } else if (typeof domElementSelector === 'string') { + const element = document.getElementById(domElementSelector); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + } + + #emitEvent = (eventName: string, options?: Record) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot?.getElementById(this.#iframe?.name) as HTMLIFrameElement; + iframe?.contentWindow?.postMessage({ + name: eventName, + ...options, + }, properties?.IFRAME_SECURE_ORIGIN); + } else { + const iframe = document?.getElementById(this.#iframe?.name) as HTMLIFrameElement; + iframe?.contentWindow?.postMessage({ + name: eventName, + ...options, + }, properties?.IFRAME_SECURE_ORIGIN); + } + }; + + renderFile(recordData): Promise { + let altText = ''; + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + altText = recordData.altText; + } + this.setAltText('loading...'); + const loglevel = this.#context.logLevel; + if (this.#isComposableFrameReady) { + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + printLog(parameterizedString(logs.infoLogs.VALIDATE_RENDER_RECORDS, CLASS_NAME), + MessageType.LOG, + loglevel); + validateRenderElementRecord(recordData); + + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + recordData.name, + { + data: { + type: REVEAL_TYPES.RENDER_FILE, + containerId: this.#containerId, + iframeName: recordData.name, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }, + ); + window?.addEventListener('message', (event) => { + if (event?.data && event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + + recordData.name) { + if (event?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { + const revealData = event?.data?.data?.result; + if (revealData?.error || revealData?.errors) { + printLog(parameterizedString( + logs.errorLogs.FAILED_RENDER, + ), MessageType.ERROR, + this.#context.logLevel); + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + this.setAltText(altText); + } + reject(revealData?.error || revealData?.errors); + } else { + printLog(parameterizedString(logs.infoLogs.RENDER_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + printLog(parameterizedString(logs.infoLogs.FILE_RENDERED, + CLASS_NAME, recordData.skyflowID), + MessageType.LOG, this.#context.logLevel); + resolve(revealData); + } + } + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST), + MessageType.LOG, loglevel); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, + loglevel); + reject(err); + } + }); + } + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + printLog(parameterizedString(logs.infoLogs.VALIDATE_RENDER_RECORDS, CLASS_NAME), + MessageType.LOG, + loglevel); + validateRenderElementRecord(recordData); + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#isMounted = true; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + recordData.name, + { + data: { + type: REVEAL_TYPES.RENDER_FILE, + containerId: this.#containerId, + iframeName: recordData.name, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }, + ); + window.addEventListener('message', (event1) => { + if (event1?.data + && event1?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + + this.#iframe.name) { + if (event1?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { + const revealData = event1?.data?.data?.result; + if (revealData?.error) { + printLog(parameterizedString( + logs.errorLogs.FAILED_RENDER, + ), MessageType.ERROR, + this.#context.logLevel); + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + this.setAltText(altText); + } + reject(revealData); + } else { + // eslint-disable-next-line max-len + printLog(parameterizedString(logs.infoLogs.RENDER_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + printLog(parameterizedString(logs.infoLogs.FILE_RENDERED, + CLASS_NAME, recordData.skyflowID), + MessageType.LOG, this.#context.logLevel); + resolve(revealData); + } + } + } + }); + }).catch((err:any) => { + printLog(`${err?.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } + }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST), + MessageType.LOG, loglevel); + } catch (err: any) { + printLog(`Error: ${err?.message}`, MessageType.ERROR, + loglevel); + reject(err); + } + }); + } + + iframeName(): string { + return this.#iframe.name; + } + + isMounted():boolean { + return this.#isMounted; + } + + hasToken():boolean { + if (this.#recordData.token) return true; + return false; + } + + isClientSetError():boolean { + return this.#isClientSetError; + } + + getRecordData() { + return this.#recordData; + } + + setErrorOverride(clientErrorText: string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + }); + } + this.#isClientSetError = true; + } + + setError(clientErrorText:string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + }); + } + this.#isClientSetError = true; + } + + resetError() { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: false, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: false, + }); + }); + } + this.#isClientSetError = false; + } + + setAltText(altText:string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }); + }); + } + } + + clearAltText() { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: null, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: null, + }); + }); + } + } + + setToken(token:string) { + this.#recordData = { + ...this.#recordData, + token, + }; + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, + updatedValue: token, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, + updatedValue: token, + }); + }); + } + } + + unmount() { + if (this.#recordData.skyflowID) { + this.#isMounted = false; + this.#iframe.container?.remove(); + } + this.#isMounted = false; + this.#iframe.unmount(); + } + + update(options: IRevealElementInput | IRevealElementOptions, record) { + if (this.#isComposableFrameReady) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue: options, + }, + ); + } else { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue: options, + }, + ); + } + }); + } + } +} + +export default ComposableRevealInternalElement; diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts new file mode 100644 index 00000000..51210e5a --- /dev/null +++ b/src/core/internal/composable-frame-element-init.ts @@ -0,0 +1,369 @@ +import injectStylesheet from 'inject-stylesheet'; +import bus from 'framebus'; +import { getValueAndItsUnit } from '../../libs/element-options'; +import { getFlexGridStyles } from '../../libs/styles'; +import { ContainerType } from '../../skyflow'; +import { + Context, IRevealRecordComposable, +} from '../../utils/common'; +import { + getContainerType, +} from '../../utils/helpers'; +import { + ALLOWED_MULTIPLE_FIELDS_STYLES, + ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ERROR_TEXT_STYLES, REVEAL_TYPES, STYLE_TYPE, +} from '../constants'; +import IFrameFormElement from './iframe-form'; +import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-styles'; +import FrameElement from '.'; +import Client from '../../client'; +import RevealFrame from './reveal/reveal-frame'; +import { + fetchRecordsByTokenIdComposable, formatRecordsForClientComposable, +} from '../../core-utils/reveal'; + +export default class RevealComposableFrameElementInit { + iframeFormElement: IFrameFormElement | undefined; + + clientMetaData: any; + + #domForm: HTMLFormElement; + + frameElement!: FrameElement; + + private static frameEle?: any; + + containerId: string; + + group: any; + + frameList: FrameElement[] = []; + + iframeFormList: IFrameFormElement[] = []; + + #client!: Client; + + #context!: Context; + + revealFrameList: any[] = []; + + rootDiv: HTMLDivElement; + + constructor() { + this.containerId = ''; + this.#domForm = document?.createElement('form'); + this.#domForm.action = '#'; + this.#domForm.onsubmit = (event) => { + event?.preventDefault(); + }; + + this.rootDiv = document?.createElement('div'); + this.updateGroupData(); + this.createContainerDiv(this.group); + + window?.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + + this.containerId && event?.data?.data?.type === REVEAL_TYPES.REVEAL) { + this.#context = event?.data?.context; + const data = event?.data?.data ?? {}; + const elementIds = data?.elementIds ?? []; + const revealDataInput: IRevealRecordComposable[] = []; + this.#client = new Client(event?.data?.clientConfig ?? {}, {}); + + elementIds?.forEach((element) => { + this.revealFrameList?.forEach((revealFrame) => { + const data2 = revealFrame?.getData?.(); + if (data2?.name === element?.frameId) { + if (data2 && !data2?.skyflowID) { + const revealRecord: IRevealRecordComposable = { + token: data2?.token ?? '', + redaction: data2?.redaction, + iframeName: data2?.name ?? '', + }; + revealDataInput?.push(revealRecord); + } + } + }); + }); + + this.revealData(revealDataInput, this.containerId, event?.data?.clientConfig?.authToken) + ?.then((revealResponse: any) => { + if (revealResponse?.records?.length > 0) { + const formattedRecord = formatRecordsForClientComposable(revealResponse); + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.containerId, + data: formattedRecord, + }, + this.clientMetaData?.clientDomain, + ); + + revealResponse?.records?.forEach((record: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === record?.frameId) { + revealFrame?.responseUpdate?.(record); + } + }); + }); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + }) + ?.catch((error) => { + const formattedRecord = formatRecordsForClientComposable(error); + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.containerId, + data: formattedRecord, + }, + this.clientMetaData?.clientDomain, + ); + + error?.records?.forEach((record: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === record?.frameId) { + revealFrame?.responseUpdate?.(record); + } + }); + }); + + error?.errors?.forEach((error1: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === error1?.frameId) { + revealFrame?.responseUpdate?.(error1); + } + }); + }); + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + }); + } + }); + + bus?.emit(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId, {}, (data: any) => { + this.#context = data?.context; + if (data?.client?.config) { + data.client.config = { + ...data?.client?.config, + }; + } + this.#client = Client?.fromJSON?.(data?.client); + }); + } + + updateGroupData = () => { + const url = window?.location?.href ?? ''; + const configIndex = url?.indexOf('?') ?? -1; + const encodedString = configIndex !== -1 ? decodeURIComponent(url?.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; + this.clientMetaData = parsedRecord?.clientJSON?.metaData; + this.group = parsedRecord?.record; + this.containerId = parsedRecord?.containerId ?? ''; + this.#context = parsedRecord?.context; + }; + + static startFrameElement = () => { + RevealComposableFrameElementInit.frameEle = new RevealComposableFrameElementInit(); + }; + + revealData(revealRecords: IRevealRecordComposable[], containerId, authToken) { + return new Promise((resolve, reject) => { + fetchRecordsByTokenIdComposable(revealRecords, this.#client, authToken)?.then( + (resolvedResult) => { + resolve(resolvedResult); + }, + (rejectedResult) => { + reject(rejectedResult); + }, + ); + }); + } + + createContainerDiv = (newGroup) => { + this.group = newGroup; + const { + rows = [], + styles, + errorTextStyles, + } = this.group ?? {}; + + const isComposableContainer = getContainerType(window?.name) === ContainerType?.COMPOSABLE; + this.group.spacing = getValueAndItsUnit(this.group?.spacing)?.join('') ?? ''; + this.rootDiv = document?.createElement('div'); + this.rootDiv.className = 'container'; + + const containerStylesByClassName = getFlexGridStyles({ + 'align-items': this.group?.alignItems ?? 'stretch', + 'justify-content': this.group?.justifyContent ?? 'flex-start', + spacing: this.group?.spacing, + }); + + injectStylesheet?.injectWithAllowlist( + { + [`.${this.rootDiv?.className}`]: containerStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + + let count = 0; + rows?.forEach((row, rowIndex) => { + row.spacing = getValueAndItsUnit(row?.spacing)?.join('') ?? ''; + const rowDiv = document?.createElement('div'); + rowDiv.id = `row-${rowIndex}`; + + const intialRowStyles = { + 'align-items': row?.alignItems ?? 'stretch', + 'justify-content': row?.justifyContent ?? 'flex-start', + spacing: row?.spacing, + padding: this.group?.spacing, + }; + + const rowStylesByClassName = getFlexGridStyles(intialRowStyles); + let errorTextElement; + + if (isComposableContainer) { + rowDiv.className = `${rowDiv?.id} SkyflowElement-${rowDiv?.id}-base`; + const rowStyles = { + [STYLE_TYPE?.BASE]: { + ...(styles?.[STYLE_TYPE?.BASE] ?? {}), + }, + }; + + getCssClassesFromJss?.(rowStyles, `${rowDiv?.id}`); + + errorTextElement = document?.createElement('span'); + errorTextElement.id = `${rowDiv?.id}-error`; + errorTextElement.className = 'SkyflowElement-row-error-base'; + + const errorStyles = { + [STYLE_TYPE?.BASE]: { + ...ERROR_TEXT_STYLES, + ...(errorTextStyles?.[STYLE_TYPE?.BASE] ?? {}), + }, + }; + + getCssClassesFromJss?.(errorStyles, 'row-error'); + if (errorTextStyles?.[STYLE_TYPE?.GLOBAL]) { + generateCssWithoutClass?.(errorTextStyles?.[STYLE_TYPE?.GLOBAL]); + } + } else { + rowDiv.className = `row-${rowIndex}`; + injectStylesheet?.injectWithAllowlist( + { + [`.${rowDiv?.className}`]: rowStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + } + + row?.elements?.forEach((element) => { + const elementDiv = document?.createElement('div'); + elementDiv.className = `element-${count}`; + elementDiv.id = `${rowDiv?.id}:element-${count}`; + count += 1; + + const elementStylesByClassName = { + padding: row?.spacing, + }; + + injectStylesheet?.injectWithAllowlist( + { + [`.${elementDiv?.className}`]: elementStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + + const revealFrame = new RevealFrame(element, this.#context, + this.containerId, elementDiv); + this.revealFrameList?.push(revealFrame); + rowDiv?.append(elementDiv); + }); + + this.rootDiv?.append(rowDiv); + if (isComposableContainer) { + this.rootDiv?.append(errorTextElement); + } + }); + + if (this.#domForm) { + this.#domForm.innerHTML = ''; + document.body.innerHTML = ''; + this.#domForm?.append(this.rootDiv); + document?.body?.append(this.#domForm); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.containerId, + data: { + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + + bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window?.name, (data, callback) => { + callback?.({ + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }); + }); + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + + window?.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window?.name) { + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + } + if (event?.data?.type + === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name) { + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + } + }); + }; +} diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index d2c35e6b..13c89945 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -1,17 +1,35 @@ import injectStylesheet from 'inject-stylesheet'; import bus from 'framebus'; +import get from 'lodash/get'; import { getValueAndItsUnit, validateAndSetupGroupOptions } from '../../libs/element-options'; import { getFlexGridStyles } from '../../libs/styles'; import { ContainerType } from '../../skyflow'; -import { Context, Env, LogLevel } from '../../utils/common'; -import { getContainerType } from '../../utils/helpers'; +import { + Context, Env, LogLevel, + MessageType, +} from '../../utils/common'; +import { + fileValidation, generateUploadFileName, getContainerType, vaildateFileName, +} from '../../utils/helpers'; import { ALLOWED_MULTIPLE_FIELDS_STYLES, - ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ERROR_TEXT_STYLES, STYLE_TYPE, + COLLECT_TYPES, + ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ELEMENTS, ERROR_TEXT_STYLES, STYLE_TYPE, } from '../constants'; import IFrameFormElement from './iframe-form'; import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-styles'; import FrameElement from '.'; +import { + checkForElementMatchRule, checkForValueMatch, constructElementsInsertReq, + constructInsertRecordRequest, insertDataInCollect, + updateRecordsBySkyflowIDComposable, +} from '../../core-utils/collect'; +import SkyflowError from '../../libs/skyflow-error'; +import SKYFLOW_ERROR_CODE from '../../utils/constants'; +import Client from '../../client'; +import { printLog } from '../../utils/logs-helper'; + +const set = require('set-value'); export default class FrameElementInit { iframeFormElement: IFrameFormElement | undefined; @@ -30,9 +48,15 @@ export default class FrameElementInit { group: any; + frameList: FrameElement[] = []; + + iframeFormList: IFrameFormElement[] = []; + + #client!: Client; + constructor() { // this.createIframeElement(frameName, label, skyflowID, isRequired); - this.context = { logLevel: LogLevel.ERROR, env: Env.PROD }; // client level + this.context = { logLevel: LogLevel.INFO, env: Env.PROD }; // client level this.containerId = ''; this.#domForm = document.createElement('form'); this.#domForm.action = '#'; @@ -41,8 +65,571 @@ export default class FrameElementInit { }; this.updateGroupData(); this.createContainerDiv(this.group); + bus + // .target(this.clientMetaData.clientDomain) + .emit(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId, {}, (data: any) => { + data.client.config = { + ...data.client.config, + }; + this.#client = Client.fromJSON(data.client) as any; + }); + + window.addEventListener('message', this.handleCollectCall); } + private handleCollectCall = (event: MessageEvent) => { + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + if (inputElement.fieldType + === 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) + ?.then((response: any) => { + window?.parent.postMessage({ + type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${inputElement.iFrameName}`, + data: response, + }, this.clientMetaData?.clientDomain); + }).catch((error) => { + window?.parent.postMessage({ + type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${inputElement.iFrameName}`, + data: error, + }, this.clientMetaData?.clientDomain); + }); + } + } + } + }); + + // if (event.origin === this.clientMetaData.clientDomain) { + 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) + .then((response: any) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, + data: response, + }, this.clientMetaData.clientDomain); + }) + .catch((error) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, + data: error, + }, this.clientMetaData.clientDomain); + }); + } else if (event.data.data && event.data.data.type === COLLECT_TYPES.FILE_UPLOAD) { + this.parallelUploadFiles(event.data.data, event.data.clientConfig) + .then((response: any) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, + data: response, + }, this.clientMetaData.clientDomain); + }) + .catch((error) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, + data: error, + }, this.clientMetaData.clientDomain); + }); + } + } + if (event.data.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId) { + const data = event.data; + data.client.config = { + ...data.client.config, + }; + this.#client = Client.fromJSON(data.client) as any; + } + // } + }; + + private parallelUploadFiles = (options, config) => new Promise((rootResolve, rootReject) => { + const promises: Promise[] = []; + this.iframeFormList.forEach((inputElement) => { + let res: Promise; + if (inputElement) { + if ( + inputElement.fieldType + === ELEMENTS.FILE_INPUT.name + ) { + res = this.uploadFiles(inputElement, config); + promises.push(res); + } + } + }); + Promise.allSettled( + promises, + ).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response); + } + } + } else if (result.status === 'rejected') { + errorResponse.push(result.reason); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }); + + uploadFiles = (fileElement, clientConfig) => { + this.#client = new Client(clientConfig, {}); + if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); + const fileUploadObject: any = {}; + + const { + state, tableName, skyflowID, onFocusChange, preserveFileName, + } = fileElement; + + if (state.isRequired) { + onFocusChange(false); + } + try { + fileValidation(state.value, state.isRequired, fileElement); + } catch (err) { + return Promise.reject(err); + } + + const validatedFileState = fileValidation(state.value, state.isRequired, fileElement); + + if (!validatedFileState) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_TYPE, [], true)); + } + fileUploadObject[state.name] = state.value; + + const formData = new FormData(); + + const column = Object.keys(fileUploadObject)[0]; + + const value: Blob = Object.values(fileUploadObject)[0] as Blob; + + if (preserveFileName) { + const isValidFileName = vaildateFileName(state.value.name); + if (!isValidFileName) { + return Promise.reject( + new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true), + ); + } + formData.append(column, value); + } else { + const generatedFileName = generateUploadFileName(state.value.name); + formData.append(column, new File([value], generatedFileName, { type: state.value.type })); + } + + const client = this.#client; + const sendRequest = () => new Promise((rootResolve, rootReject) => { + client + .request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowID}/files`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }) + .then((response: any) => { + rootResolve(response); + }) + .catch((error) => { + rootReject(error); + }); + }); + + return new Promise((resolve, reject) => { + sendRequest() + .then((res) => resolve(res)) + .catch((err) => { + reject(err); + }); + }); + }; + + private tokenize = (options, clientConfig: any) => { + let errorMessage = ''; + const insertRequestObject: any = {}; + const updateRequestObject: any = {}; + + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + if (inputElement) { + if ( + inputElement.fieldType + !== ELEMENTS.FILE_INPUT.name && inputElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name + ) { + const { + // eslint-disable-next-line max-len + state, doesClientHasError, clientErrorText, errorText, onFocusChange, validations, + setValue, + } = inputElement; + if (state.isRequired || !state.isValid) { + onFocusChange(false); + } + if (validations + && checkForElementMatchRule(validations) + && checkForValueMatch(validations, inputElement)) { + setValue(state.value); + onFocusChange(false); + } + if (!state.isValid || !state.isComplete) { + if (doesClientHasError) { + errorMessage += `${state.name}:${clientErrorText}`; + } else { errorMessage += `${state.name}:${errorText} `; } + } + } + } + } + }); + + // return for error + if (errorMessage.length > 0) { + // eslint-disable-next-line max-len + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.COMPLETE_AND_VALID_INPUTS, [`${errorMessage}`], true)); + } + // eslint-disable-next-line consistent-return + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + const { + state, tableName, validations, skyflowID, + } = inputElement; + if (tableName) { + if ( + inputElement.fieldType + !== ELEMENTS.FILE_INPUT.name && inputElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name + ) { + if ( + inputElement.fieldType + === ELEMENTS.checkbox.name + ) { + if (insertRequestObject[state.name]) { + insertRequestObject[state.name] = `${insertRequestObject[state.name]},${state.value + }`; + } else { + insertRequestObject[state.name] = state.value; + } + } else if (insertRequestObject[tableName] && !(skyflowID === '') && skyflowID === undefined) { + if (get(insertRequestObject[tableName], state.name) + && !(validations && checkForElementMatchRule(validations))) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.DUPLICATE_ELEMENT, + [state.name, tableName], true)); + } + set( + insertRequestObject[tableName], + state.name, + inputElement.getUnformattedValue(), + ); + } else if (skyflowID || skyflowID === '') { + if (skyflowID === '' || skyflowID === null) { + return Promise.reject(new SkyflowError( + SKYFLOW_ERROR_CODE.EMPTY_SKYFLOW_ID_IN_ADDITIONAL_FIELDS, + )); + } + if (updateRequestObject[skyflowID]) { + set( + updateRequestObject[skyflowID], + state.name, + inputElement.getUnformattedValue(), + ); + } else { + updateRequestObject[skyflowID] = {}; + set( + updateRequestObject[skyflowID], + state.name, + inputElement.getUnformattedValue(), + ); + set( + updateRequestObject[skyflowID], + 'table', + tableName, + ); + } + } else { + insertRequestObject[tableName] = {}; + set( + insertRequestObject[tableName], + state.name, + inputElement.getUnformattedValue(), + ); + } + } + } + } + }); + let finalInsertRequest; + let finalInsertRecords; + let finalUpdateRecords; + try { + [finalInsertRecords, finalUpdateRecords] = constructElementsInsertReq( + insertRequestObject, updateRequestObject, options.options, + ); + finalInsertRequest = constructInsertRecordRequest(finalInsertRecords, options.options); + } catch (error:any) { + return Promise.reject({ + error: error?.message, + }); + } + this.#client = new Client(clientConfig, {}); + const client = this.#client; + const sendRequest = () => new Promise((rootResolve, rootReject) => { + const insertPromiseSet: Promise[] = []; + + // const clientId = client.toJSON()?.metaData?.uuid || ''; + // getAccessToken(clientId).then((authToken) => { + if (finalInsertRequest.length !== 0) { + insertPromiseSet.push( + insertDataInCollect(finalInsertRequest, + client, options, finalInsertRecords, clientConfig.authToken as string), + ); + } + if (finalUpdateRecords.updateRecords.length !== 0) { + insertPromiseSet.push( + updateRecordsBySkyflowIDComposable( + finalUpdateRecords, client, options, clientConfig.authToken as string, + ), + ); + } + if (insertPromiseSet.length !== 0) { + Promise.allSettled(insertPromiseSet).then((resultSet: any) => { + const recordsResponse: any[] = []; + const errorsResponse: any[] = []; + + resultSet.forEach((result: + { status: string; value: any; reason?: any; }) => { + if (result.status === 'fulfilled') { + if (result.value.records !== undefined && Array.isArray(result.value.records)) { + result.value.records.forEach((record) => { + recordsResponse.push(record); + }); + } + if (result.value.errors !== undefined && Array.isArray(result.value.errors)) { + result.value.errors.forEach((error) => { + errorsResponse.push(error); + }); + } + } else { + if (result.reason?.records !== undefined && Array.isArray(result.reason?.records)) { + result.reason.records.forEach((record) => { + recordsResponse.push(record); + }); + } + if (result.reason?.errors !== undefined && Array.isArray(result.reason?.errors)) { + result.reason.errors.forEach((error) => { + errorsResponse.push(error); + }); + } + } + }); + if (errorsResponse.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse.length === 0) rootReject({ errors: errorsResponse }); + else rootReject({ records: recordsResponse, errors: errorsResponse }); + }); + } + // }).catch((err) => { + // rootReject({ + // error: err, + // }); + // }); + }); + + return new Promise((resolve, reject) => { + sendRequest() + .then((res) => resolve(res)) + .catch((err) => reject(err)); + }); + }; + + // eslint-disable-next-line consistent-return + private multipleUploadFiles = + (fileElement: IFrameFormElement, + clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + this.#client = new Client(clientConfig, {}); + if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); + const { + state, tableName, onFocusChange, preserveFileName, + } = fileElement; + if (state.isRequired) { + onFocusChange(false); + } + + if (fileElement.state.value === undefined || fileElement.state.value === null || fileElement.state.value === '') { + rootReject({ error: 'No files selected' }); + return; + } + const files = state.value instanceof FileList + ? Array.from(state.value) + : [state.value]; + + this.validateFiles(files, state, fileElement); + const insertRequest = this.createInsertRequest(files.length, metaData); + this.insertDataCallInMultiFiles( + insertRequest, this.#client, tableName as string, clientConfig.authToken as string, + ).then((response: any) => { + const skyflowIDs = this.extractSkyflowIDs(response); + if (skyflowIDs.length === 0) { + rootReject({ error: 'No skyflow IDs returned from insert data' }); + return; + } + const promises: Promise[] = []; + + files.forEach((file, index) => { + const fileUploadObject: any = {}; + fileUploadObject[state.name] = file; + const formData = new FormData(); + const column = Object.keys(fileUploadObject)[0]; + const value: Blob = Object.values(fileUploadObject)[0] as Blob; + if (preserveFileName) { + formData.append(column, value); + } else { + const generatedFileName = generateUploadFileName(file.name); + formData.append(column, new File([value], generatedFileName, { type: file.type })); + } + const client = this.#client; + const promise1 = new Promise((resolve, reject) => { + client + .request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowIDs[index]}/files`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }) + .then((response1) => { + resolve(response1); + }) + .catch((error) => { + reject(error); + }); + }); + promises.push(promise1); + }); + Promise.allSettled( + promises, + ).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response1 = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response1); + } + } + } else if (result.status === 'rejected') { + errorResponse.push({ error: result.reason }); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }).catch((error) => { + printLog(`${error}`, MessageType.LOG, this.context?.logLevel); + rootReject({ + error: error?.error || error, + }); + }); + }); + + private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => { + files.forEach((file) => { + // Check file validation + const validatedFileState = fileValidation(file, state.isRequired, fileElement); + if (!validatedFileState) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_TYPE, [], true); + } + + // Check filename validation + const isValidFileName = vaildateFileName(file.name); + if (!isValidFileName) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true); + } + }); + return true; + }; + + private createInsertRequest = (numberOfRequests: number, options = {}) => { + // Create basic request structure + const request = { + records: [] as Array<{ fields: Record }>, + tokenization: false, + }; + + // Add empty field objects based on number of requests + for (let i = 0; i < numberOfRequests; i += 1) { + request.records.push({ + fields: options === undefined ? {} : options, + }); + } + + return request; + }; + + private extractSkyflowIDs = (response: { records: Array<{ skyflow_id: string }> }): string[] => { + if (!response?.records || !Array.isArray(response.records)) { + return []; + } + + return response.records + .map((record) => record.skyflow_id) + .filter((id) => id !== undefined && id !== null); + }; + + private insertDataCallInMultiFiles = ( + insertRequest, + client: Client, + tableName: string, + authToken: string, + ) => new Promise((rootResolve, rootReject) => { + client + .request({ + body: { + ...insertRequest, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + .then((response: any) => { + // Extract skyflow IDs from response + const skyflowIDs = this.extractSkyflowIDs(response); + rootResolve({ + ...response, + skyflowIDs, // Add extracted IDs to response + }); + }) + .catch((error) => { + rootReject(error); + }); + }); + updateGroupData = () => { const frameName = window.name; const url = window.location?.href; @@ -56,7 +643,6 @@ export default class FrameElementInit { }; this.group = parsedRecord.record; this.containerId = parsedRecord.containerId; - bus .target(this.clientMetaData.clientDomain) .on(ELEMENT_EVENTS_TO_IFRAME.SET_VALUE + frameName, (data) => { @@ -73,6 +659,7 @@ export default class FrameElementInit { ...this.clientMetaData, isRequired, }, this.context, skyflowID); + this.iframeFormList.push(this.iframeFormElement); return this.iframeFormElement; }; @@ -184,11 +771,21 @@ export default class FrameElementInit { iFrameFormElement, element, elementDiv, + this.clientMetaData.clientDomain, ); + this.frameList.push(this.frameElement); + if (isComposableContainer && errorTextElement) { iFrameFormElement.on(ELEMENT_EVENTS_TO_CLIENT.BLUR, (state) => { errorTextMap[element.elementName] = state.error; this.#updateCombinedErrorText(errorTextElement.id, errorTextMap); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); }); } @@ -208,6 +805,24 @@ export default class FrameElementInit { bus.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name, (data, callback) => { callback({ height: rootDiv.scrollHeight, name: window.name }); }); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); + window.addEventListener('message', (event) => { + if (event.data.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name) { + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); + } + }); }; #updateCombinedErrorText = (elementId, errorMessages) => { diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 2ae70aac..de56c27f 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -219,6 +219,35 @@ export default class IFrameFormElement extends EventEmitter { : ELEMENT_EVENTS_TO_CLIENT.BLUR, value: { ...this.getStatus() }, }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: focus + ? ELEMENT_EVENTS_TO_CLIENT.FOCUS + : ELEMENT_EVENTS_TO_CLIENT.BLUR, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: focus + ? ELEMENT_EVENTS_TO_CLIENT.FOCUS + : ELEMENT_EVENTS_TO_CLIENT.BLUR, + name: this.iFrameName, + value: { ...this.getStatus() }, + }, + }, this.metaData.clientDomain); + } + } if (!focus) { bus.emit(ELEMENT_EVENTS_TO_CLIENT.BLUR + this.iFrameName); this._emit(ELEMENT_EVENTS_TO_CLIENT.BLUR, { @@ -282,6 +311,40 @@ export default class IFrameFormElement extends EventEmitter { this.mask = newMask; } + getFileDetails = (value: FileList | File | null): Array<{ + name: string; + size: number; + type: string; + }> => { + // Return empty array if no value + if (!value) return []; + + try { + // Handle FileList + if (value instanceof FileList) { + return Array.from(value).map((file) => ({ + name: file.name, + size: file.size, + type: file.type, + })); + } + + // Handle single File + if (value instanceof File) { + return [{ + name: value.name, + size: value.size, + type: value.type, + }]; + } + + // Return empty array for invalid input + return []; + } catch (error) { + return []; + } + }; + setValidation(validations: IValidationRule[] | undefined) { if (ELEMENTS[this.fieldType].regex) { this.regex = ELEMENTS[this.fieldType].regex; @@ -469,6 +532,21 @@ export default class IFrameFormElement extends EventEmitter { resp = false; } if (this.preserveFileName) vaildateFileNames = vaildateFileName(value.name); + } else if (this.fieldType === ElementType.MULTI_FILE_INPUT) { + const files = this.state.value instanceof FileList + ? Array.from(this.state.value) + : [this.state.value]; + for (let i = 0; i < files.length; i += 1) { + try { + resp = fileValidation(files[i], this.state.isRequired, { + allowedFileType: this.allowedFileType, + blockEmptyFiles: this.blockEmptyFiles, + }); + } catch (err) { + resp = false; + } + if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name); + } } else { // eslint-disable-next-line no-lonely-if if (this.regex && value) { @@ -591,6 +669,31 @@ export default class IFrameFormElement extends EventEmitter { event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } } else if ( data.options !== undefined @@ -673,23 +776,99 @@ export default class IFrameFormElement extends EventEmitter { event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } else if ( this.state.value && (this.fieldType === ELEMENTS.EXPIRATION_DATE.name || this.fieldType === ELEMENTS.EXPIRATION_MONTH.name - || this.fieldType === ELEMENTS.FILE_INPUT.name) + || this.fieldType === ELEMENTS.FILE_INPUT.name + || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) ) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } else if (!this.state.isEmpty) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } this._emit(ELEMENT_EVENTS_TO_CLIENT.CHANGE, { diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 8648eb8c..aca89ed3 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -89,15 +89,20 @@ export default class FrameElement { private selectedData?: number = undefined; + private clientDomain: string; + constructor( iFrameFormElement: IFrameFormElement, options: any, htmlDivElement: HTMLDivElement, + clientDomain: string = '', ) { + this.clientDomain = clientDomain; this.iFrameFormElement = iFrameFormElement; this.options = options; this.htmlDivElement = htmlDivElement; this.hasError = false; + this.mount(); this.iFrameFormElement.fieldName = options.column; this.iFrameFormElement.tableName = options.table; @@ -138,7 +143,6 @@ export default class FrameElement { this.inputParent = document.createElement('div'); this.inputParent.style.position = 'relative'; - const inputElement = document.createElement(type); this.domInput = inputElement; this.domInput.iFrameFormElement = this.iFrameFormElement; @@ -217,6 +221,9 @@ export default class FrameElement { if (state.value && this.iFrameFormElement.fieldType === ELEMENTS.FILE_INPUT.name) { this.focusChange(false); } + if (state.value && this.iFrameFormElement.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + this.focusChange(false); + } this.focusChange(false); if (state.error && this.domError) { @@ -454,6 +461,12 @@ export default class FrameElement { .emit(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.iFrameFormElement.iFrameName, { name: this.iFrameFormElement.iFrameName, }); + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.iFrameFormElement.iFrameName, + data: { + name: this.iFrameFormElement.iFrameName, + }, + }, this.clientDomain); this.updateStyleClasses(this.iFrameFormElement.getStatus()); }; @@ -567,6 +580,10 @@ export default class FrameElement { const target = event.target as HTMLFormElement; this.iFrameFormElement.setValue(target.files[0], target.checkValidity()); this.focusChange(true); + } else if (this.iFrameFormElement.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + const target = event.target as HTMLFormElement; + this.iFrameFormElement.setValue(target.files, target.checkValidity()); + this.focusChange(true); } else { const target = event.target as HTMLInputElement; const { mask } = this.iFrameFormElement; @@ -817,11 +834,21 @@ export default class FrameElement { }; onSubmit = () => { - bus - .emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, { - name: this.iFrameFormElement.iFrameName, - event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, - }); + if (this.iFrameFormElement.containerType === ContainerType.COMPOSABLE) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, + data: { + name: this.iFrameFormElement.iFrameName, + event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, + }, + }, this.clientDomain); + } else { + bus + .emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, { + name: this.iFrameFormElement.iFrameName, + event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, + }); + } }; onArrowKeys = (keyBoardEvent: KeyboardEvent) => { diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 5611c51b..7fac88e1 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -15,17 +15,25 @@ import { RENDER_ELEMENT_IMAGE_STYLES, DEFAULT_FILE_RENDER_ERROR, ELEMENT_EVENTS_TO_CLIENT, + REVEAL_TYPES, } from '../../constants'; import getCssClassesFromJss, { generateCssWithoutClass } from '../../../libs/jss-styles'; import { printLog, parameterizedString, } from '../../../utils/logs-helper'; import logs from '../../../utils/logs'; -import { Context, MessageType } from '../../../utils/common'; +import { + Context, IRenderResponseType, IRevealRecord, MessageType, +} from '../../../utils/common'; import { constructMaskTranslation, - getAtobValue, getMaskedOutput, getValueFromName, handleCopyIconClick, styleToString, + formatRevealElementOptions, + getAtobValue, + getMaskedOutput, getValueFromName, handleCopyIconClick, styleToString, } from '../../../utils/helpers'; +import { formatForRenderClient, getFileURLFromVaultBySkyflowIDComposable } from '../../../core-utils/reveal'; +import Client from '../../../client'; +import properties from '../../../properties'; const { getType } = require('mime'); @@ -65,6 +73,8 @@ class RevealFrame { #skyflowContainerId: string = ''; + #client!: Client; + static init() { const url = window.location?.href; const configIndex = url.indexOf('?'); @@ -75,9 +85,9 @@ class RevealFrame { parsedRecord.context, skyflowContainerId); } - constructor(record, context, id) { + constructor(record, context, id, rootDiv?) { this.#skyflowContainerId = id; - this.#name = window.name; + this.#name = rootDiv ? record?.name : window.name; this.#containerId = getValueFromName(this.#name, 2); const encodedClientDomain = getValueFromName(this.#name, 4); const clientDomain = getAtobValue(encodedClientDomain); @@ -91,14 +101,14 @@ class RevealFrame { getCssClassesFromJss(REVEAL_ELEMENT_DIV_STYLE, 'div'); this.#labelElement = document.createElement('span'); - this.#labelElement.className = `SkyflowElement-label-${STYLE_TYPE.BASE}`; + this.#labelElement.className = `SkyflowElement-${this.#name}-label-${STYLE_TYPE.BASE}`; this.#dataElememt = document.createElement('span'); - this.#dataElememt.className = `SkyflowElement-content-${STYLE_TYPE.BASE}`; + this.#dataElememt.className = `SkyflowElement-${this.#name}-content-${STYLE_TYPE.BASE}`; this.#dataElememt.id = this.#name; this.#errorElement = document.createElement('span'); - this.#errorElement.className = `SkyflowElement-error-${STYLE_TYPE.BASE}`; + this.#errorElement.className = `SkyflowElement-${this.#name}-error-${STYLE_TYPE.BASE}`; if (this.#record.enableCopy) { this.domCopy = document.createElement('img'); @@ -125,13 +135,14 @@ class RevealFrame { ...REVEAL_ELEMENT_LABEL_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.labelStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#labelStyles, 'label'); + // getCssClassesFromJss(this.#labelStyles, 'label'); + getCssClassesFromJss(this.#labelStyles, `${this.#name}-label`); if (this.#record.labelStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.labelStyles[STYLE_TYPE.GLOBAL]); } } else { - getCssClassesFromJss(REVEAL_ELEMENT_LABEL_DEFAULT_STYLES, 'label'); + getCssClassesFromJss(REVEAL_ELEMENT_LABEL_DEFAULT_STYLES, `${this.#name}-label`); } } this.updateDataView(); @@ -140,7 +151,7 @@ class RevealFrame { this.#inputStyles[STYLE_TYPE.BASE] = { ...this.#record.inputStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#inputStyles, 'content'); + getCssClassesFromJss(this.#inputStyles, `${this.#name}-content`); if (this.#record.inputStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.inputStyles[STYLE_TYPE.GLOBAL]); } @@ -155,26 +166,68 @@ class RevealFrame { ...REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.errorTextStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#errorTextStyles, 'error'); + getCssClassesFromJss(this.#errorTextStyles, `${this.#name}-error`); if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]); } } else { getCssClassesFromJss( REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES, - 'error', + `${this.#name}-error`, ); } this.#elementContainer.appendChild(this.#dataElememt); - document.body.append(this.#elementContainer); + if (rootDiv) rootDiv.append(this.#elementContainer); + else document.body.append(this.#elementContainer); + // document.body.append(this.#elementContainer); bus.emit(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#name, { name: this.#name }); - + if (rootDiv) { + this.getConfig(); + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#name, + data: { + name: this.#name, + }, + }, this.#clientDomain); + + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { height: this.#elementContainer.scrollHeight, name: this.#name }, + }, this.#clientDomain); + } bus.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, (_, callback) => { callback({ height: this.#elementContainer.scrollHeight, name: this.#name }); }); + const sub2 = (responseUrl) => { + if (responseUrl.iframeName === this.#name) { + if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { + this.setRevealError(DEFAULT_FILE_RENDER_ERROR); + if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { + this.#dataElememt.innerText = this.#record.altText; + } + bus + .emit( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + { + height: this.#elementContainer.scrollHeight, + }, () => { + }, + ); + } else { + const ext = this.getExtension(responseUrl.url); + this.addFileRender(responseUrl.url, ext); + } + } + }; + bus + .target(window.location.origin) + .on( + ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY + this.#name, + sub2, + ); const sub = (data) => { if (Object.prototype.hasOwnProperty.call(data, this.#record.token)) { @@ -189,7 +242,7 @@ class RevealFrame { this.#dataElememt.innerText = formattedOutput; } printLog(parameterizedString(logs.infoLogs.ELEMENT_REVEALED, - CLASS_NAME, this.#record.token), MessageType.LOG, this.#context.logLevel); + CLASS_NAME, this.#record.token), MessageType.LOG, this.#context?.logLevel); // bus // .target(window.location.origin) @@ -220,35 +273,172 @@ class RevealFrame { if (data.isTriggerError) { this.setRevealError(data.clientErrorText as string); } else { this.setRevealError(''); } } }); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#containerId, + data: { + name: window.name, + }, + }, this.#clientDomain, + ); this.updateRevealElementOptions(); - - const sub2 = (responseUrl) => { - if (responseUrl.iframeName === this.#name) { - if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { - this.setRevealError(DEFAULT_FILE_RENDER_ERROR); - if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { - this.#dataElememt.innerText = this.#record.altText; - } - bus - .emit( - ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, - { - height: this.#elementContainer.scrollHeight, - }, () => { - }, + window.addEventListener('message', (event) => { + 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) => { + const result = formatForRenderClient( + resolvedResult as IRenderResponseType, + this.#record?.column, ); - } else { - const ext = this.getExtension(responseUrl.url); - this.addFileRender(responseUrl.url, ext); + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, + data: { + type: REVEAL_TYPES.RENDER_FILE, + result, + }, + }, this.#clientDomain); + + window?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name, + }, properties?.IFRAME_SECURE_ORIGIN); + })?.catch((error) => { + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, + data: { + type: REVEAL_TYPES.RENDER_FILE, + result: { + errors: error, + }, + }, + }, this.#clientDomain); + + window?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name, + }, properties?.IFRAME_SECURE_ORIGIN); + }); } } - }; - bus - .target(window.location.origin) - .on( - ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY + this.#name, - sub2, + + if (event?.data?.type === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name) { + if (event?.data?.data?.height) { + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { + height: this.#elementContainer?.scrollHeight ?? 0, + name: this.#name, + }, + }, this.#clientDomain); + } + } + }); + } + + responseUpdate = (data) => { + if (data?.frameId === this.#record?.name && data?.error) { + if (!Object.prototype.hasOwnProperty.call(this.#record, 'skyflowID')) { + this.setRevealError(REVEAL_ELEMENT_ERROR_TEXT); + } + } else if (data?.frameId === this.#record?.name + && data?.[0]?.token + && this.#record?.token === data?.[0]?.token) { + const responseValue = data?.[0]?.value as string ?? ''; + this.#revealedValue = responseValue; + this.isRevealCalled = true; + this.#dataElememt.innerText = responseValue; + + if (this.#record?.mask) { + const { formattedOutput } = getMaskedOutput( + this.#dataElememt?.innerText ?? '', + this.#record?.mask?.[0], + constructMaskTranslation(this.#record?.mask), + ); + this.#dataElememt.innerText = formattedOutput ?? ''; + } + + printLog( + parameterizedString( + logs?.infoLogs?.ELEMENT_REVEALED, + CLASS_NAME, + this.#record?.token, + ), + MessageType.LOG, + this.#context?.logLevel, ); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { + height: this.#elementContainer?.scrollHeight ?? 0, + name: this.#name, + }, + }, + this.#clientDomain, + ); + }; + + getConfig = () => { + const url = window.location?.href; + const configIndex = url.indexOf('?'); + const encodedString = configIndex !== -1 ? decodeURIComponent(url.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; + this.#clientDomain = parsedRecord.clientDomain || ''; + this.#containerId = parsedRecord.containerId; + }; + + getData = () => this.#record; + + private sub2 = (responseUrl) => { + if (responseUrl.iframeName === this.#name) { + if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { + this.setRevealError(DEFAULT_FILE_RENDER_ERROR); + if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { + this.#dataElememt.innerText = this.#record.altText; + } + bus + .emit( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + { + height: this.#elementContainer.scrollHeight, + }, () => { + }, + ); + } else { + const ext = this.getExtension(responseUrl.url); + this.addFileRender(responseUrl.url, ext); + } + } + }; + + private renderFile(data: IRevealRecord, clientConfig) { + this.#client = new Client(clientConfig, {}); + return new Promise((resolve, reject) => { + try { + getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) + .then((resolvedResult) => { + let url = ''; + if (resolvedResult.fields && data.column) { + url = resolvedResult.fields[data.column]; + } + this.sub2({ + url, + iframeName: this.#name, + }); + resolve(resolvedResult); + }, + (rejectedResult) => { + this.sub2({ + error: DEFAULT_FILE_RENDER_ERROR, + iframeName: this.#name, + }); + reject(rejectedResult); + }); + } catch (err) { + reject(err); + } + }); } // eslint-disable-next-line class-methods-use-this @@ -312,6 +502,31 @@ class RevealFrame { } private updateRevealElementOptions() { + window.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + + this.#name) { + const data = event?.data; + if (data.updateType === REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS) { + const updatedValue = data.updatedValue as object; + this.#record = { + ...this.#record, + ...updatedValue, + ...formatRevealElementOptions(updatedValue), + }; + this.updateElementProps(); + if (this.isRevealCalled) { + if (this.#record?.mask) { + const { formattedOutput } = getMaskedOutput( + this.#revealedValue ?? '', + this.#record?.mask?.[0], + constructMaskTranslation(this.#record?.mask), + ); + this.#dataElememt.innerText = formattedOutput ?? ''; + } + } + } + } + }); bus .target(this.#clientDomain) .on(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#name, (data) => { @@ -355,7 +570,7 @@ class RevealFrame { ...this.#inputStyles, ...this.#record.inputStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#inputStyles, 'content'); + getCssClassesFromJss(this.#inputStyles, `${this.#name}-content`); if (this.#record.inputStyles[STYLE_TYPE.GLOBAL]) { const newInputGlobalStyles = { ...this.#inputStyles[STYLE_TYPE.GLOBAL], @@ -370,7 +585,7 @@ class RevealFrame { ...REVEAL_ELEMENT_LABEL_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.labelStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#labelStyles, 'label'); + getCssClassesFromJss(this.#labelStyles, `${this.#name}-label`); if (this.#record.labelStyles[STYLE_TYPE.GLOBAL]) { const newLabelGlobalStyles = { @@ -386,7 +601,7 @@ class RevealFrame { ...this.#errorTextStyles[STYLE_TYPE.BASE], ...this.#record.errorTextStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#errorTextStyles, 'error'); + getCssClassesFromJss(this.#errorTextStyles, `${this.#name}-error`); if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { const newErrorTextGlobalStyles = { ...this.#errorTextStyles[STYLE_TYPE.GLOBAL], diff --git a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts index c06964de..146f3888 100644 --- a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts +++ b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts @@ -92,11 +92,11 @@ class SkyflowFrameController { try { window.CoralogixRum.info(SDK_IFRAME_EVENT, data.event); printLog(parameterizedString(logs.infoLogs.METRIC_CAPTURE_EVENT), - MessageType.LOG, this.#context.logLevel); + MessageType.LOG, this.#context?.logLevel); } catch (err: any) { printLog(parameterizedString(logs.infoLogs.UNKNOWN_METRIC_CAPTURE_EVENT, err.toString()), - MessageType.LOG, this.#context.logLevel); + MessageType.LOG, this.#context?.logLevel); } } }, @@ -485,7 +485,8 @@ class SkyflowFrameController { if (inputElement) { if ( inputElement.iFrameFormElement.fieldType - !== ELEMENTS.FILE_INPUT.name + !== ELEMENTS.FILE_INPUT.name && inputElement.iFrameFormElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name ) { const { state, doesClientHasError, clientErrorText, errorText, onFocusChange, validations, @@ -525,6 +526,7 @@ class SkyflowFrameController { if ( inputElement.iFrameFormElement.fieldType !== ELEMENTS.FILE_INPUT.name + && inputElement.iFrameFormElement.fieldType !== ELEMENTS.MULTI_FILE_INPUT.name ) { if ( inputElement.iFrameFormElement.fieldType diff --git a/src/index-internal.ts b/src/index-internal.ts index 31274800..1cf7049f 100644 --- a/src/index-internal.ts +++ b/src/index-internal.ts @@ -4,6 +4,7 @@ Copyright (c) 2022 Skyflow, Inc. import 'core-js/stable'; import RevealFrame from './core/internal/reveal/reveal-frame'; import { + COMPOSABLE_REVEAL, FRAME_ELEMENT, FRAME_REVEAL, SKYFLOW_FRAME_CONTROLLER, @@ -18,6 +19,7 @@ import { } from './utils/logs-helper'; import { getAtobValue, getValueFromName } from './utils/helpers'; import FrameElementInit from './core/internal/frame-element-init'; +import RevealComposableFrameElementInit from './core/internal/composable-frame-element-init'; (function init(root: any) { try { @@ -26,6 +28,9 @@ import FrameElementInit from './core/internal/frame-element-init'; const frameId = getValueFromName(frameName, 1); if (frameType === SKYFLOW_FRAME_CONTROLLER) { SkyflowFrameController.init(frameId); + } else if (frameType === COMPOSABLE_REVEAL) { + root.Skyflow = RevealComposableFrameElementInit; + RevealComposableFrameElementInit.startFrameElement(); } else if (frameType === FRAME_ELEMENT) { const logLevel = getValueFromName(frameName, 4) || LogLevel.ERROR; printLog( diff --git a/src/libs/element-options.ts b/src/libs/element-options.ts index 3ee11b57..85a5364d 100644 --- a/src/libs/element-options.ts +++ b/src/libs/element-options.ts @@ -373,7 +373,7 @@ export const formatOptions = (elementType, options, logLevel) => { break; } - case ELEMENTS.FILE_INPUT.name: { + case ELEMENTS.FILE_INPUT.name || ELEMENTS.MULTI_FILE_INPUT.name: { if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName')) { formattedOptions = { ...formattedOptions, preserveFileName: true }; } @@ -395,7 +395,7 @@ export const formatOptions = (elementType, options, logLevel) => { if (Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName') && !validateBooleanOptions(formattedOptions.preserveFileName)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['preserveFileName'], true); } - if (elementType === ELEMENTS.FILE_INPUT.name) { + if (elementType === ELEMENTS.FILE_INPUT.name || elementType === ELEMENTS.MULTI_FILE_INPUT.name) { if (options.allowedFileType) { if (!Array.isArray(options.allowedFileType)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ALLOWED_OPTIONS, [], true); diff --git a/src/skyflow.ts b/src/skyflow.ts index 50333549..ef8dd5f4 100644 --- a/src/skyflow.ts +++ b/src/skyflow.ts @@ -47,11 +47,13 @@ import { formatVaultURL, checkAndSetForCustomUrl } from './utils/helpers'; import ComposableContainer from './core/external/collect/compose-collect-container'; import { validateComposableContainerOptions } from './utils/validators'; import ThreeDS from './core/external/threeds/threeds'; +import ComposableRevealContainer from './core/external/reveal/composable-reveal-container'; export enum ContainerType { COLLECT = 'COLLECT', REVEAL = 'REVEAL', COMPOSABLE = 'COMPOSABLE', + COMPOSE_REVEAL = 'COMPOSABLE_REVEAL', } export interface ISkyflow { vaultID?: string; @@ -164,9 +166,50 @@ class Skyflow { return skyflow; } + #getSkyflowBearerToken = () => new Promise((resolve, reject) => { + if ( + this.#client.config.getBearerToken + && (!this.#bearerToken || !isTokenValid(this.#bearerToken)) + ) { + this.#client.config + .getBearerToken() + .then((bearerToken) => { + if (isTokenValid(bearerToken)) { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + this.#bearerToken = bearerToken; + resolve(this.#bearerToken); + } else { + printLog(parameterizedString( + logs.errorLogs.INVALID_BEARER_TOKEN, + ), MessageType.ERROR, this.#logLevel); + reject({ + error: parameterizedString( + logs.errorLogs.INVALID_BEARER_TOKEN, + ), + }); + } + }) + .catch((err) => { + printLog(parameterizedString(logs.errorLogs.BEARER_TOKEN_REJECTED), MessageType.ERROR, + this.#logLevel); + reject({ error: err }); + }); + } else { + printLog(parameterizedString(logs.infoLogs.REUSE_BEARER_TOKEN, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + resolve(this.#bearerToken); + } + }); + container(type: ContainerType.COLLECT, options?: ContainerOptions): CollectContainer; container(type: ContainerType.COMPOSABLE, options?: ContainerOptions): ComposableContainer; container(type: ContainerType.REVEAL, options?: ContainerOptions): RevealContainer; + container(type: ContainerType.COMPOSE_REVEAL, + options?: ContainerOptions) + : ComposableRevealContainer; container(type: ContainerType, options?: ContainerOptions) { switch (type) { case ContainerType.COLLECT: { @@ -189,6 +232,7 @@ class Skyflow { clientJSON: this.#client.toJSON(), containerType: type, skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, }, this.#skyflowElements, { logLevel: this.#logLevel }, options); @@ -204,6 +248,7 @@ class Skyflow { clientJSON: this.#client.toJSON(), containerType: type, skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, }, this.#skyflowElements, { logLevel: this.#logLevel, env: this.#env }); @@ -213,6 +258,23 @@ class Skyflow { return collectContainer; } + case ContainerType.COMPOSE_REVEAL: { + validateComposableContainerOptions(options); + const revealComposableContainer = new ComposableRevealContainer(options, { + ...this.#metadata, + clientJSON: this.#client.toJSON(), + containerType: type, + skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, + }, + this.#skyflowElements, + { logLevel: this.#logLevel, env: this.#env }); + printLog(parameterizedString(logs.infoLogs.REVEAL_CONTAINER_CREATED, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + return revealComposableContainer; + } + default: if (!type) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CONTAINER_TYPE, [], true); diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 5932f888..4077d8f8 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -75,6 +75,15 @@ export interface IRevealRecord { table?: string; } +export interface IRevealRecordComposable { + token?: string; + redaction?: RedactionType; + column?: string; + skyflowID?: string; + table?: string; + iframeName?: string; +} + export interface IInsertResponse { records: IInsertResponseReocrds[]; } @@ -308,6 +317,9 @@ export interface ICollectOptions { additionalFields?: IInsertRecordInput, upsert?: Array, } +export interface MetaData { + [key: string]: any, +} export interface UploadFilesResponse { fileUploadResponse: [{ skyflow_id: string }], diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e80fcf2c..539ae099 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,6 +4,12 @@ Copyright (c) 2022 Skyflow, Inc. import logs from './logs'; const SKYFLOW_ERROR_CODE = { + MULTI_FILE_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.MULTI_FILE_NOT_SUPPORTED, + }, + INVALID_REVEAL_COMPOSABLE_INPUT: + { code: 400, description: logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT }, INVALID_FILE_NAME: { code: 400, description: logs.errorLogs.INVALID_FILE_NAME }, INVALID_FIELD: { code: 400, description: logs.errorLogs.INVALID_FIELD }, VAULTID_IS_REQUIRED: { code: 400, description: logs.errorLogs.VAULTID_IS_REQUIRED }, diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 367ae2a8..8b5a75c0 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -10,6 +10,8 @@ const logs = { CREATE_COLLECT_CONTAINER: '%s1 - Creating Collect container.', COLLECT_CONTAINER_CREATED: '%s1 - Created Collect container successfully.', + INITIALIZE_COMPOSABLE_CLIENT: '%s1 - Initializing Composable container.', + CREATE_REVEAL_CONTAINER: '%s1 - Creating Reveal container.', REVEAL_CONTAINER_CREATED: '%s1 - Created Reveal container successfully.', @@ -26,6 +28,8 @@ const logs = { ELEMENT_REVEALED: '%s1 - %s2 Element revealed.', FILE_RENDERED: '%s1 - %s2 File rendered.', COLLECT_SUBMIT_SUCCESS: '%s1 - Data has been collected successfully.', + UPLOAD_FILES_SUCCESS: '%s1 - Files uploaded successfully.', + MULTI_UPLOAD_FILES_SUCCESS: '%s1 - Multiple files uploaded successfully.', REVEAL_SUBMIT_SUCCESS: '%s1 - Data has been revealed successfully.', RENDER_SUBMIT_SUCCESS: '%s1 - File download URL has been fetched successfully.', INSERT_DATA_SUCCESS: '%s1 - Data has been inserted successfully.', @@ -91,6 +95,8 @@ const logs = { VALIDATE_GET_BY_ID_INPUT: '%s1 - Validating getByID input.', }, errorLogs: { + MULTI_FILE_NOT_SUPPORTED: 'Multi file upload is only supported in MULT_FILE_INPUT element in composable container. Please use MULT_FILE_INPUT element for multi file upload.', + INVALID_REVEAL_COMPOSABLE_INPUT: 'Reveal composable input is invalid. Please provide a valid input.', NO_ELEMENTS_IN_COLLECT: 'Validation error. No elements found in collect container', NO_ELEMENTS_IN_COMPOSABLE: 'Validation error. No elements found in composable container', NO_ELEMENTS_IN_REVEAL: 'Validation error. No elements found in reveal container', From f13b04ec476560a9484faaa60455a41546b99f07 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 29 Jul 2025 09:10:59 +0000 Subject: [PATCH 02/50] [AUTOMATED] Release - 2.5.0-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c7281b16..3bb4392e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.2-dev.6c87190", + "version": "2.5.0-beta.8", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From de01056bca8b8bbffe89f14b6d2c1ddd497be007 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:50:44 +0530 Subject: [PATCH 03/50] Release/25.7.8.2 (#616) * SK-2177 support composable container in shadow-dom and normal dom * SK-2177 internal release * [AUTOMATED] Release - 2.5.0-beta.5-dev.3d22e02 * SK-2177 fix promises * [AUTOMATED] Release - 2.5.0-beta.5-dev.c97c0f5 * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.1a3ba57 * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.4f9c03b * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.410308a * SK-2177 comment mount * [AUTOMATED] Release - 2.5.0-beta.5-dev.06519d4 * SK-2177 comment mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.9d975d3 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.1c981ce * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.deb34bf * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.7dda8b1 * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.a9e2143 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.d5616a2 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.a0c77c9 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.69a8f50 * SK-2177 client fix * [AUTOMATED] Release - 2.5.0-beta.5-dev.9812099 * [AUTOMATED] Release - 2.5.0-beta.5-dev.0ee2970 * [AUTOMATED] Release - 2.5.0-beta.5-dev.ef404d4 * SK-2177 target test * [AUTOMATED] Release - 2.5.0-beta.5-dev.5f64ad2 * SK-2177 target tests * [AUTOMATED] Release - 2.5.0-beta.5-dev.25da8a5 * SK-2177 target tests * SK-2177 fix height * [AUTOMATED] Release - 2.5.0-beta.5-dev.36a43c5 * SK-2177 file render * [AUTOMATED] Release - 2.5.0-beta.5-dev.fc93609 * SKS-2117 WIP reveal composable changes. * SKS-2177: WIP added reveal composble element styles. * SK-2177 added event for reveal composable * SK-2177 added event for reveal composable * SK-2177 add reveal element input validations. * SK-2177 file render changes * SK-2177 file render changes * [AUTOMATED] Release - 2.5.0-beta.5-dev.50ad902 * SK-2177 fix height * SK-2177 fix render changes * [AUTOMATED] Release - 2.5.0-beta.5-dev.8fd34b7 * SK-2177 added error handling * SK-2177 trigger release internal * [AUTOMATED] Release - 2.5.0-beta.5-dev.de7a4f1 * SK-2177 add optional checks * [AUTOMATED] Release - 2.5.0-beta.5-dev.f8a1528 * SK-2191 add support for multiple file upload * [AUTOMATED] Release - 2.5.0-beta.6-dev.97db0e3 * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.e034fae * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.9003510 * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.b79aa08 * [AUTOMATED] Release - 2.5.0-beta.6-dev.5974a3a * SK-2191 fix drag and drop * SK-2197 removed commented code * [AUTOMATED] Release - 2.5.0-beta.6-dev.19a5b44 * SK-2191 fix height * [AUTOMATED] Release - 2.5.0-beta.6-dev.7c41e89 * [AUTOMATED] Release - 2.5.0-beta.6-dev.a29ea92 * SK-2202 fix the listeners * [AUTOMATED] Release - 2.4.2-dev.eb6dbb3 * SK-2202 fix the submit * [AUTOMATED] Release - 2.4.2-dev.47ea8b2 * SK-2202 add optional check * [AUTOMATED] Release - 2.4.2-dev.141ab05 * [AUTOMATED] Release - 2.4.2-dev.6c87190 * SK-2202 event listener * [AUTOMATED] Release - 2.5.0-beta.8-dev.d79138c * [AUTOMATED] Release - 2.5.0-beta.8-dev.cc9dc5a --------- Co-authored-by: skyflow-bharti Co-authored-by: yaswanth-pula-skyflow --- package.json | 2 +- src/core/external/collect/collect-element.ts | 3 ++- src/core/internal/iframe-form/index.ts | 20 +++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 3bb4392e..4ce21b38 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.8", + "version": "2.5.0-beta.8-dev.cc9dc5a", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index d2aa0ac7..967f5b40 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -512,7 +512,8 @@ class CollectElement extends SkyflowElement { this.#states[index].isFocused = data.value.isFocused; this.#states[index].isRequired = data.value.isRequired; this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; - if (element.elementType === ElementType.MULTI_FILE_INPUT) { + if (element.elementType === ElementType.MULTI_FILE_INPUT + || element.elementType === ElementType.FILE_INPUT) { this.#states[index].metaData = data?.value?.metaData || []; } if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index de56c27f..6da0eebb 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -220,7 +220,8 @@ export default class IFrameFormElement extends EventEmitter { value: { ...this.getStatus() }, }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -670,7 +671,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -777,7 +779,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -806,7 +809,8 @@ export default class IFrameFormElement extends EventEmitter { && (this.fieldType === ELEMENTS.EXPIRATION_DATE.name || this.fieldType === ELEMENTS.EXPIRATION_MONTH.name || this.fieldType === ELEMENTS.FILE_INPUT.name - || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) + || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + ) ) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, @@ -814,7 +818,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -845,7 +850,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -905,4 +911,4 @@ export default class IFrameFormElement extends EventEmitter { this.resetData(); this.resetEvents(); } -} +} \ No newline at end of file From c482a14055878207ece771b8c5f9542a2267682e Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 16:57:33 +0530 Subject: [PATCH 04/50] SK-2239: add file metadata in file render and wcag keyboard actions --- package.json | 2 +- src/core-utils/reveal.ts | 3 +- src/core/external/collect/collect-element.ts | 9 +++--- src/core/external/common/iframe.ts | 1 + .../reveal/composable-reveal-container.ts | 4 +-- .../reveal/composable-reveal-internal.ts | 20 ++++++------- src/core/internal/iframe-form/index.ts | 20 ++++++------- src/core/internal/index.ts | 30 ++++++++++++++++++- src/core/internal/reveal/reveal-frame.ts | 4 ++- src/utils/common/index.ts | 1 + tests/utils/jwt-utils.test.js | 4 +-- 11 files changed, 65 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 4ce21b38..3c308b17 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.8-dev.cc9dc5a", + "version": "2.4.3-dev.b8e2236", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index 79361634..145708d0 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -141,7 +141,7 @@ export const getFileURLForRender = ( paramList += `${skyflowIdRecord.skyflowID}?`; - paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}`; + paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}&returnFileMetadata=true`; const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}/${paramList}`; return client.request({ @@ -340,6 +340,7 @@ export const formatForRenderClient = (response: IRenderResponseType, column: str const successRecord = { skyflow_id: response.fields.skyflow_id, column, + fileMetadata: response.fileMetadata, }; formattedResponse.success = successRecord; } else if (response.errors) { diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 967f5b40..a423b3b5 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -468,7 +468,7 @@ class CollectElement extends SkyflowElement { } }); this.#elements.forEach((element1) => { - const isComposableContainer = this.#elements.length > 1; + const isComposableContainer = this.#metaData?.containerType === 'COMPOSABLE'; if (isComposableContainer) { window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT @@ -482,7 +482,6 @@ class CollectElement extends SkyflowElement { ) { this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); } else { - const isComposable = this.#elements.length > 1; this.#elements.forEach((element, index) => { if (data.name === element.elementName) { let emitEvent = ''; @@ -519,7 +518,7 @@ class CollectElement extends SkyflowElement { if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; else this.#states[index].value = undefined; - emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + emitEvent = isComposableContainer ? `${emitEvent}:${data.name}` : emitEvent; this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, {}, (payload:any) => { @@ -531,12 +530,12 @@ class CollectElement extends SkyflowElement { ...this.#states[index], elementType: element.elementType, }; - if (isComposable) { + if (isComposableContainer) { this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { iframeName: this.#iframe.name, }); } - if (isComposable && this.#groupEmitter) { + if (isComposableContainer && this.#groupEmitter) { this.#groupEmitter._emit(emitEvent, emitData); } else { this.#eventEmitter._emit(emitEvent, emitData); diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index 8f7f8a17..c85b8e22 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -26,6 +26,7 @@ export default class IFrame { this.iframe = iframer({ name: this.name, referrer: clientDomain, + title: name.match(/^element:([^:]+):/)?.[1] ?? name, }); } diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index 2d829848..bf635096 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -187,9 +187,9 @@ class ComposableRevealContainer extends Container { let element = this.#elements[this.#tempElements.elementName]; if (element) { if (isSingleElementAPI) { - element.update(elements[0]); + // element.update(elements[0]); } else { - element.update(this.#tempElements); + // element.update(this.#tempElements); } } else { const elementId = uuid(); diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index a5584755..618f753e 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -108,12 +108,6 @@ class ComposableRevealInternalElement extends SkyflowElement { this.#iframe?.setIframeHeight(data?.height); }); - window?.addEventListener('message', (event) => { - if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { - this.#isComposableFrameReady = true; - } - }); window?.addEventListener('message', (event) => { if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + this.#iframe?.name) { @@ -136,6 +130,12 @@ class ComposableRevealInternalElement extends SkyflowElement { rows?.forEach((row, rowIndex) => { row?.elements?.forEach((element: any, elementIndex: number) => { if (!element?.name) return; + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + element?.name) { + this.#isComposableFrameReady = true; + } + }); this.#eventEmitter?.on( `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST}:${element?.name}`, (data, callback) => { @@ -351,9 +351,9 @@ class ComposableRevealInternalElement extends SkyflowElement { MessageType.LOG, loglevel); validateRenderElementRecord(recordData); - window.addEventListener('message', (event) => { + window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { + + recordData?.name) { this.#isMounted = true; this.#getSkyflowBearerToken()?.then((authToken) => { printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), @@ -377,7 +377,7 @@ class ComposableRevealInternalElement extends SkyflowElement { window.addEventListener('message', (event1) => { if (event1?.data && event1?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE - + this.#iframe.name) { + + recordData.name) { if (event1?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { const revealData = event1?.data?.data?.result; if (revealData?.error) { @@ -592,7 +592,7 @@ class ComposableRevealInternalElement extends SkyflowElement { } else { window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { + + record?.name) { this.#emitEvent( ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, { diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 6da0eebb..32d29010 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -313,9 +313,9 @@ export default class IFrameFormElement extends EventEmitter { } getFileDetails = (value: FileList | File | null): Array<{ - name: string; - size: number; - type: string; + fileName: string; + fileSizeKB: number; + fileType: string; }> => { // Return empty array if no value if (!value) return []; @@ -324,18 +324,18 @@ export default class IFrameFormElement extends EventEmitter { // Handle FileList if (value instanceof FileList) { return Array.from(value).map((file) => ({ - name: file.name, - size: file.size, - type: file.type, + fileName: file.name, + fileSizeKB: Math.ceil(file.size / 1024), + fileType: file.type, })); } // Handle single File if (value instanceof File) { return [{ - name: value.name, - size: value.size, - type: value.type, + fileName: value.name, + fileSizeKB: Math.ceil(value.size / 1024), + fileType: value.type, }]; } @@ -911,4 +911,4 @@ export default class IFrameFormElement extends EventEmitter { this.resetData(); this.resetEvents(); } -} \ No newline at end of file +} diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index aca89ed3..01878a53 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -145,6 +145,14 @@ export default class FrameElement { this.inputParent.style.position = 'relative'; const inputElement = document.createElement(type); this.domInput = inputElement; + inputElement.addEventListener('keydown', (event) => { + const keyboardEvent = event as KeyboardEvent; + if ((keyboardEvent.ctrlKey || keyboardEvent.metaKey) && keyboardEvent.key.toLowerCase() === 'z') { + keyboardEvent.preventDefault(); + this.setValue(''); + this.iFrameFormElement.setValue('', true); + } + }); this.domInput.iFrameFormElement = this.iFrameFormElement; inputElement.setAttribute(CUSTOM_ROW_ID_ATTRIBUTE, this.htmlDivElement?.id?.split(':')[0] || ''); this.inputParent.append(inputElement); @@ -164,6 +172,15 @@ export default class FrameElement { this.dropdownSelect = document.createElement('select'); this.dropdownSelect.setAttribute('style', this.options?.inputStyles?.dropdown ? (DROPDOWN_STYLES + styleToString(this.options.inputStyles.dropdown)) : DROPDOWN_STYLES); + this.dropdownSelect.addEventListener('focus', () => { + if (this.options?.inputStyles?.dropdownIcon?.focus) { + this.setDropdownIconStyle(this.options?.inputStyles?.dropdownIcon?.focus); + } + }); + + this.dropdownSelect.addEventListener('blur', () => { + this.setDropdownIconStyle(this.options?.inputStyles?.dropdownIcon); + }); this.dropdownSelect.addEventListener('change', (event:any) => { event.preventDefault(); @@ -888,7 +905,6 @@ export default class FrameElement { case INPUT_KEYBOARD_EVENTS.ENTER: this.onSubmit(); - keyBoardEvent.preventDefault(); break; default: break; @@ -1165,4 +1181,16 @@ export default class FrameElement { } } } + + private setDropdownIconStyle(styleObj?: any) { + if (this.dropdownIcon?.style.display === 'block') { + this.dropdownIcon.setAttribute( + 'style', + styleObj + ? DROPDOWN_ICON_STYLES + styleToString(styleObj) + : DROPDOWN_ICON_STYLES, + ); + this.dropdownIcon.style.display = 'block'; + } + } } diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 7fac88e1..71a268d3 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -275,7 +275,7 @@ class RevealFrame { }); window.parent.postMessage( { - type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#containerId, + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#name, data: { name: window.name, }, @@ -291,6 +291,8 @@ class RevealFrame { resolvedResult as IRenderResponseType, this.#record?.column, ); + console.log("event name : ",ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name) + console.log("resultt : ", result); window?.parent?.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, data: { diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 4077d8f8..678043d8 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -99,6 +99,7 @@ export interface IRevealResponseType { export interface IRenderResponseType { fields?: Record errors?: Record + fileMetadata?: Record } export interface IDetokenizeInput { diff --git a/tests/utils/jwt-utils.test.js b/tests/utils/jwt-utils.test.js index e6c4f52e..332e11c5 100644 --- a/tests/utils/jwt-utils.test.js +++ b/tests/utils/jwt-utils.test.js @@ -5,7 +5,7 @@ import isTokenValid from "../../src/utils/jwt-utils"; jest.mock('jwt-decode', () => () => ({exp: 123})) describe('Validation token', () => { - + test('empty token', () => { const res = isTokenValid("") expect(res).toBe(false) @@ -20,4 +20,4 @@ describe('Validation token', () => { const res = isTokenValid("token") expect(res).toBe(false) }) -}); \ No newline at end of file +}); From 1ea087379cc789c749aab4301f561a2134032db6 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 11:28:22 +0000 Subject: [PATCH 05/50] [AUTOMATED] Release - 2.4.3-dev.c482a14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c308b17..991e2df5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.b8e2236", + "version": "2.4.3-dev.c482a14", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 15b241930f1fb87e33cdfdcaf4e2c87953014502 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 17:22:25 +0530 Subject: [PATCH 06/50] SK-2239: remove console log --- src/core/internal/reveal/reveal-frame.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 71a268d3..4476eded 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -291,8 +291,6 @@ class RevealFrame { resolvedResult as IRenderResponseType, this.#record?.column, ); - console.log("event name : ",ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name) - console.log("resultt : ", result); window?.parent?.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, data: { From 6e78ac2ba029c96204c085d462aa3ae2c674dbaa Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 11:53:08 +0000 Subject: [PATCH 07/50] [AUTOMATED] Release - 2.4.3-dev.15b2419 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 991e2df5..ee1cee60 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.c482a14", + "version": "2.4.3-dev.15b2419", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From a4369f10c1328493b95be6ff3e652950362e2c64 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 12:14:13 +0000 Subject: [PATCH 08/50] [AUTOMATED] Release - 2.5.0-beta.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee1cee60..55bd50d4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.15b2419", + "version": "2.5.0-beta.9", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 99fa815fe130079108a692e9bab473917c399643 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 13:15:45 +0530 Subject: [PATCH 09/50] SK-2254: fix 0px height in composable container --- src/core/external/collect/collect-element.ts | 20 ++- src/core/internal/frame-element-init.ts | 141 +++++++++++-------- src/utils/validators/index.ts | 4 - 3 files changed, 98 insertions(+), 67 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index a423b3b5..c4cb3e25 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -41,6 +41,7 @@ import { pushElementEventWithTimeout, updateMetricObjectValue, } from '../../../metrics'; +import properties from '../../../properties'; const CLASS_NAME = 'Element'; class CollectElement extends SkyflowElement { @@ -202,12 +203,27 @@ class CollectElement extends SkyflowElement { if (!domElement) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['CollectElement'], true); } - this.resizeObserver = new ResizeObserver(() => { + + if(domElement instanceof HTMLElement){ + this.resizeObserver = new ResizeObserver(() => { + if (domElement.getElementsByTagName('iframe')[0]?.contentWindow) { + const iframeElement = domElement.getElementsByTagName('iframe')[0] + if(iframeElement.name === this.#iframe.name){ + iframeElement?.contentWindow?.postMessage({ + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }); + } else if (typeof domElement === 'string'){ + this.resizeObserver = new ResizeObserver(() => { this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, {}, (payload:any) => { this.#iframe.setIframeHeight(payload.height); }); - }); + }); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElement); if ( this.#metaData?.clientJSON?.config?.options?.trackMetrics diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index 13c89945..a3b033ca 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -217,6 +217,9 @@ export default class FrameElementInit { const value: Blob = Object.values(fileUploadObject)[0] as Blob; + formData.append('columnName', column); + formData.append('tableName', tableName); + if (preserveFileName) { const isValidFileName = vaildateFileName(state.value.name); if (!isValidFileName) { @@ -224,10 +227,14 @@ export default class FrameElementInit { new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true), ); } - formData.append(column, value); + formData.append('file', value); } else { const generatedFileName = generateUploadFileName(state.value.name); - formData.append(column, new File([value], generatedFileName, { type: state.value.type })); + formData.append('file', new File([value], generatedFileName, { type: state.value.type })); + } + + if (skyflowID) { + formData.append('skyflowID', skyflowID); } const client = this.#client; @@ -236,7 +243,7 @@ export default class FrameElementInit { .request({ body: formData, requestMethod: 'POST', - url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowID}/files`, + url: `${client.config.vaultURL}/v2/vaults/${client.config.vaultID}/files/upload`, headers: { authorization: `Bearer ${clientConfig.authToken}`, 'content-type': 'multipart/form-data', @@ -457,73 +464,90 @@ export default class FrameElementInit { // eslint-disable-next-line consistent-return private multipleUploadFiles = - (fileElement: IFrameFormElement, - clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + (fileElement: IFrameFormElement, clientConfig, metaData) => new Promise((rootResolve, rootReject) => { this.#client = new Client(clientConfig, {}); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); - const { - state, tableName, onFocusChange, preserveFileName, - } = fileElement; + + const { state, tableName, onFocusChange, preserveFileName } = fileElement; if (state.isRequired) { onFocusChange(false); } - if (fileElement.state.value === undefined || fileElement.state.value === null || fileElement.state.value === '') { + if (state.value === undefined || state.value === null || state.value === '') { rootReject({ error: 'No files selected' }); return; } - const files = state.value instanceof FileList - ? Array.from(state.value) - : [state.value]; + const files = state.value instanceof FileList ? Array.from(state.value) : [state.value]; this.validateFiles(files, state, fileElement); - const insertRequest = this.createInsertRequest(files.length, metaData); - this.insertDataCallInMultiFiles( - insertRequest, this.#client, tableName as string, clientConfig.authToken as string, - ).then((response: any) => { - const skyflowIDs = this.extractSkyflowIDs(response); - if (skyflowIDs.length === 0) { - rootReject({ error: 'No skyflow IDs returned from insert data' }); - return; + + const uploadFile = (file: File, skyflowID?: string) => { + const formData = new FormData(); + formData.append('columnName', state.name); + if (tableName) formData.append('tableName', tableName); + if (preserveFileName) { + formData.append('file', file); + } else { + const generatedFileName = generateUploadFileName(file.name); + formData.append('file', new File([file], generatedFileName, { type: file.type })); } - const promises: Promise[] = []; - - files.forEach((file, index) => { - const fileUploadObject: any = {}; - fileUploadObject[state.name] = file; - const formData = new FormData(); - const column = Object.keys(fileUploadObject)[0]; - const value: Blob = Object.values(fileUploadObject)[0] as Blob; - if (preserveFileName) { - formData.append(column, value); - } else { - const generatedFileName = generateUploadFileName(file.name); - formData.append(column, new File([value], generatedFileName, { type: file.type })); + if (skyflowID) formData.append('skyflowID', skyflowID); + const client = this.#client; + return this.#client.request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v2/vaults/${this.#client.config.vaultID}/files/upload`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }); + }; + + if (metaData && Object.keys(metaData).length > 0) { + const insertRequest = this.createInsertRequest(files.length, metaData); + this.insertDataCallInMultiFiles( + insertRequest, this.#client, tableName as string, clientConfig.authToken as string, + ).then((response: any) => { + const skyflowIDs = this.extractSkyflowIDs(response); + if (skyflowIDs.length === 0) { + rootReject({ error: 'No skyflow IDs returned from insert data' }); + return; } - const client = this.#client; - const promise1 = new Promise((resolve, reject) => { - client - .request({ - body: formData, - requestMethod: 'POST', - url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowIDs[index]}/files`, - headers: { - authorization: `Bearer ${clientConfig.authToken}`, - 'content-type': 'multipart/form-data', - }, - }) - .then((response1) => { - resolve(response1); - }) - .catch((error) => { - reject(error); - }); + const promises = files.map((file, idx) => uploadFile(file, skyflowIDs[idx])); + Promise.allSettled(promises).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response1 = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response1); + } + } + } else if (result.status === 'rejected') { + errorResponse.push({ error: result.reason }); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }).catch((error) => { + printLog(`${error}`, MessageType.LOG, this.context?.logLevel); + rootReject({ + error: error?.error || error, }); - promises.push(promise1); }); - Promise.allSettled( - promises, - ).then((resultSet) => { + } else { + const promises = files.map((file) => uploadFile(file)); + Promise.allSettled(promises).then((resultSet) => { const fileUploadResponse: any[] = []; const errorResponse: any[] = []; resultSet.forEach((result) => { @@ -547,12 +571,7 @@ export default class FrameElementInit { } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); else rootReject({ fileUploadResponse, errorResponse }); }); - }).catch((error) => { - printLog(`${error}`, MessageType.LOG, this.context?.logLevel); - rootReject({ - error: error?.error || error, - }); - }); + } }); private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => { diff --git a/src/utils/validators/index.ts b/src/utils/validators/index.ts index c3d26997..1cf03de7 100644 --- a/src/utils/validators/index.ts +++ b/src/utils/validators/index.ts @@ -560,10 +560,6 @@ export const validateCollectElementInput = (input: CollectElementInput, logLevel if (Object.prototype.hasOwnProperty.call(input, 'skyflowID') && !(typeof input.skyflowID === 'string')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_SKYFLOWID_IN_COLLECT, [], true); } - if (input.type === ElementType.FILE_INPUT - && !Object.keys(input).includes('skyflowID')) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_SKYFLOWID_IN_COLLECT, [], true); - } }; export const validateUpsertOptions = (upsertOptions) => { From afa427716201b3b6f3d4adf00f3641eb21e26b1d Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 07:46:37 +0000 Subject: [PATCH 10/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.99fa815 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55bd50d4..c086c343 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9", + "version": "2.5.0-beta.9-dev.99fa815", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From fcfbd99c49bb50b87e11b15e55f47db8aacbf448 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 14:33:13 +0530 Subject: [PATCH 11/50] SK-2254: fix 0px height in composable reveal --- .../reveal/composable-reveal-internal.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index 618f753e..2e5a2cbf 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -54,6 +54,8 @@ class ComposableRevealInternalElement extends SkyflowElement { #elementId: string; + resizeObserver: ResizeObserver | null; + #readyToMount: boolean = false; #eventEmitter: EventEmitter; @@ -83,6 +85,7 @@ class ComposableRevealInternalElement extends SkyflowElement { super(); this.#elementId = elementId; this.#metaData = metaData; + this.resizeObserver = null; this.#clientId = this.#metaData?.uuid; this.#isSingleElementAPI = isSingleElementAPI; this.#recordData = recordGroup; @@ -185,6 +188,20 @@ class ComposableRevealInternalElement extends SkyflowElement { if (!domElementSelector) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['RevealElement'], true); } + + if(domElementSelector instanceof HTMLElement){ + this.resizeObserver = new ResizeObserver(() => { + if (domElementSelector.getElementsByTagName('iframe')[0]?.contentWindow) { + const iframeElement = domElementSelector.getElementsByTagName('iframe')[0] + if(iframeElement.name === this.#iframe.name){ + iframeElement?.contentWindow?.postMessage({ + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElementSelector); if ( this.#metaData?.clientJSON?.config?.options?.trackMetrics @@ -193,6 +210,15 @@ class ComposableRevealInternalElement extends SkyflowElement { pushElementEventWithTimeout(this.#elementId); } + if (typeof domElementSelector === 'string') { + const targetElement = document.querySelector(domElementSelector); + if (targetElement) { + this.resizeObserver?.observe(targetElement); + } + } else if (domElementSelector instanceof HTMLElement) { + this.resizeObserver?.observe(domElementSelector); + } + this.#readyToMount = true; if (this.#readyToMount) { this.#iframe.mount(domElementSelector, undefined, { From 3e89042a4c8a6185a31188fbee161d0765161916 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 09:04:49 +0000 Subject: [PATCH 12/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.fcfbd99 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c086c343..325b02da 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.99fa815", + "version": "2.5.0-beta.9-dev.fcfbd99", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From ff71850aaea094c49fc14ffe227b4b6d85ef3942 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 16:13:36 +0530 Subject: [PATCH 13/50] SK-2254: add resize observer for multiple iframes --- src/core/external/collect/collect-element.ts | 19 +++++++++++++------ .../reveal/composable-reveal-internal.ts | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index c4cb3e25..dee013bc 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -206,12 +206,19 @@ class CollectElement extends SkyflowElement { if(domElement instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { - if (domElement.getElementsByTagName('iframe')[0]?.contentWindow) { - const iframeElement = domElement.getElementsByTagName('iframe')[0] - if(iframeElement.name === this.#iframe.name){ - iframeElement?.contentWindow?.postMessage({ - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, properties.IFRAME_SECURE_ORIGIN); + const iframeElements = domElement.getElementsByTagName('iframe'); + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + 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 2e5a2cbf..f9b26bc6 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -191,12 +191,19 @@ class ComposableRevealInternalElement extends SkyflowElement { if(domElementSelector instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { - if (domElementSelector.getElementsByTagName('iframe')[0]?.contentWindow) { - const iframeElement = domElementSelector.getElementsByTagName('iframe')[0] - if(iframeElement.name === this.#iframe.name){ - iframeElement?.contentWindow?.postMessage({ - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, properties.IFRAME_SECURE_ORIGIN); + const iframeElements = domElementSelector.getElementsByTagName('iframe'); + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + properties.IFRAME_SECURE_ORIGIN + ); } } }); From f295fe0c87966a50a6252ae3103422b712ed820a Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 10:44:46 +0000 Subject: [PATCH 14/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.ff71850 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 325b02da..a818b825 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.fcfbd99", + "version": "2.5.0-beta.9-dev.ff71850", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 3d1ed74f3421e3dc2a1e5c13e0d87fe4752ab1d9 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 16:57:19 +0530 Subject: [PATCH 15/50] SK-2254: add undefined check for resize observer for multiple iframes --- src/core/external/collect/collect-element.ts | 26 ++++++++++--------- .../reveal/composable-reveal-internal.ts | 26 ++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index dee013bc..93a5bb8c 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -207,18 +207,20 @@ class CollectElement extends SkyflowElement { if(domElement instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { const iframeElements = domElement.getElementsByTagName('iframe'); - for (let i = 0; i < iframeElements.length; i++) { - const iframeElement = iframeElements[i]; - if ( - iframeElement.name === this.#iframe.name && - iframeElement.contentWindow - ) { - iframeElement?.contentWindow?.postMessage( - { - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, - properties.IFRAME_SECURE_ORIGIN - ); + if (iframeElements && iframeElements.length > 0) { + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + 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 f9b26bc6..9453a06a 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -192,18 +192,20 @@ class ComposableRevealInternalElement extends SkyflowElement { if(domElementSelector instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { const iframeElements = domElementSelector.getElementsByTagName('iframe'); - for (let i = 0; i < iframeElements.length; i++) { - const iframeElement = iframeElements[i]; - if ( - iframeElement.name === this.#iframe.name && - iframeElement.contentWindow - ) { - iframeElement?.contentWindow?.postMessage( - { - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, - properties.IFRAME_SECURE_ORIGIN - ); + if (iframeElements && iframeElements.length > 0) { + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + properties.IFRAME_SECURE_ORIGIN + ); + } } } }); From fcc6e87460cdaafe075406b38356be4f82403a46 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 11:28:25 +0000 Subject: [PATCH 16/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.3d1ed74 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a818b825..d677e338 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.ff71850", + "version": "2.5.0-beta.9-dev.3d1ed74", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From dd803c135a5de151b973850061da2a92242b6ce8 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 18:25:59 +0000 Subject: [PATCH 17/50] [AUTOMATED] Release - 2.5.0-beta.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d677e338..6d849e7a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.3d1ed74", + "version": "2.5.0-beta.10", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 08ce5476eeb7052a5958c8676a37d976ff1e9901 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 16 Oct 2025 18:39:26 +0530 Subject: [PATCH 18/50] SK-2330 fix the lint error --- src/core/internal/frame-element-init.ts | 7 +++++-- tests/core/external/collect/composable-container.test.js | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index a3b033ca..c615d1e9 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -464,11 +464,14 @@ export default class FrameElementInit { // eslint-disable-next-line consistent-return private multipleUploadFiles = - (fileElement: IFrameFormElement, clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + (fileElement: IFrameFormElement, + clientConfig, metaData) => new Promise((rootResolve, rootReject) => { this.#client = new Client(clientConfig, {}); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); - const { state, tableName, onFocusChange, preserveFileName } = fileElement; + const { + state, tableName, onFocusChange, preserveFileName, + } = fileElement; if (state.isRequired) { onFocusChange(false); } diff --git a/tests/core/external/collect/composable-container.test.js b/tests/core/external/collect/composable-container.test.js index 4dd7be88..7ca7037e 100644 --- a/tests/core/external/collect/composable-container.test.js +++ b/tests/core/external/collect/composable-container.test.js @@ -18,7 +18,7 @@ const bus = require('framebus'); iframerUtils.getIframeSrc = jest.fn(() => ('https://google.com')); -const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve()); +const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve('token')); const mockUuid = '1234'; jest.mock('../../../../src/libs/uuid', () => ({ @@ -57,6 +57,7 @@ EventEmitter.mockImplementation(()=>({ const metaData = { + getSkyflowBearerToken: getBearerToken, skyflowContainer:{ isControllerFrameReady: true }, @@ -78,6 +79,7 @@ const metaData = { }, }; const metaData2 = { + getSkyflowBearerToken: getBearerToken, skyflowContainer:{ isControllerFrameReady: false }, From 6d64db036338e83bd58482d6833120839d8e0dc4 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 16 Oct 2025 13:10:13 +0000 Subject: [PATCH 19/50] [AUTOMATED] Release - 2.5.0-beta.10-dev.08ce547 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d849e7a..5f39e47d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.10", + "version": "2.5.0-beta.10-dev.08ce547", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 32d8779bf20330650f6f958fc5946cdff8b3e0e6 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Tue, 29 Jul 2025 14:26:10 +0530 Subject: [PATCH 20/50] Release/25.7.8.2 (#615) --- src/core-utils/collect.ts | 147 +++- src/core-utils/reveal.ts | 109 +++ src/core/constants.ts | 24 + src/core/external/collect/collect-element.ts | 250 ++++--- .../collect/compose-collect-container.ts | 372 +++++++---- .../collect/compose-collect-element.ts | 57 +- .../reveal/composable-reveal-container.ts | 474 +++++++++++++ .../reveal/composable-reveal-element.ts | 66 ++ .../reveal/composable-reveal-internal.ts | 610 +++++++++++++++++ .../internal/composable-frame-element-init.ts | 369 +++++++++++ src/core/internal/frame-element-init.ts | 625 +++++++++++++++++- src/core/internal/iframe-form/index.ts | 181 ++++- src/core/internal/index.ts | 39 +- src/core/internal/internal-types/index.ts | 1 + src/core/internal/reveal/reveal-frame.ts | 299 +++++++-- .../skyflow-frame/skyflow-frame-controller.ts | 8 +- src/index-internal.ts | 5 + src/libs/element-options.ts | 4 +- src/skyflow.ts | 82 ++- src/utils/common/index.ts | 12 + src/utils/constants.ts | 6 + src/utils/logs.ts | 6 + 22 files changed, 3451 insertions(+), 295 deletions(-) create mode 100644 src/core/external/reveal/composable-reveal-container.ts create mode 100644 src/core/external/reveal/composable-reveal-element.ts create mode 100644 src/core/external/reveal/composable-reveal-internal.ts create mode 100644 src/core/internal/composable-frame-element-init.ts diff --git a/src/core-utils/collect.ts b/src/core-utils/collect.ts index 93181075..a0fbb98d 100644 --- a/src/core-utils/collect.ts +++ b/src/core-utils/collect.ts @@ -205,7 +205,7 @@ const updateRecordsInVault = ( options, ) => { const table = skyflowIdRecord.fields.table; - const skyflowID = skyflowIdRecord.skyflowID; + const skyflowID = skyflowIdRecord?.skyflowID; skyflowIdRecord.fields = omit(skyflowIdRecord.fields, 'table'); skyflowIdRecord.fields = omit(skyflowIdRecord.fields, 'skyflowID'); return client.request({ @@ -279,6 +279,151 @@ export const updateRecordsBySkyflowID = async ( }); }); +export const updateRecordsBySkyflowIDComposable = async ( + skyflowIdRecords, + client: Client, + options, + authToken: string, +) => new Promise((rootResolve, rootReject) => { + let updateResponseSet: Promise[]; + // eslint-disable-next-line prefer-const + updateResponseSet = skyflowIdRecords?.updateRecords?.map( + (skyflowIdRecord: IInsertRecord) => new Promise((resolve, reject) => { + updateRecordsInVault(skyflowIdRecord, client, authToken, options) + ?.then((resolvedResult: any) => { + const resp = constructFinalUpdateRecordResponse( + resolvedResult, + options?.tokens, + skyflowIdRecord, + ); + resolve(resp); + }, + (rejectedResult) => { + let errorResponse = rejectedResult; + if (rejectedResult?.error) { + errorResponse = { + error: { + code: rejectedResult?.error?.code, + description: rejectedResult?.error?.description, + }, + }; + } + printLog(rejectedResult?.error?.description ?? '', MessageType.ERROR, LogLevel.ERROR); + reject(errorResponse); + })?.catch((error) => { + reject(error); + }); + }), + ); + Promise.allSettled(updateResponseSet)?.then((resultSet: any) => { + const recordsResponse: any[] = []; + const errorsResponse: any[] = []; + resultSet?.forEach((result: { status: string; value: any; reason?: any; }) => { + if (result?.status === 'fulfilled') { + recordsResponse?.push(result?.value); + } else { + errorsResponse?.push(result?.reason); + } + }); + + if (errorsResponse?.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse?.length === 0) { + rootReject({ errors: errorsResponse }); + } else { + rootReject({ records: recordsResponse, errors: errorsResponse }); + } + }); +}); + +export const insertDataInCollect = async ( + records, + client: Client, + options, + finalInsertRecords, + authToken: string, +) => new Promise((resolve) => { + let insertResponse: any; + let insertErrorResponse: any; + client + ?.request({ + body: { + records, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + ?.then((response: any) => { + insertResponse = constructInsertRecordResponse( + response, + options?.tokens, + finalInsertRecords?.records, + ); + resolve(insertResponse); + }) + ?.catch((error) => { + insertErrorResponse = { + errors: [ + { + error: { + code: error?.error?.code, + description: error?.error?.description, + }, + }, + ], + }; + resolve(insertErrorResponse); + }); +}); + +export const insertDataInMultipleFiles = async ( + records, + client: Client, + options, + finalInsertRecords, + authToken: string, +) => new Promise((resolve) => { + let insertResponse: any; + let insertErrorResponse: any; + client + ?.request({ + body: { + records, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + ?.then((response: any) => { + insertResponse = constructInsertRecordResponse( + response, + options?.tokens, + finalInsertRecords?.records, + ); + resolve(insertResponse); + }) + ?.catch((error) => { + insertErrorResponse = { + errors: [ + { + error: { + code: error?.error?.code, + description: error?.error?.description, + }, + }, + ], + }; + resolve(insertErrorResponse); + }); +}); + export const checkForElementMatchRule = (validations: IValidationRule[]) => { if (!validations) return false; for (let i = 0; i < validations.length; i += 1) { diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index 7e11fc37..f2087d84 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -16,6 +16,7 @@ import { GetResponseRecord, GetByIdResponse, GetByIdResponseRecord, + IRevealRecordComposable, } from '../utils/common'; import { printLog } from '../utils/logs-helper'; import { FILE_DOWNLOAD_URL_PARAM } from '../core/constants'; @@ -182,6 +183,27 @@ export const getFileURLFromVaultBySkyflowID = ( rootReject(err); } }); + +export const getFileURLFromVaultBySkyflowIDComposable = ( + skyflowIdRecord: IRevealRecord, + client: Client, + authToken: string, +): Promise => new Promise((rootResolve, rootReject) => { + try { + getFileURLForRender( + skyflowIdRecord, client, authToken as string, + ).then((resolvedResult: IRenderResponseType) => { + rootResolve(resolvedResult); + }).catch((err: any) => { + const errorData = formatForRenderFileFailure(err, skyflowIdRecord.skyflowID as string, + skyflowIdRecord.column as string); + printLog(errorData.error?.description || '', MessageType.ERROR, LogLevel.ERROR); + rootReject(errorData); + }); + } catch (err) { + rootReject(err); + } +}); export const fetchRecordsByTokenId = ( tokenIdRecords: IRevealRecord[], client: Client, @@ -235,6 +257,65 @@ export const fetchRecordsByTokenId = ( rootReject(err); }); }); + +export const fetchRecordsByTokenIdComposable = ( + tokenIdRecords: IRevealRecordComposable[], + client: Client, + authToken: string, +): Promise => new Promise((rootResolve, rootReject) => { + const vaultResponseSet: Promise[] = tokenIdRecords?.map( + (tokenRecord) => new Promise((resolve) => { + const apiResponse: any = []; + const redaction: RedactionType = tokenRecord?.redaction ?? RedactionType.PLAIN_TEXT; + + getTokenRecordsFromVault(tokenRecord?.token ?? '', redaction, client, authToken) + ?.then( + (response: IApiSuccessResponse) => { + const fieldsData = formatForPureJsSuccess(response); + apiResponse?.push({ + ...fieldsData, + frameId: tokenRecord?.iframeName ?? '', + }); + }, + (cause: any) => { + const errorData = formatForPureJsFailure(cause, tokenRecord?.token ?? ''); + printLog(errorData?.error?.description ?? '', MessageType.ERROR, LogLevel.ERROR); + apiResponse?.push({ + ...errorData, + frameId: tokenRecord?.iframeName ?? '', + }); + }, + ) + ?.finally(() => { + resolve(apiResponse); + }); + }), + ); + + Promise.allSettled(vaultResponseSet)?.then((resultSet) => { + const recordsResponse: Record[] = []; + const errorResponse: Record[] = []; + resultSet?.forEach((result) => { + if (result?.status === 'fulfilled') { + result?.value?.forEach((res: Record) => { + if (Object.prototype.hasOwnProperty.call(res, 'error')) { + errorResponse?.push(res); + } else { + recordsResponse?.push(res); + } + }); + } + }); + if (errorResponse?.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse?.length === 0) { + rootReject({ errors: errorResponse }); + } else { + rootReject({ records: recordsResponse, errors: errorResponse }); + } + }); +}); + export const formatRecordsForIframe = (response: IRevealResponseType) => { const result: Record = {}; if (response.records) { @@ -295,6 +376,34 @@ export const formatRecordsForClient = (response: IRevealResponseType): RevealRes return revealResponse; }; +export const formatRecordsForClientComposable = (response) => { + let successRecords = []; + let errorRecords = []; + + if (response?.errors && response?.errors?.length > 0) { + errorRecords = response?.errors?.map((errors) => ({ + error: errors?.error ?? {}, + })); + } + + if (response?.records) { + successRecords = response?.records?.map((record) => ({ + token: record?.[0]?.token ?? '', + valueType: record?.[0]?.valueType ?? '', + })); + } + + if (successRecords?.length > 0 && errorRecords?.length > 0) { + return { success: successRecords, errors: errorRecords }; + } + + if (successRecords?.length > 0) { + return { success: successRecords }; + } + + return { errors: errorRecords }; +}; + export const fetchRecordsGET = async ( skyflowIdRecords: IGetRecord[], client: Client, diff --git a/src/core/constants.ts b/src/core/constants.ts index 57196408..7e2a2761 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -30,11 +30,13 @@ export const SDK_IFRAME_EVENT = 'SDK IFRAME EVENT'; 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 ELEMENT_TYPES = { COLLECT: 'COLLECT', REVEAL: 'REVEAL', COMPOSE: 'COMPOSABLE', + REVEAL_COMPOSE: 'REVEAL_COMPOSE', }; export const EVENT_TYPES = { @@ -104,8 +106,19 @@ export const ELEMENT_EVENTS_TO_CLIENT = { }; export const ELEMENT_EVENTS_TO_IFRAME = { + MULTIPLE_UPLOAD_FILES_RESPONSE: 'MULTIPLE_UPLOAD_FILES_RESPONSE', + RENDER_MOUNTED: 'RENDER_MOUNTED', + HEIGHT_CALLBACK: 'HEIGHT_CALLBACK', + HEIGHT_CALLBACK_COMPOSABLE: 'HEIGHT_CALLBACK_COMPOSABLE', + COMPOSABLE_REVEAL: 'COMPOSABLE_REVEAL', + MULTIPLE_UPLOAD_FILES: 'MULTIPLE_UPLOAD_FILES', COLLECT_CALL_REQUESTS: 'COLLECT_CALL_REQUESTS', + COMPOSABLE_CALL_REQUESTS: 'COMPOSABLE_CALL_REQUESTS', + COMPOSABLE_CALL_RESPONSE: 'COMPOSABLE_CALL_RESPONSE', + COMPOSABLE_FILE_CALL_RESPONSE: 'COMPOSABLE_FILE_CALL_RESPONSE', + COMPOSABLE_CONTAINER: 'COMPOSABLE_CONTAINER', REVEAL_CALL_REQUESTS: 'REVEAL_CALL_REQUESTS', + REVEAL_CALL_RESPONSE: 'REVEAL_CALL_RESPONSE', FRAME_READY: 'FRAME_READY', READY_FOR_CLIENT: 'READY_FOR_CLIENT', TOKENIZATION_REQUEST: 'TOKENIZATION_REQUEST', @@ -162,6 +175,7 @@ export enum ElementType { EXPIRATION_MONTH = 'EXPIRATION_MONTH', EXPIRATION_YEAR = 'EXPIRATION_YEAR', FILE_INPUT = 'FILE_INPUT', + MULTI_FILE_INPUT = 'MULTI_FILE_INPUT', } export enum CardType { @@ -324,6 +338,14 @@ export const ELEMENTS = { type: 'file', }, }, + [ElementType.MULTI_FILE_INPUT]: { + name: 'MULTI_FILE_INPUT', + sensitive: true, + attributes: { + type: 'file', + multiple: '', + }, + }, }; export const CARDNUMBER_INPUT_FORMAT = { @@ -637,6 +659,7 @@ export const DEFAULT_ERROR_TEXT_ELEMENT_TYPES = { [ElementType.EXPIRATION_MONTH]: 'Invalid expiration month', [ElementType.EXPIRATION_YEAR]: 'Invalid expiration year', [ElementType.FILE_INPUT]: logs.errorLogs.INVALID_COLLECT_VALUE, + [ElementType.MULTI_FILE_INPUT]: logs.errorLogs.INVALID_COLLECT_VALUE, }; export const DEFAULT_REQUIRED_TEXT_ELEMENT_TYPES = { @@ -649,6 +672,7 @@ export const DEFAULT_REQUIRED_TEXT_ELEMENT_TYPES = { [ElementType.EXPIRATION_MONTH]: 'expiration month is required', [ElementType.EXPIRATION_YEAR]: 'expiration year is required', [ElementType.FILE_INPUT]: logs.errorLogs.DEFAULT_REQUIRED_COLLECT_VALUE, + [ElementType.MULTI_FILE_INPUT]: logs.errorLogs.DEFAULT_REQUIRED_COLLECT_VALUE, }; export const INPUT_KEYBOARD_EVENTS = { diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 8dfc05de..6d73de71 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -120,7 +120,6 @@ class CollectElement extends SkyflowElement { // if (this.#isSingleElementAPI && this.#elements.length > 1) { // throw new SkyflowError(SKYFLOW_ERROR_CODE.UNKNOWN_ERROR, [], true); // } - this.#doesReturnValue = EnvOptions[this.#context.env].doesReturnValue; this.elementType = this.#isSingleElementAPI ? this.#elements[0].elementType @@ -165,18 +164,18 @@ class CollectElement extends SkyflowElement { this.#readyToMount = container.isMounted; if (container.type === ContainerType.COMPOSABLE) { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + + this.#iframe.name) { + this.#iframe.setIframeHeight(event.data.data.height); + } + }); this.#elements.forEach((element) => { - this.#bus.on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED - + formatFrameNameToId(element.elementName), (data) => { - if (data.name === element.elementName) { - updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, `${element.elementType}_${METRIC_TYPES.EVENTS.MOUNTED}`); + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + + formatFrameNameToId(element.elementName)) { element.isMounted = true; this.#mounted = true; - this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT - + this.#iframe.name, - {}, (payload:any) => { - this.#iframe.setIframeHeight(payload.height); - }); } }); }); @@ -200,6 +199,7 @@ class CollectElement extends SkyflowElement { getID = () => this.#elementId; mount = (domElement: HTMLElement | string) => { + this.#mounted = true; if (!domElement) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['CollectElement'], true); } @@ -469,84 +469,168 @@ class CollectElement extends SkyflowElement { } }); this.#elements.forEach((element1) => { - this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT - + element1.elementName, (data: any) => { - if ( - this.#isSingleElementAPI + const isComposableContainer = this.#elements.length > 1; + if (isComposableContainer) { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + + element1.elementName) { + const data = event.data.data; + if (data.name === element1.elementName) { + if ( + this.#isSingleElementAPI && data.event === ELEMENT_EVENTS_TO_CLIENT.READY && data.name === formatFrameNameToId(this.#iframe.name) - ) { - this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); - } else { - const isComposable = this.#elements.length > 1; - this.#elements.forEach((element, index) => { - if (data.name === element.elementName) { - let emitEvent = ''; - switch (data.event) { - case ELEMENT_EVENTS_TO_CLIENT.FOCUS: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; - break; - case ELEMENT_EVENTS_TO_CLIENT.BLUR: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; - break; - case ELEMENT_EVENTS_TO_CLIENT.CHANGE: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; - break; - case ELEMENT_EVENTS_TO_CLIENT.READY: - emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; - break; - case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: - this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); - return; - // case ELEMENT_EVENTS_TO_CLIENT.CREATED: - // this.#mounted = true; - // return; - // todo: need to implement the below events - // case ELEMENT_EVENTS_TO_CLIENT.ESCAPE: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ESCAPE); - // break; - // case ELEMENT_EVENTS_TO_CLIENT.CLICK: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.CLICK); - // break; - // case ELEMENT_EVENTS_TO_CLIENT.ERROR: - // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ERROR); - // break; - - default: - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); - } - this.#states[index].isEmpty = data.value.isEmpty; - this.#states[index].isValid = data.value.isValid; - this.#states[index].isComplete = data.value.isComplete; - this.#states[index].isFocused = data.value.isFocused; - this.#states[index].isRequired = data.value.isRequired; - this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; - - if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; - else this.#states[index].value = undefined; - - emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; - - this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT - + this.#iframe.name, - {}, (payload:any) => { - this.#iframe.setIframeHeight(payload.height); - }); - - this.#updateState(); - const emitData = { - ...this.#states[index], - elementType: element.elementType, - }; - if (isComposable && this.#groupEmitter) { - this.#groupEmitter._emit(emitEvent, emitData); + ) { + this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); } else { - this.#eventEmitter._emit(emitEvent, emitData); + const isComposable = this.#elements.length > 1; + this.#elements.forEach((element, index) => { + if (data.name === element.elementName) { + let emitEvent = ''; + switch (data.event) { + case ELEMENT_EVENTS_TO_CLIENT.FOCUS: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; + break; + case ELEMENT_EVENTS_TO_CLIENT.BLUR: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; + break; + case ELEMENT_EVENTS_TO_CLIENT.CHANGE: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; + break; + case ELEMENT_EVENTS_TO_CLIENT.READY: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; + break; + case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); + return; + + default: + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); + } + this.#states[index].isEmpty = data.value.isEmpty; + this.#states[index].isValid = data.value.isValid; + this.#states[index].isComplete = data.value.isComplete; + this.#states[index].isFocused = data.value.isFocused; + this.#states[index].isRequired = data.value.isRequired; + this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; + if (element.elementType === ElementType.MULTI_FILE_INPUT) { + this.#states[index].metaData = data?.value?.metaData || []; + } + if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; + else this.#states[index].value = undefined; + + emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + + this.#updateState(); + const emitData = { + ...this.#states[index], + elementType: element.elementType, + }; + if (isComposable) { + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { + iframeName: this.#iframe.name, + }); + } + if (isComposable && this.#groupEmitter) { + this.#groupEmitter._emit(emitEvent, emitData); + } else { + this.#eventEmitter._emit(emitEvent, emitData); + } + } + }); } } - }); - } - }); + } + }); + } else { + this.#bus.on(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + + element1.elementName, (data: any) => { + if ( + this.#isSingleElementAPI + && data.event === ELEMENT_EVENTS_TO_CLIENT.READY + && data.name === formatFrameNameToId(this.#iframe.name) + ) { + this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); + } else { + const isComposable = this.#elements.length > 1; + this.#elements.forEach((element, index) => { + if (data.name === element.elementName) { + let emitEvent = ''; + switch (data.event) { + case ELEMENT_EVENTS_TO_CLIENT.FOCUS: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.FOCUS; + break; + case ELEMENT_EVENTS_TO_CLIENT.BLUR: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.BLUR; + break; + case ELEMENT_EVENTS_TO_CLIENT.CHANGE: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.CHANGE; + break; + case ELEMENT_EVENTS_TO_CLIENT.READY: + emitEvent = ELEMENT_EVENTS_TO_CLIENT.READY; + break; + case ELEMENT_EVENTS_TO_CLIENT.SUBMIT: + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.SUBMIT); + return; + // case ELEMENT_EVENTS_TO_CLIENT.CREATED: + // this.#mounted = true; + // return; + // todo: need to implement the below events + // case ELEMENT_EVENTS_TO_CLIENT.ESCAPE: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ESCAPE); + // break; + // case ELEMENT_EVENTS_TO_CLIENT.CLICK: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.CLICK); + // break; + // case ELEMENT_EVENTS_TO_CLIENT.ERROR: + // this.eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.ERROR); + // break; + + default: + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_EVENT_TYPE, [], true); + } + this.#states[index].isEmpty = data.value.isEmpty; + this.#states[index].isValid = data.value.isValid; + this.#states[index].isComplete = data.value.isComplete; + this.#states[index].isFocused = data.value.isFocused; + this.#states[index].isRequired = data.value.isRequired; + this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; + + if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; + else this.#states[index].value = undefined; + + emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + + this.#updateState(); + const emitData = { + ...this.#states[index], + elementType: element.elementType, + }; + if (isComposable) { + this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { + iframeName: this.#iframe.name, + }); + } + if (isComposable && this.#groupEmitter) { + this.#groupEmitter._emit(emitEvent, emitData); + } else { + this.#eventEmitter._emit(emitEvent, emitData); + } + } + }); + } + }); + } }); }; diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index c3f7d0ef..d63ab17e 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -24,6 +24,7 @@ import { InputStyles, ErrorTextStyles, ContainerOptions, + UploadFilesResponse, } from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import logs from '../../../utils/logs'; @@ -43,6 +44,8 @@ import CollectElement from './collect-element'; 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; @@ -81,7 +84,13 @@ class ComposableContainer extends Container { #clientDomain: string = ''; - #isSkyflowFrameReady: boolean = false; + #isComposableFrameReady: boolean = false; + + #shadowRoot: ShadowRoot | null = null; + + #iframeID: string = ''; + + #getSkyflowBearerToken: () => Promise | undefined; constructor( metaData: Metadata, @@ -104,8 +113,7 @@ class ComposableContainer extends Container { }, }, }; - this.#isSkyflowFrameReady = metaData.skyflowContainer.isControllerFrameReady; - + this.#getSkyflowBearerToken = metaData.getSkyflowBearerToken; this.#skyflowElements = skyflowElements; this.#context = context; this.#options = options; @@ -125,6 +133,18 @@ class ComposableContainer extends Container { this.#context.logLevel); this.#containerMounted = true; this.#updateListeners(); + bus + // .target(properties.IFRAME_SECURE_ORIGIN) + .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; + }); } create = (input: CollectElementInput, options: CollectElementOptions = { @@ -150,7 +170,11 @@ class ComposableContainer extends Container { elementName, }); const controllerIframeName = `${FRAME_ELEMENT}:group:${btoa(this.#tempElements)}:${this.#containerId}:${this.#context.logLevel}:${btoa(this.#clientDomain)}`; - return new ComposableElement(elementName, this.#eventEmitter, controllerIframeName); + this.#iframeID = controllerIframeName; + return new ComposableElement( + elementName, this.#eventEmitter, controllerIframeName, + { ...this.#metaData, type: input.type }, + ); }; #createMultipleElement = ( @@ -316,156 +340,228 @@ class ComposableContainer extends Container { this.#containerElement.mount(domElement); this.#isMounted = true; } + this.#elementsList.forEach((element) => { + this.#eventEmitter.on(`${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${element.elementName}`, (data, callback) => { + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${element.elementName}`, + { + elementName: element.name, + data: { + type: COLLECT_TYPES.FILE_UPLOAD, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + options: { + ...data.options, + }, + }, + ); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + callback(err); + }); + }); + }); + if (domElement instanceof HTMLElement + && (domElement as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElement.getRootNode() as ShadowRoot; + } else if (typeof domElement === 'string') { + const element = document.getElementById(domElement); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + if (this.#shadowRoot !== null) { + this.#eventEmitter.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, (data) => { + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + data.iframeName, {}); + }); + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframeID, {}); + } }; unmount = () => { this.#containerElement.unmount(); }; - collect = (options: ICollectOptions = { tokens: true }): Promise => { - this.#isSkyflowFrameReady = this.#metaData.skyflowContainer.isControllerFrameReady; - if (this.#isSkyflowFrameReady) { - return new Promise((resolve, reject) => { - try { - validateInitConfig(this.#metaData.clientJSON.config); - if (!this.#elementsList || this.#elementsList.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); - } - if (!this.#isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); - } - const containerElements = getElements(this.#tempElements); - containerElements.forEach((element:any) => { - if (!element?.isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); - } - }); - const elementIds:{ frameId:string, elementId:string }[] = []; - const collectElements = Object.values(this.#elements); - collectElements.forEach((element) => { - element.isValidElement(); - }); - if (options && options.tokens && typeof options.tokens !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); - } - if (options?.additionalFields) { - validateAdditionalFieldsInCollect(options.additionalFields); - } - if (options?.upsert) { - validateUpsertOptions(options?.upsert); + collect = (options: ICollectOptions = { tokens: true }) : + Promise => new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + if (!this.#isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); + } + const containerElements = getElements(this.#tempElements); + containerElements.forEach((element:any) => { + if (!element?.isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); + } + }); + const elementIds:{ frameId:string, elementId:string }[] = []; + const collectElements = Object.values(this.#elements); + collectElements.forEach((element) => { + element.isValidElement(); + }); + if (options && options.tokens && typeof options.tokens !== 'boolean') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); + } + if (options?.additionalFields) { + validateAdditionalFieldsInCollect(options.additionalFields); + } + if (options?.upsert) { + validateUpsertOptions(options?.upsert); + } + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: this.#tempElements.elementName, + elementId: element.elementName ?? '', + }); + }); + const client = Client.fromJSON(this.#metaData.clientJSON) as any; + const clientId = client.toJSON()?.metaData?.uuid || ''; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + this.#containerId, { + data: { + type: COLLECT_TYPES.COLLECT, + ...options, + tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + window.addEventListener('message', (event) => { + if (event.data?.type + === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.#containerId) { + const data = event.data.data; + if (!data || data?.error) { + printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); + reject(data?.error); + } else if (data?.records) { + printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(data); + } else { + printLog(`${JSON.stringify(data)}`, MessageType.ERROR, this.#context.logLevel); + reject(data); } - this.#elementsList.forEach((element) => { - elementIds.push({ - frameId: this.#tempElements.elementName, - elementId: element.elementName || '', - }); - }); - bus - // .target(properties.IFRAME_SECURE_ORIGIN) - .emit( - ELEMENT_EVENTS_TO_IFRAME.COLLECT_CALL_REQUESTS + this.#metaData.uuid, - { - type: COLLECT_TYPES.COLLECT, - ...options, - tokens: options?.tokens !== undefined ? options.tokens : true, - elementIds, - containerId: this.#containerId, - }, - (data: any) => { - if (!data || data?.error) { - printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); - reject(data?.error); - } else { - printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), - MessageType.LOG, - this.#context.logLevel); - - resolve(data); - } - }, - ); - printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, - CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), - MessageType.LOG, this.#context.logLevel); - } catch (err:any) { - printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); - reject(err); } }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), + MessageType.LOG, this.#context.logLevel); + } catch (err:any) { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); } - return new Promise((resolve, reject) => { - try { - validateInitConfig(this.#metaData.clientJSON.config); - if (!this.#elementsList || this.#elementsList.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); - } - if (!this.#isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); - } + }); + + #emitEvent = (eventName: string, options?: Record, callback?: any) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } else { + const iframe = document.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }; - const containerElements = getElements(this.#tempElements); - containerElements.forEach((element:any) => { - if (!element?.isMounted) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.ELEMENTS_NOT_MOUNTED, [], true); - } + uploadFiles = (options: ICollectOptions): + Promise => new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + if (!this.#isMounted) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.COMPOSABLE_CONTAINER_NOT_MOUNTED, [], true); + } + const elementIds:{ frameId:string, elementId:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: this.#tempElements.elementName, + elementId: element.elementName ?? '', }); - const elementIds:{ frameId:string, elementId:string }[] = []; - const collectElements = Object.values(this.#elements); - collectElements.forEach((element) => { - element.isValidElement(); + }); + const client = Client.fromJSON(this.#metaData.clientJSON) as any; + const clientId = client.toJSON()?.metaData?.uuid || ''; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + this.#containerId, { + data: { + type: COLLECT_TYPES.FILE_UPLOAD, + ...options, + // tokens: options?.tokens !== undefined ? options.tokens : true, + elementIds, + containerId: this.#containerId, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, }); - - if (options && options.tokens && typeof options.tokens !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_COLLECT, [], true); - } - if (options?.additionalFields) { - validateAdditionalFieldsInCollect(options.additionalFields); - } - if (options?.upsert) { - validateUpsertOptions(options?.upsert); - } - this.#elementsList.forEach((element) => { - elementIds.push({ - frameId: this.#tempElements.elementName, - elementId: element.elementName || '', - }); + window.addEventListener('message', (event) => { + if (event.data?.type + === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.#containerId) { + const data = event.data.data; + if (!data || data?.error) { + printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); + reject(data?.error); + } else if (data?.fileUploadResponse) { + printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(data); + } else { + printLog(`${JSON.stringify(data)}`, MessageType.ERROR, this.#context.logLevel); + reject(data); + } + } }); - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + this.#containerId, () => { - bus - // .target(properties.IFRAME_SECURE_ORIGIN) - .emit( - ELEMENT_EVENTS_TO_IFRAME.COLLECT_CALL_REQUESTS + this.#metaData.uuid, - { - type: COLLECT_TYPES.COLLECT, - ...options, - tokens: options?.tokens !== undefined ? options.tokens : true, - elementIds, - containerId: this.#containerId, - }, - (data: any) => { - if (!data || data?.error) { - printLog(`${JSON.stringify(data?.error)}`, MessageType.ERROR, this.#context.logLevel); - reject(data?.error); - } else { - printLog(parameterizedString(logs.infoLogs.COLLECT_SUBMIT_SUCCESS, CLASS_NAME), - MessageType.LOG, - this.#context.logLevel); - resolve(data); - } - }, - ); - }); - printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, - CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.TOKENIZATION_REQUEST), - MessageType.LOG, this.#context.logLevel); - } catch (err:any) { + }).catch((err:any) => { printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); reject(err); - } - }); - }; + }); + } catch (err:any) { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); #updateListeners = () => { this.#eventEmitter.on(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_UPDATE_OPTIONS, (data) => { diff --git a/src/core/external/collect/compose-collect-element.ts b/src/core/external/collect/compose-collect-element.ts index b3e7b7c3..e0696be3 100644 --- a/src/core/external/collect/compose-collect-element.ts +++ b/src/core/external/collect/compose-collect-element.ts @@ -1,10 +1,15 @@ +import { Context } from 'vm'; import EventEmitter from '../../../event-emitter'; import { formatValidations } from '../../../libs/element-options'; import SkyflowError from '../../../libs/skyflow-error'; import { ContainerType } from '../../../skyflow'; -import { CollectElementUpdateOptions, EventName } from '../../../utils/common'; +import { + CollectElementUpdateOptions, EventName, MessageType, MetaData, +} from '../../../utils/common'; import SKYFLOW_ERROR_CODE from '../../../utils/constants'; import { ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ElementType } from '../../constants'; +import { printLog } from '../../../utils/logs-helper'; +import logs from '../../../utils/logs'; class ComposableElement { #elementName: string; @@ -19,7 +24,13 @@ class ComposableElement { #isUpdateCalled = false; - constructor(name: string, eventEmitter: EventEmitter, iframeName: string) { + #metaData: any; + + #context: Context; + + #elementType: ElementType; + + constructor(name, eventEmitter, iframeName, metaData) { this.#elementName = name; this.#iframeName = iframeName; this.#eventEmitter = eventEmitter; @@ -27,6 +38,12 @@ class ComposableElement { this.#eventEmitter.on(`${EventName.READY}:${this.#elementName}`, () => { this.#isMounted = true; }); + this.#metaData = metaData; + this.#context = { + logLevel: this.#metaData.clientJSON?.config?.options?.logLevel, + env: this.#metaData.clientJSON?.config?.options?.env, + }; + this.#elementType = this.#metaData?.type as ElementType; } on(eventName: string, handler: Function) { @@ -98,6 +115,40 @@ class ComposableElement { }); } }; -} + uploadMultipleFiles = (metaData?: MetaData) => new Promise((resolve, reject) => { + try { + if (this.#elementType !== ElementType.MULTI_FILE_INPUT) { + throw new SkyflowError( + SKYFLOW_ERROR_CODE.MULTI_FILE_NOT_SUPPORTED, + [], + true, + ); + } + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter._emit(`${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:${this.#elementName}`, { + options: metaData, + }, (response: any) => { + if (response.error) { + reject(response); + } + }); + window.addEventListener('message', (event) => { + if (event?.data?.type === `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${this.#elementName}`) { + if (event?.data?.data?.errorResponse || event?.data?.data?.error) { + printLog(`${event?.data?.data.errorResponse || event?.data?.data.error}`, MessageType.ERROR, this.#context.logLevel); + reject(event?.data?.data); + } else { + printLog(logs.infoLogs.MULTI_UPLOAD_FILES_SUCCESS, + MessageType.LOG, this.#context.logLevel); + resolve(event?.data?.data); + } + } + }); + } catch (error) { + printLog(`${error}`, MessageType.ERROR, this.#context.logLevel); + reject(error); + } + }); +} export default ComposableElement; diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts new file mode 100644 index 00000000..2d829848 --- /dev/null +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -0,0 +1,474 @@ +/* eslint-disable no-plusplus */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* +Copyright (c) 2023 Skyflow, Inc. +*/ +import bus from 'framebus'; +import sum from 'lodash/sum'; +import EventEmitter from '../../../event-emitter'; +import iframer, { setAttributes, getIframeSrc, setStyles } from '../../../iframe-libs/iframer'; +import deepClone from '../../../libs/deep-clone'; +import SkyflowError from '../../../libs/skyflow-error'; +import uuid from '../../../libs/uuid'; +import properties from '../../../properties'; +import { ContainerType } from '../../../skyflow'; +import { + Context, MessageType, +} from '../../../utils/common'; +import SKYFLOW_ERROR_CODE from '../../../utils/constants'; +import logs from '../../../utils/logs'; +import { printLog, parameterizedString } from '../../../utils/logs-helper'; +import { + validateInitConfig, + validateInputFormatOptions, + validateRevealElementRecords, +} from '../../../utils/validators'; +import { + COLLECT_FRAME_CONTROLLER, + CONTROLLER_STYLES, ELEMENT_EVENTS_TO_IFRAME, + FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, + COMPOSABLE_REVEAL, + REVEAL_TYPES, +} from '../../constants'; +import Container from '../common/container'; + +import ComposableRevealElement from './composable-reveal-element'; +import { RevealElementInput, RevealResponse } from '../../../index-node'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; +import ComposableRevealInternalElement from './composable-reveal-internal'; +import { formatRevealElementOptions } from '../../../utils/helpers'; + +const CLASS_NAME = 'ComposableRevealContainer'; +class ComposableRevealContainer extends Container { + #containerId: string; + + #elements: Record = {}; + + #metaData: any; + + #elementGroup: any = { rows: [] }; + + #elementsList:any = []; + + #context:Context; + + #skyflowElements:any; + + #eventEmitter: EventEmitter; + + #isMounted: boolean = false; + + #options: any; + + #containerElement:any; + + type:string = ContainerType.COMPOSE_REVEAL; + + #containerMounted: boolean = false; + + #tempElements: any = {}; + + #clientDomain: string = ''; + + #isComposableFrameReady: boolean = false; + + #shadowRoot: ShadowRoot | null = null; + + #iframeID: string = ''; + + #revealRecords: IRevealElementInput[] = []; + + #getSkyflowBearerToken: () => Promise | undefined; + + constructor(options, metaData, skyflowElements, context) { + super(); + this.#containerId = uuid(); + this.#metaData = { + ...metaData, + clientJSON: { + ...metaData.clientJSON, + config: { + ...metaData.clientJSON.config, + options: { + ...metaData.clientJSON.config?.options, + ...options, + }, + }, + }, + }; + this.#getSkyflowBearerToken = metaData.getSkyflowBearerToken; + this.#skyflowElements = skyflowElements; + this.#context = context; + this.#options = options; + this.#eventEmitter = new EventEmitter(); + + this.#clientDomain = this.#metaData.clientDomain || ''; + const iframe = iframer({ + name: `${COLLECT_FRAME_CONTROLLER}:${this.#containerId}:${this.#context.logLevel}:${btoa(this.#clientDomain)}`, + referrer: this.#clientDomain, + }); + setAttributes(iframe, { + src: getIframeSrc(), + }); + setStyles(iframe, { ...CONTROLLER_STYLES }); + printLog(parameterizedString(logs.infoLogs.CREATE_COLLECT_CONTAINER, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#containerMounted = true; + bus + // .target(properties.IFRAME_SECURE_ORIGIN) + .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) { + this.#isComposableFrameReady = true; + } + }); + } + + create = (input: RevealElementInput, options?: IRevealElementOptions) => { + const elementId = uuid(); + validateInputFormatOptions(options); + + const elementName = `${COMPOSABLE_REVEAL}:${btoa(elementId)}`; + this.#elementsList?.push({ + name: elementName, + ...input, + elementName, + elementId, + ...formatRevealElementOptions(options ?? {}), + }); + const controllerIframeName = `${FRAME_ELEMENT}:group:${btoa(this.#tempElements ?? {})}:${this.#containerId}:${this.#context?.logLevel}:${btoa(this.#clientDomain ?? '')}`; + return new ComposableRevealElement(elementName, + this.#eventEmitter, + controllerIframeName); + }; + + #createMultipleElement = ( + multipleElements: any, + isSingleElementAPI: boolean = false, + ) => { + try { + const elements: any[] = []; + this.#tempElements = deepClone(multipleElements); + this.#tempElements?.rows?.forEach((row) => { + row?.elements?.forEach((element) => { + const options = element ?? {}; + const { elementType } = options; + options.isMounted = false; + + options.label = element?.label; + options.skyflowID = element?.skyflowID; + + elements.push(options); + }); + }); + + this.#tempElements.elementName = isSingleElementAPI + ? elements[0].elementName + : `${FRAME_ELEMENT}:group:${btoa(this.#tempElements)}`; + if ( + isSingleElementAPI + && !this.#elements[elements[0].elementName] + && this.#hasElementName(elements[0].name) + ) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.UNIQUE_ELEMENT_NAME, [`${elements[0].name}`], true); + } + + let element = this.#elements[this.#tempElements.elementName]; + if (element) { + if (isSingleElementAPI) { + element.update(elements[0]); + } else { + element.update(this.#tempElements); + } + } else { + const elementId = uuid(); + try { + element = new ComposableRevealInternalElement( + elementId, + this.#tempElements, + this.#metaData, + { + containerId: this.#containerId, + isMounted: this.#containerMounted, + type: this.type, + eventEmitter: this.#eventEmitter, + }, + true, + this.#context, + ); + this.#elements[this.#tempElements.elementName] = element; + this.#skyflowElements[elementId] = element; + } catch (error: any) { + printLog(logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT, + MessageType.ERROR, + this.#context.logLevel); + throw error; + } + } + this.#iframeID = element.iframeName(); + return element; + } catch (error: any) { + printLog(logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT, + MessageType.ERROR, + this.#context.logLevel); + throw error; + } + }; + + #hasElementName = (name: string) => { + const tempElements = Object.keys(this.#elements); + for (let i = 0; i < tempElements.length; i += 1) { + if (atob(tempElements[i].split(':')[2]) === name) { + return true; + } + } + return false; + }; + + mount = (domElement: HTMLElement | string) => { + if (!domElement) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, + ['RevealElement'], true); + } + + const { layout } = this.#options; + if (sum(layout) !== this.#elementsList.length) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.MISMATCH_ELEMENT_COUNT_LAYOUT_SUM, [], true); + } + let count = 0; + layout.forEach((rowCount, index) => { + this.#elementGroup.rows = [ + ...this.#elementGroup.rows, + { elements: [] }, + ]; + for (let i = 0; i < rowCount; i++) { + this.#elementGroup.rows[index].elements.push( + this.#elementsList[count], + ); + count++; + } + }); + if (this.#options.styles) { + this.#elementGroup.styles = { + ...this.#options.styles, + }; + } + if (this.#options.errorTextStyles) { + this.#elementGroup.errorTextStyles = { + ...this.#options.errorTextStyles, + }; + } + if (this.#containerMounted) { + this.#containerElement = this.#createMultipleElement(this.#elementGroup, false); + this.#containerElement.mount(domElement); + this.#isMounted = true; + } + if (domElement instanceof HTMLElement + && (domElement as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElement.getRootNode() as ShadowRoot; + } else if (typeof domElement === 'string') { + const element = document.getElementById(domElement); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + if (this.#shadowRoot !== null) { + this.#eventEmitter.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, (data) => { + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + data.iframeName, {}); + }); + this.#emitEvent(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframeID, {}); + } + }; + + unmount = () => { + this.#containerElement.unmount(); + }; + + #emitEvent = (eventName: string, options?: Record, callback?: any) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } else { + const iframe = document.getElementById(this.#iframeID) as HTMLIFrameElement; + if (iframe?.contentWindow) { + iframe.contentWindow.postMessage({ + name: eventName, + ...options, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }; + + reveal(): Promise { + this.#revealRecords = []; + if (this.#isComposableFrameReady) { + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + printLog(parameterizedString(logs.infoLogs.VALIDATE_REVEAL_RECORDS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#elementsList.forEach((currentElement) => { + // if (currentElement.isClientSetError()) { + // throw new SkyflowError(SKYFLOW_ERROR_CODE.REVEAL_ELEMENT_ERROR_STATE); + // } + if (!currentElement.skyflowID) { + this.#revealRecords.push(currentElement); + } + }); + validateRevealElementRecords(this.#revealRecords); + const elementIds:{ frameId:string, token:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: element.name, + token: element.token, + }); + }); + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + this.#containerId, + { + data: { + type: REVEAL_TYPES.REVEAL, + containerId: this.#containerId, + elementIds, + }, + clientConfig: { + vaultURL: this.#metaData?.clientJSON?.config?.vaultURL, + vaultID: this.#metaData?.clientJSON?.config?.vaultID, + authToken, + }, + context: this.#context, + }, + ); + + window?.addEventListener('message', (event) => { + if (event?.data?.type + === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.#containerId) { + const revealData = event?.data?.data; + if (revealData?.errors) { + printLog( + parameterizedString(logs?.errorLogs?.FAILED_REVEAL), + MessageType.ERROR, + this.#context?.logLevel, + ); + reject(revealData); + } else { + printLog( + parameterizedString(logs?.infoLogs?.REVEAL_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context?.logLevel, + ); + resolve(revealData); + } + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); + } + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + if (!this.#elementsList || this.#elementsList.length === 0) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.NO_ELEMENTS_IN_COMPOSABLE, [], true); + } + printLog(parameterizedString(logs.infoLogs.VALIDATE_REVEAL_RECORDS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#elementsList.forEach((currentElement) => { + // if (currentElement.isClientSetError()) { + // throw new SkyflowError(SKYFLOW_ERROR_CODE.REVEAL_ELEMENT_ERROR_STATE); + // } + if (!currentElement.skyflowID) { + this.#revealRecords.push(currentElement); + } + }); + validateRevealElementRecords(this.#revealRecords); + const elementIds:{ frameId:string, token:string }[] = []; + this.#elementsList.forEach((element) => { + elementIds.push({ + frameId: element.name, + token: element.token, + }); + }); + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + window.addEventListener('message', (messagEevent) => { + if (messagEevent.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + + this.#containerId) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + this.#containerId, { + data: { + type: REVEAL_TYPES.REVEAL, + containerId: this.#containerId, + elementIds, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + context: this.#context, + }, + ); + window.addEventListener('message', (event) => { + if (event.data.type + === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.#containerId) { + const revealData = event.data.data; + if (revealData.errors) { + printLog(parameterizedString(logs.errorLogs.FAILED_REVEAL), + MessageType.ERROR, this.#context.logLevel); + reject(revealData); + } else { + printLog(parameterizedString(logs.infoLogs.REVEAL_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + resolve(revealData); + } + } + }); + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + } + }); + } +} +export default ComposableRevealContainer; diff --git a/src/core/external/reveal/composable-reveal-element.ts b/src/core/external/reveal/composable-reveal-element.ts new file mode 100644 index 00000000..0f3f328d --- /dev/null +++ b/src/core/external/reveal/composable-reveal-element.ts @@ -0,0 +1,66 @@ +import EventEmitter from '../../../event-emitter'; +import { ContainerType } from '../../../skyflow'; +import { EventName, RenderFileResponse } from '../../../utils/common'; +import { ELEMENT_EVENTS_TO_IFRAME, REVEAL_ELEMENT_OPTIONS_TYPES } from '../../constants'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; + +class ComposableRevealElement { + #elementName: string; + + #eventEmitter: EventEmitter; + + #iframeName: string; + + type: string = ContainerType?.COMPOSABLE ?? 'COMPOSABLE'; + + #isMounted: boolean = false; + + constructor(name, eventEmitter, iframeName) { + this.#elementName = name ?? ''; + this.#iframeName = iframeName ?? ''; + this.#eventEmitter = eventEmitter; + this.#eventEmitter?.on?.(`${EventName?.READY ?? 'READY'}:${this.#elementName}`, () => { + this.#isMounted = true; + }); + } + + iframeName(): string { + return this.#iframeName ?? ''; + } + + getID(): string { + return this.#elementName ?? ''; + } + + renderFile(): Promise { + return new Promise((resolve, reject) => { + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter?._emit?.( + `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST ?? ''}:${this.#elementName}`, + {}, + (response) => { + if (response?.errors) { + reject(response); + } else if (response?.error) { + reject({ errors: response?.error }); + } else { + resolve(response); + } + }, + ); + }); + } + + update = (options: IRevealElementInput | IRevealElementOptions) => { + // eslint-disable-next-line no-underscore-dangle + this.#eventEmitter?._emit?.( + `${ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS}:${this.#elementName}`, + { + options: options as IRevealElementInput | IRevealElementOptions, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + }, + ); + }; +} + +export default ComposableRevealElement; diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts new file mode 100644 index 00000000..a5584755 --- /dev/null +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -0,0 +1,610 @@ +/* +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 SKYFLOW_ERROR_CODE from '../../../utils/constants'; +import { + ELEMENT_EVENTS_TO_IFRAME, + ELEMENT_EVENTS_TO_CONTAINER, + REVEAL_ELEMENT_OPTIONS_TYPES, + METRIC_TYPES, + ELEMENT_EVENTS_TO_CLIENT, + EVENT_TYPES, + REVEAL_TYPES, + COMPOSABLE_REVEAL, +} from '../../constants'; +import IFrame from '../common/iframe'; +import SkyflowElement from '../common/skyflow-element'; +import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; +import { + pushElementEventWithTimeout, + updateMetricObjectValue, +} from '../../../metrics'; +import logs from '../../../utils/logs'; +import { parameterizedString, printLog } from '../../../utils/logs-helper'; +import properties from '../../../properties'; +import { validateInitConfig, validateRenderElementRecord } from '../../../utils/validators'; +import EventEmitter from '../../../event-emitter'; +import { formatRevealElementOptions } from '../../../utils/helpers'; + +const CLASS_NAME = 'RevealElementInteranalElement'; + +export interface RevealComposableGroup{ + record: IRevealElementInput + options: IRevealElementOptions +} + +class ComposableRevealInternalElement extends SkyflowElement { + #iframe: IFrame; + + #metaData: any; + + #recordData: any; + + #containerId: any; + + #isMounted:boolean = false; + + #isClientSetError:boolean = false; + + #context: Context; + + #elementId: string; + + #readyToMount: boolean = false; + + #eventEmitter: EventEmitter; + + #isFrameReady: boolean; + + #domSelecter: string; + + #clientId: string; + + #isSkyflowFrameReady: boolean = false; + + #isSingleElementAPI: boolean; + + #shadowRoot: ShadowRoot | null = null; + + #getSkyflowBearerToken: () => Promise | undefined; + + #composableIframeName!: string; + + #isComposableFrameReady: boolean = false; + + constructor(elementId: string, + recordGroup: RevealComposableGroup[], + metaData: any, container: any, isSingleElementAPI: boolean = false, + context: Context) { + super(); + this.#elementId = elementId; + this.#metaData = metaData; + this.#clientId = this.#metaData?.uuid; + this.#isSingleElementAPI = isSingleElementAPI; + this.#recordData = recordGroup; + this.#containerId = container?.containerId; + this.#readyToMount = container?.isMounted ?? true; + this.#eventEmitter = container?.eventEmitter; + this.#context = context; + + this.#iframe = new IFrame( + `${COMPOSABLE_REVEAL}:${btoa(uuid())}`, + metaData, + this.#containerId, + this.#context?.logLevel, + ); + + this.#domSelecter = ''; + this.#isFrameReady = false; + this.#readyToMount = true; + this.#getSkyflowBearerToken = metaData?.getSkyflowBearerToken; + this.#isSkyflowFrameReady = metaData?.skyflowContainer?.isControllerFrameReady ?? false; + + bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe?.name, (data) => { + this.#iframe?.setIframeHeight(data?.height); + }); + + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#isComposableFrameReady = true; + } + }); + + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + this.#iframe?.name) { + this.#iframe?.setIframeHeight(event?.data?.data?.height); + } + }); + + // eslint-disable-next-line max-len + if (this.#recordData?.rows) { + this.setupRenderFileEventListener(this.getRecordData()?.rows); + } + } + + private setupRenderFileEventListener(rows: any[]): void { + if (!rows?.length) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REVEAL_COMPOSABLE_INPUT, ['COMPOSABLE_REVEAL'], true); + } + + try { + rows?.forEach((row, rowIndex) => { + row?.elements?.forEach((element: any, elementIndex: number) => { + if (!element?.name) return; + this.#eventEmitter?.on( + `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST}:${element?.name}`, + (data, callback) => { + this.renderFile(element)?.then((response) => { + callback?.(response); + })?.catch((error) => { + callback?.({ error }); + }); + }, + ); + this.#eventEmitter?.on( + `${ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS}:${element?.name}`, + (data) => { + if (data.updateType === REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS) { + // make this change in original elememt that is inside rows + const updatedElement = { + ...element, + ...data.options, + ...formatRevealElementOptions(data.options as IRevealElementOptions), + }; + + // Update element in this.#recordData.rows structure + if (this.#recordData?.rows?.[rowIndex]?.elements?.[elementIndex]) { + this.#recordData.rows[rowIndex].elements[elementIndex] = updatedElement; + } + + // Update local element reference + element = updatedElement; + + // Call update method + this.update(data.options, element); + } + }, + ); + }); + }); + } catch (error) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REVEAL_COMPOSABLE_INPUT, ['COMPOSABLE_REVEAL'], true); + } + } + + getID() { + return this.#elementId; + } + + mount(domElementSelector: HTMLElement | string) { + if (!domElementSelector) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['RevealElement'], true); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElementSelector); + if ( + this.#metaData?.clientJSON?.config?.options?.trackMetrics + && this.#metaData.clientJSON.config?.options?.trackingKey + ) { + pushElementEventWithTimeout(this.#elementId); + } + + this.#readyToMount = true; + if (this.#readyToMount) { + this.#iframe.mount(domElementSelector, undefined, { + record: JSON.stringify({ + ...this.#metaData, + record: this.#recordData, + context: this.#context, + containerId: this.#containerId, + }), + }); + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + if (this.#recordData.skyflowID) { + bus + // .target(location.origin) + .emit( + ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED + this.#containerId, + { + skyflowID: this.#recordData.skyflowID, + containerId: this.#containerId, + }, + ); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_END_TIME, Date.now()); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.MOUNTED); + } else { + bus + // .target(location.origin) + .emit( + ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED + this.#containerId, + { + id: this.#recordData.token, + containerId: this.#containerId, + }, + ); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_END_TIME, Date.now()); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.MOUNTED); + } + if (Object.prototype.hasOwnProperty.call(this.#recordData, 'skyflowID')) { + bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + } + }); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.EVENTS_KEY, EVENT_TYPES.READY); + updateMetricObjectValue(this.#elementId, METRIC_TYPES.MOUNT_START_TIME, Date.now()); + } + if (domElementSelector instanceof HTMLElement + && (domElementSelector as HTMLElement).getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = domElementSelector.getRootNode() as ShadowRoot; + } else if (typeof domElementSelector === 'string') { + const element = document.getElementById(domElementSelector); + if (element && element.getRootNode() instanceof ShadowRoot) { + this.#shadowRoot = element.getRootNode() as ShadowRoot; + } + } + } + + #emitEvent = (eventName: string, options?: Record) => { + if (this.#shadowRoot) { + const iframe = this.#shadowRoot?.getElementById(this.#iframe?.name) as HTMLIFrameElement; + iframe?.contentWindow?.postMessage({ + name: eventName, + ...options, + }, properties?.IFRAME_SECURE_ORIGIN); + } else { + const iframe = document?.getElementById(this.#iframe?.name) as HTMLIFrameElement; + iframe?.contentWindow?.postMessage({ + name: eventName, + ...options, + }, properties?.IFRAME_SECURE_ORIGIN); + } + }; + + renderFile(recordData): Promise { + let altText = ''; + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + altText = recordData.altText; + } + this.setAltText('loading...'); + const loglevel = this.#context.logLevel; + if (this.#isComposableFrameReady) { + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + printLog(parameterizedString(logs.infoLogs.VALIDATE_RENDER_RECORDS, CLASS_NAME), + MessageType.LOG, + loglevel); + validateRenderElementRecord(recordData); + + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + recordData.name, + { + data: { + type: REVEAL_TYPES.RENDER_FILE, + containerId: this.#containerId, + iframeName: recordData.name, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }, + ); + window?.addEventListener('message', (event) => { + if (event?.data && event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + + recordData.name) { + if (event?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { + const revealData = event?.data?.data?.result; + if (revealData?.error || revealData?.errors) { + printLog(parameterizedString( + logs.errorLogs.FAILED_RENDER, + ), MessageType.ERROR, + this.#context.logLevel); + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + this.setAltText(altText); + } + reject(revealData?.error || revealData?.errors); + } else { + printLog(parameterizedString(logs.infoLogs.RENDER_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + printLog(parameterizedString(logs.infoLogs.FILE_RENDERED, + CLASS_NAME, recordData.skyflowID), + MessageType.LOG, this.#context.logLevel); + resolve(revealData); + } + } + } + }); + }).catch((err:any) => { + printLog(`${err.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST), + MessageType.LOG, loglevel); + } catch (err: any) { + printLog(`Error: ${err.message}`, MessageType.ERROR, + loglevel); + reject(err); + } + }); + } + return new Promise((resolve, reject) => { + try { + validateInitConfig(this.#metaData.clientJSON.config); + printLog(parameterizedString(logs.infoLogs.VALIDATE_RENDER_RECORDS, CLASS_NAME), + MessageType.LOG, + loglevel); + validateRenderElementRecord(recordData); + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#isMounted = true; + this.#getSkyflowBearerToken()?.then((authToken) => { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS + recordData.name, + { + data: { + type: REVEAL_TYPES.RENDER_FILE, + containerId: this.#containerId, + iframeName: recordData.name, + }, + clientConfig: { + vaultURL: this.#metaData.clientJSON.config.vaultURL, + vaultID: this.#metaData.clientJSON.config.vaultID, + authToken, + }, + }, + ); + window.addEventListener('message', (event1) => { + if (event1?.data + && event1?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + + this.#iframe.name) { + if (event1?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { + const revealData = event1?.data?.data?.result; + if (revealData?.error) { + printLog(parameterizedString( + logs.errorLogs.FAILED_RENDER, + ), MessageType.ERROR, + this.#context.logLevel); + if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { + this.setAltText(altText); + } + reject(revealData); + } else { + // eslint-disable-next-line max-len + printLog(parameterizedString(logs.infoLogs.RENDER_SUBMIT_SUCCESS, CLASS_NAME), + MessageType.LOG, + this.#context.logLevel); + printLog(parameterizedString(logs.infoLogs.FILE_RENDERED, + CLASS_NAME, recordData.skyflowID), + MessageType.LOG, this.#context.logLevel); + resolve(revealData); + } + } + } + }); + }).catch((err:any) => { + printLog(`${err?.message}`, MessageType.ERROR, this.#context.logLevel); + reject(err); + }); + } + }); + printLog(parameterizedString(logs.infoLogs.EMIT_EVENT, + CLASS_NAME, ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST), + MessageType.LOG, loglevel); + } catch (err: any) { + printLog(`Error: ${err?.message}`, MessageType.ERROR, + loglevel); + reject(err); + } + }); + } + + iframeName(): string { + return this.#iframe.name; + } + + isMounted():boolean { + return this.#isMounted; + } + + hasToken():boolean { + if (this.#recordData.token) return true; + return false; + } + + isClientSetError():boolean { + return this.#isClientSetError; + } + + getRecordData() { + return this.#recordData; + } + + setErrorOverride(clientErrorText: string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + }); + } + this.#isClientSetError = true; + } + + setError(clientErrorText:string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: true, + clientErrorText, + }); + }); + } + this.#isClientSetError = true; + } + + resetError() { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: false, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { + name: this.#iframe.name, + isTriggerError: false, + }); + }); + } + this.#isClientSetError = false; + } + + setAltText(altText:string) { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }); + }); + } + } + + clearAltText() { + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: null, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: null, + }); + }); + } + } + + setToken(token:string) { + this.#recordData = { + ...this.#recordData, + token, + }; + if (this.#isMounted) { + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, + updatedValue: token, + }); + } else { + bus + .target(properties.IFRAME_SECURE_ORIGIN) + .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { + this.#isMounted = true; + bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { + name: this.#iframe.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, + updatedValue: token, + }); + }); + } + } + + unmount() { + if (this.#recordData.skyflowID) { + this.#isMounted = false; + this.#iframe.container?.remove(); + } + this.#isMounted = false; + this.#iframe.unmount(); + } + + update(options: IRevealElementInput | IRevealElementOptions, record) { + if (this.#isComposableFrameReady) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue: options, + }, + ); + } else { + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + this.#containerId) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + updatedValue: options, + }, + ); + } + }); + } + } +} + +export default ComposableRevealInternalElement; diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts new file mode 100644 index 00000000..51210e5a --- /dev/null +++ b/src/core/internal/composable-frame-element-init.ts @@ -0,0 +1,369 @@ +import injectStylesheet from 'inject-stylesheet'; +import bus from 'framebus'; +import { getValueAndItsUnit } from '../../libs/element-options'; +import { getFlexGridStyles } from '../../libs/styles'; +import { ContainerType } from '../../skyflow'; +import { + Context, IRevealRecordComposable, +} from '../../utils/common'; +import { + getContainerType, +} from '../../utils/helpers'; +import { + ALLOWED_MULTIPLE_FIELDS_STYLES, + ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ERROR_TEXT_STYLES, REVEAL_TYPES, STYLE_TYPE, +} from '../constants'; +import IFrameFormElement from './iframe-form'; +import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-styles'; +import FrameElement from '.'; +import Client from '../../client'; +import RevealFrame from './reveal/reveal-frame'; +import { + fetchRecordsByTokenIdComposable, formatRecordsForClientComposable, +} from '../../core-utils/reveal'; + +export default class RevealComposableFrameElementInit { + iframeFormElement: IFrameFormElement | undefined; + + clientMetaData: any; + + #domForm: HTMLFormElement; + + frameElement!: FrameElement; + + private static frameEle?: any; + + containerId: string; + + group: any; + + frameList: FrameElement[] = []; + + iframeFormList: IFrameFormElement[] = []; + + #client!: Client; + + #context!: Context; + + revealFrameList: any[] = []; + + rootDiv: HTMLDivElement; + + constructor() { + this.containerId = ''; + this.#domForm = document?.createElement('form'); + this.#domForm.action = '#'; + this.#domForm.onsubmit = (event) => { + event?.preventDefault(); + }; + + this.rootDiv = document?.createElement('div'); + this.updateGroupData(); + this.createContainerDiv(this.group); + + window?.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + + this.containerId && event?.data?.data?.type === REVEAL_TYPES.REVEAL) { + this.#context = event?.data?.context; + const data = event?.data?.data ?? {}; + const elementIds = data?.elementIds ?? []; + const revealDataInput: IRevealRecordComposable[] = []; + this.#client = new Client(event?.data?.clientConfig ?? {}, {}); + + elementIds?.forEach((element) => { + this.revealFrameList?.forEach((revealFrame) => { + const data2 = revealFrame?.getData?.(); + if (data2?.name === element?.frameId) { + if (data2 && !data2?.skyflowID) { + const revealRecord: IRevealRecordComposable = { + token: data2?.token ?? '', + redaction: data2?.redaction, + iframeName: data2?.name ?? '', + }; + revealDataInput?.push(revealRecord); + } + } + }); + }); + + this.revealData(revealDataInput, this.containerId, event?.data?.clientConfig?.authToken) + ?.then((revealResponse: any) => { + if (revealResponse?.records?.length > 0) { + const formattedRecord = formatRecordsForClientComposable(revealResponse); + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.containerId, + data: formattedRecord, + }, + this.clientMetaData?.clientDomain, + ); + + revealResponse?.records?.forEach((record: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === record?.frameId) { + revealFrame?.responseUpdate?.(record); + } + }); + }); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + }) + ?.catch((error) => { + const formattedRecord = formatRecordsForClientComposable(error); + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.containerId, + data: formattedRecord, + }, + this.clientMetaData?.clientDomain, + ); + + error?.records?.forEach((record: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === record?.frameId) { + revealFrame?.responseUpdate?.(record); + } + }); + }); + + error?.errors?.forEach((error1: any) => { + this.revealFrameList?.forEach((revealFrame) => { + if (revealFrame?.getData()?.name === error1?.frameId) { + revealFrame?.responseUpdate?.(error1); + } + }); + }); + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + }); + } + }); + + bus?.emit(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId, {}, (data: any) => { + this.#context = data?.context; + if (data?.client?.config) { + data.client.config = { + ...data?.client?.config, + }; + } + this.#client = Client?.fromJSON?.(data?.client); + }); + } + + updateGroupData = () => { + const url = window?.location?.href ?? ''; + const configIndex = url?.indexOf('?') ?? -1; + const encodedString = configIndex !== -1 ? decodeURIComponent(url?.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; + this.clientMetaData = parsedRecord?.clientJSON?.metaData; + this.group = parsedRecord?.record; + this.containerId = parsedRecord?.containerId ?? ''; + this.#context = parsedRecord?.context; + }; + + static startFrameElement = () => { + RevealComposableFrameElementInit.frameEle = new RevealComposableFrameElementInit(); + }; + + revealData(revealRecords: IRevealRecordComposable[], containerId, authToken) { + return new Promise((resolve, reject) => { + fetchRecordsByTokenIdComposable(revealRecords, this.#client, authToken)?.then( + (resolvedResult) => { + resolve(resolvedResult); + }, + (rejectedResult) => { + reject(rejectedResult); + }, + ); + }); + } + + createContainerDiv = (newGroup) => { + this.group = newGroup; + const { + rows = [], + styles, + errorTextStyles, + } = this.group ?? {}; + + const isComposableContainer = getContainerType(window?.name) === ContainerType?.COMPOSABLE; + this.group.spacing = getValueAndItsUnit(this.group?.spacing)?.join('') ?? ''; + this.rootDiv = document?.createElement('div'); + this.rootDiv.className = 'container'; + + const containerStylesByClassName = getFlexGridStyles({ + 'align-items': this.group?.alignItems ?? 'stretch', + 'justify-content': this.group?.justifyContent ?? 'flex-start', + spacing: this.group?.spacing, + }); + + injectStylesheet?.injectWithAllowlist( + { + [`.${this.rootDiv?.className}`]: containerStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + + let count = 0; + rows?.forEach((row, rowIndex) => { + row.spacing = getValueAndItsUnit(row?.spacing)?.join('') ?? ''; + const rowDiv = document?.createElement('div'); + rowDiv.id = `row-${rowIndex}`; + + const intialRowStyles = { + 'align-items': row?.alignItems ?? 'stretch', + 'justify-content': row?.justifyContent ?? 'flex-start', + spacing: row?.spacing, + padding: this.group?.spacing, + }; + + const rowStylesByClassName = getFlexGridStyles(intialRowStyles); + let errorTextElement; + + if (isComposableContainer) { + rowDiv.className = `${rowDiv?.id} SkyflowElement-${rowDiv?.id}-base`; + const rowStyles = { + [STYLE_TYPE?.BASE]: { + ...(styles?.[STYLE_TYPE?.BASE] ?? {}), + }, + }; + + getCssClassesFromJss?.(rowStyles, `${rowDiv?.id}`); + + errorTextElement = document?.createElement('span'); + errorTextElement.id = `${rowDiv?.id}-error`; + errorTextElement.className = 'SkyflowElement-row-error-base'; + + const errorStyles = { + [STYLE_TYPE?.BASE]: { + ...ERROR_TEXT_STYLES, + ...(errorTextStyles?.[STYLE_TYPE?.BASE] ?? {}), + }, + }; + + getCssClassesFromJss?.(errorStyles, 'row-error'); + if (errorTextStyles?.[STYLE_TYPE?.GLOBAL]) { + generateCssWithoutClass?.(errorTextStyles?.[STYLE_TYPE?.GLOBAL]); + } + } else { + rowDiv.className = `row-${rowIndex}`; + injectStylesheet?.injectWithAllowlist( + { + [`.${rowDiv?.className}`]: rowStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + } + + row?.elements?.forEach((element) => { + const elementDiv = document?.createElement('div'); + elementDiv.className = `element-${count}`; + elementDiv.id = `${rowDiv?.id}:element-${count}`; + count += 1; + + const elementStylesByClassName = { + padding: row?.spacing, + }; + + injectStylesheet?.injectWithAllowlist( + { + [`.${elementDiv?.className}`]: elementStylesByClassName, + }, + ALLOWED_MULTIPLE_FIELDS_STYLES, + ); + + const revealFrame = new RevealFrame(element, this.#context, + this.containerId, elementDiv); + this.revealFrameList?.push(revealFrame); + rowDiv?.append(elementDiv); + }); + + this.rootDiv?.append(rowDiv); + if (isComposableContainer) { + this.rootDiv?.append(errorTextElement); + } + }); + + if (this.#domForm) { + this.#domForm.innerHTML = ''; + document.body.innerHTML = ''; + this.#domForm?.append(this.rootDiv); + document?.body?.append(this.#domForm); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.containerId, + data: { + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + + bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window?.name, (data, callback) => { + callback?.({ + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }); + }); + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + + window?.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window?.name) { + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData?.clientDomain, + ); + } + if (event?.data?.type + === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name) { + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window?.name, + data: { + height: this.rootDiv?.scrollHeight ?? 0, + name: window?.name, + }, + }, + this.clientMetaData.clientDomain, + ); + } + }); + }; +} diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index d2c35e6b..13c89945 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -1,17 +1,35 @@ import injectStylesheet from 'inject-stylesheet'; import bus from 'framebus'; +import get from 'lodash/get'; import { getValueAndItsUnit, validateAndSetupGroupOptions } from '../../libs/element-options'; import { getFlexGridStyles } from '../../libs/styles'; import { ContainerType } from '../../skyflow'; -import { Context, Env, LogLevel } from '../../utils/common'; -import { getContainerType } from '../../utils/helpers'; +import { + Context, Env, LogLevel, + MessageType, +} from '../../utils/common'; +import { + fileValidation, generateUploadFileName, getContainerType, vaildateFileName, +} from '../../utils/helpers'; import { ALLOWED_MULTIPLE_FIELDS_STYLES, - ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ERROR_TEXT_STYLES, STYLE_TYPE, + COLLECT_TYPES, + ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_IFRAME, ELEMENTS, ERROR_TEXT_STYLES, STYLE_TYPE, } from '../constants'; import IFrameFormElement from './iframe-form'; import getCssClassesFromJss, { generateCssWithoutClass } from '../../libs/jss-styles'; import FrameElement from '.'; +import { + checkForElementMatchRule, checkForValueMatch, constructElementsInsertReq, + constructInsertRecordRequest, insertDataInCollect, + updateRecordsBySkyflowIDComposable, +} from '../../core-utils/collect'; +import SkyflowError from '../../libs/skyflow-error'; +import SKYFLOW_ERROR_CODE from '../../utils/constants'; +import Client from '../../client'; +import { printLog } from '../../utils/logs-helper'; + +const set = require('set-value'); export default class FrameElementInit { iframeFormElement: IFrameFormElement | undefined; @@ -30,9 +48,15 @@ export default class FrameElementInit { group: any; + frameList: FrameElement[] = []; + + iframeFormList: IFrameFormElement[] = []; + + #client!: Client; + constructor() { // this.createIframeElement(frameName, label, skyflowID, isRequired); - this.context = { logLevel: LogLevel.ERROR, env: Env.PROD }; // client level + this.context = { logLevel: LogLevel.INFO, env: Env.PROD }; // client level this.containerId = ''; this.#domForm = document.createElement('form'); this.#domForm.action = '#'; @@ -41,8 +65,571 @@ export default class FrameElementInit { }; this.updateGroupData(); this.createContainerDiv(this.group); + bus + // .target(this.clientMetaData.clientDomain) + .emit(ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId, {}, (data: any) => { + data.client.config = { + ...data.client.config, + }; + this.#client = Client.fromJSON(data.client) as any; + }); + + window.addEventListener('message', this.handleCollectCall); } + private handleCollectCall = (event: MessageEvent) => { + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + if (inputElement.fieldType + === 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) + ?.then((response: any) => { + window?.parent.postMessage({ + type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${inputElement.iFrameName}`, + data: response, + }, this.clientMetaData?.clientDomain); + }).catch((error) => { + window?.parent.postMessage({ + type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:${inputElement.iFrameName}`, + data: error, + }, this.clientMetaData?.clientDomain); + }); + } + } + } + }); + + // if (event.origin === this.clientMetaData.clientDomain) { + 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) + .then((response: any) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, + data: response, + }, this.clientMetaData.clientDomain); + }) + .catch((error) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, + data: error, + }, this.clientMetaData.clientDomain); + }); + } else if (event.data.data && event.data.data.type === COLLECT_TYPES.FILE_UPLOAD) { + this.parallelUploadFiles(event.data.data, event.data.clientConfig) + .then((response: any) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, + data: response, + }, this.clientMetaData.clientDomain); + }) + .catch((error) => { + window?.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, + data: error, + }, this.clientMetaData.clientDomain); + }); + } + } + if (event.data.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId) { + const data = event.data; + data.client.config = { + ...data.client.config, + }; + this.#client = Client.fromJSON(data.client) as any; + } + // } + }; + + private parallelUploadFiles = (options, config) => new Promise((rootResolve, rootReject) => { + const promises: Promise[] = []; + this.iframeFormList.forEach((inputElement) => { + let res: Promise; + if (inputElement) { + if ( + inputElement.fieldType + === ELEMENTS.FILE_INPUT.name + ) { + res = this.uploadFiles(inputElement, config); + promises.push(res); + } + } + }); + Promise.allSettled( + promises, + ).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response); + } + } + } else if (result.status === 'rejected') { + errorResponse.push(result.reason); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }); + + uploadFiles = (fileElement, clientConfig) => { + this.#client = new Client(clientConfig, {}); + if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); + const fileUploadObject: any = {}; + + const { + state, tableName, skyflowID, onFocusChange, preserveFileName, + } = fileElement; + + if (state.isRequired) { + onFocusChange(false); + } + try { + fileValidation(state.value, state.isRequired, fileElement); + } catch (err) { + return Promise.reject(err); + } + + const validatedFileState = fileValidation(state.value, state.isRequired, fileElement); + + if (!validatedFileState) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_TYPE, [], true)); + } + fileUploadObject[state.name] = state.value; + + const formData = new FormData(); + + const column = Object.keys(fileUploadObject)[0]; + + const value: Blob = Object.values(fileUploadObject)[0] as Blob; + + if (preserveFileName) { + const isValidFileName = vaildateFileName(state.value.name); + if (!isValidFileName) { + return Promise.reject( + new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true), + ); + } + formData.append(column, value); + } else { + const generatedFileName = generateUploadFileName(state.value.name); + formData.append(column, new File([value], generatedFileName, { type: state.value.type })); + } + + const client = this.#client; + const sendRequest = () => new Promise((rootResolve, rootReject) => { + client + .request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowID}/files`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }) + .then((response: any) => { + rootResolve(response); + }) + .catch((error) => { + rootReject(error); + }); + }); + + return new Promise((resolve, reject) => { + sendRequest() + .then((res) => resolve(res)) + .catch((err) => { + reject(err); + }); + }); + }; + + private tokenize = (options, clientConfig: any) => { + let errorMessage = ''; + const insertRequestObject: any = {}; + const updateRequestObject: any = {}; + + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + if (inputElement) { + if ( + inputElement.fieldType + !== ELEMENTS.FILE_INPUT.name && inputElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name + ) { + const { + // eslint-disable-next-line max-len + state, doesClientHasError, clientErrorText, errorText, onFocusChange, validations, + setValue, + } = inputElement; + if (state.isRequired || !state.isValid) { + onFocusChange(false); + } + if (validations + && checkForElementMatchRule(validations) + && checkForValueMatch(validations, inputElement)) { + setValue(state.value); + onFocusChange(false); + } + if (!state.isValid || !state.isComplete) { + if (doesClientHasError) { + errorMessage += `${state.name}:${clientErrorText}`; + } else { errorMessage += `${state.name}:${errorText} `; } + } + } + } + } + }); + + // return for error + if (errorMessage.length > 0) { + // eslint-disable-next-line max-len + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.COMPLETE_AND_VALID_INPUTS, [`${errorMessage}`], true)); + } + // eslint-disable-next-line consistent-return + this.iframeFormList.forEach((inputElement) => { + if (inputElement) { + const { + state, tableName, validations, skyflowID, + } = inputElement; + if (tableName) { + if ( + inputElement.fieldType + !== ELEMENTS.FILE_INPUT.name && inputElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name + ) { + if ( + inputElement.fieldType + === ELEMENTS.checkbox.name + ) { + if (insertRequestObject[state.name]) { + insertRequestObject[state.name] = `${insertRequestObject[state.name]},${state.value + }`; + } else { + insertRequestObject[state.name] = state.value; + } + } else if (insertRequestObject[tableName] && !(skyflowID === '') && skyflowID === undefined) { + if (get(insertRequestObject[tableName], state.name) + && !(validations && checkForElementMatchRule(validations))) { + return Promise.reject(new SkyflowError(SKYFLOW_ERROR_CODE.DUPLICATE_ELEMENT, + [state.name, tableName], true)); + } + set( + insertRequestObject[tableName], + state.name, + inputElement.getUnformattedValue(), + ); + } else if (skyflowID || skyflowID === '') { + if (skyflowID === '' || skyflowID === null) { + return Promise.reject(new SkyflowError( + SKYFLOW_ERROR_CODE.EMPTY_SKYFLOW_ID_IN_ADDITIONAL_FIELDS, + )); + } + if (updateRequestObject[skyflowID]) { + set( + updateRequestObject[skyflowID], + state.name, + inputElement.getUnformattedValue(), + ); + } else { + updateRequestObject[skyflowID] = {}; + set( + updateRequestObject[skyflowID], + state.name, + inputElement.getUnformattedValue(), + ); + set( + updateRequestObject[skyflowID], + 'table', + tableName, + ); + } + } else { + insertRequestObject[tableName] = {}; + set( + insertRequestObject[tableName], + state.name, + inputElement.getUnformattedValue(), + ); + } + } + } + } + }); + let finalInsertRequest; + let finalInsertRecords; + let finalUpdateRecords; + try { + [finalInsertRecords, finalUpdateRecords] = constructElementsInsertReq( + insertRequestObject, updateRequestObject, options.options, + ); + finalInsertRequest = constructInsertRecordRequest(finalInsertRecords, options.options); + } catch (error:any) { + return Promise.reject({ + error: error?.message, + }); + } + this.#client = new Client(clientConfig, {}); + const client = this.#client; + const sendRequest = () => new Promise((rootResolve, rootReject) => { + const insertPromiseSet: Promise[] = []; + + // const clientId = client.toJSON()?.metaData?.uuid || ''; + // getAccessToken(clientId).then((authToken) => { + if (finalInsertRequest.length !== 0) { + insertPromiseSet.push( + insertDataInCollect(finalInsertRequest, + client, options, finalInsertRecords, clientConfig.authToken as string), + ); + } + if (finalUpdateRecords.updateRecords.length !== 0) { + insertPromiseSet.push( + updateRecordsBySkyflowIDComposable( + finalUpdateRecords, client, options, clientConfig.authToken as string, + ), + ); + } + if (insertPromiseSet.length !== 0) { + Promise.allSettled(insertPromiseSet).then((resultSet: any) => { + const recordsResponse: any[] = []; + const errorsResponse: any[] = []; + + resultSet.forEach((result: + { status: string; value: any; reason?: any; }) => { + if (result.status === 'fulfilled') { + if (result.value.records !== undefined && Array.isArray(result.value.records)) { + result.value.records.forEach((record) => { + recordsResponse.push(record); + }); + } + if (result.value.errors !== undefined && Array.isArray(result.value.errors)) { + result.value.errors.forEach((error) => { + errorsResponse.push(error); + }); + } + } else { + if (result.reason?.records !== undefined && Array.isArray(result.reason?.records)) { + result.reason.records.forEach((record) => { + recordsResponse.push(record); + }); + } + if (result.reason?.errors !== undefined && Array.isArray(result.reason?.errors)) { + result.reason.errors.forEach((error) => { + errorsResponse.push(error); + }); + } + } + }); + if (errorsResponse.length === 0) { + rootResolve({ records: recordsResponse }); + } else if (recordsResponse.length === 0) rootReject({ errors: errorsResponse }); + else rootReject({ records: recordsResponse, errors: errorsResponse }); + }); + } + // }).catch((err) => { + // rootReject({ + // error: err, + // }); + // }); + }); + + return new Promise((resolve, reject) => { + sendRequest() + .then((res) => resolve(res)) + .catch((err) => reject(err)); + }); + }; + + // eslint-disable-next-line consistent-return + private multipleUploadFiles = + (fileElement: IFrameFormElement, + clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + this.#client = new Client(clientConfig, {}); + if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); + const { + state, tableName, onFocusChange, preserveFileName, + } = fileElement; + if (state.isRequired) { + onFocusChange(false); + } + + if (fileElement.state.value === undefined || fileElement.state.value === null || fileElement.state.value === '') { + rootReject({ error: 'No files selected' }); + return; + } + const files = state.value instanceof FileList + ? Array.from(state.value) + : [state.value]; + + this.validateFiles(files, state, fileElement); + const insertRequest = this.createInsertRequest(files.length, metaData); + this.insertDataCallInMultiFiles( + insertRequest, this.#client, tableName as string, clientConfig.authToken as string, + ).then((response: any) => { + const skyflowIDs = this.extractSkyflowIDs(response); + if (skyflowIDs.length === 0) { + rootReject({ error: 'No skyflow IDs returned from insert data' }); + return; + } + const promises: Promise[] = []; + + files.forEach((file, index) => { + const fileUploadObject: any = {}; + fileUploadObject[state.name] = file; + const formData = new FormData(); + const column = Object.keys(fileUploadObject)[0]; + const value: Blob = Object.values(fileUploadObject)[0] as Blob; + if (preserveFileName) { + formData.append(column, value); + } else { + const generatedFileName = generateUploadFileName(file.name); + formData.append(column, new File([value], generatedFileName, { type: file.type })); + } + const client = this.#client; + const promise1 = new Promise((resolve, reject) => { + client + .request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowIDs[index]}/files`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }) + .then((response1) => { + resolve(response1); + }) + .catch((error) => { + reject(error); + }); + }); + promises.push(promise1); + }); + Promise.allSettled( + promises, + ).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response1 = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response1); + } + } + } else if (result.status === 'rejected') { + errorResponse.push({ error: result.reason }); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }).catch((error) => { + printLog(`${error}`, MessageType.LOG, this.context?.logLevel); + rootReject({ + error: error?.error || error, + }); + }); + }); + + private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => { + files.forEach((file) => { + // Check file validation + const validatedFileState = fileValidation(file, state.isRequired, fileElement); + if (!validatedFileState) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_TYPE, [], true); + } + + // Check filename validation + const isValidFileName = vaildateFileName(file.name); + if (!isValidFileName) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true); + } + }); + return true; + }; + + private createInsertRequest = (numberOfRequests: number, options = {}) => { + // Create basic request structure + const request = { + records: [] as Array<{ fields: Record }>, + tokenization: false, + }; + + // Add empty field objects based on number of requests + for (let i = 0; i < numberOfRequests; i += 1) { + request.records.push({ + fields: options === undefined ? {} : options, + }); + } + + return request; + }; + + private extractSkyflowIDs = (response: { records: Array<{ skyflow_id: string }> }): string[] => { + if (!response?.records || !Array.isArray(response.records)) { + return []; + } + + return response.records + .map((record) => record.skyflow_id) + .filter((id) => id !== undefined && id !== null); + }; + + private insertDataCallInMultiFiles = ( + insertRequest, + client: Client, + tableName: string, + authToken: string, + ) => new Promise((rootResolve, rootReject) => { + client + .request({ + body: { + ...insertRequest, + }, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}`, + headers: { + authorization: `Bearer ${authToken}`, + 'content-type': 'application/json', + }, + }) + .then((response: any) => { + // Extract skyflow IDs from response + const skyflowIDs = this.extractSkyflowIDs(response); + rootResolve({ + ...response, + skyflowIDs, // Add extracted IDs to response + }); + }) + .catch((error) => { + rootReject(error); + }); + }); + updateGroupData = () => { const frameName = window.name; const url = window.location?.href; @@ -56,7 +643,6 @@ export default class FrameElementInit { }; this.group = parsedRecord.record; this.containerId = parsedRecord.containerId; - bus .target(this.clientMetaData.clientDomain) .on(ELEMENT_EVENTS_TO_IFRAME.SET_VALUE + frameName, (data) => { @@ -73,6 +659,7 @@ export default class FrameElementInit { ...this.clientMetaData, isRequired, }, this.context, skyflowID); + this.iframeFormList.push(this.iframeFormElement); return this.iframeFormElement; }; @@ -184,11 +771,21 @@ export default class FrameElementInit { iFrameFormElement, element, elementDiv, + this.clientMetaData.clientDomain, ); + this.frameList.push(this.frameElement); + if (isComposableContainer && errorTextElement) { iFrameFormElement.on(ELEMENT_EVENTS_TO_CLIENT.BLUR, (state) => { errorTextMap[element.elementName] = state.error; this.#updateCombinedErrorText(errorTextElement.id, errorTextMap); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); }); } @@ -208,6 +805,24 @@ export default class FrameElementInit { bus.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name, (data, callback) => { callback({ height: rootDiv.scrollHeight, name: window.name }); }); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); + window.addEventListener('message', (event) => { + if (event.data.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name) { + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, + data: { height: rootDiv.scrollHeight, name: window.name }, + }, + this.clientMetaData.clientDomain, + ); + } + }); }; #updateCombinedErrorText = (elementId, errorMessages) => { diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 2ae70aac..de56c27f 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -219,6 +219,35 @@ export default class IFrameFormElement extends EventEmitter { : ELEMENT_EVENTS_TO_CLIENT.BLUR, value: { ...this.getStatus() }, }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: focus + ? ELEMENT_EVENTS_TO_CLIENT.FOCUS + : ELEMENT_EVENTS_TO_CLIENT.BLUR, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: focus + ? ELEMENT_EVENTS_TO_CLIENT.FOCUS + : ELEMENT_EVENTS_TO_CLIENT.BLUR, + name: this.iFrameName, + value: { ...this.getStatus() }, + }, + }, this.metaData.clientDomain); + } + } if (!focus) { bus.emit(ELEMENT_EVENTS_TO_CLIENT.BLUR + this.iFrameName); this._emit(ELEMENT_EVENTS_TO_CLIENT.BLUR, { @@ -282,6 +311,40 @@ export default class IFrameFormElement extends EventEmitter { this.mask = newMask; } + getFileDetails = (value: FileList | File | null): Array<{ + name: string; + size: number; + type: string; + }> => { + // Return empty array if no value + if (!value) return []; + + try { + // Handle FileList + if (value instanceof FileList) { + return Array.from(value).map((file) => ({ + name: file.name, + size: file.size, + type: file.type, + })); + } + + // Handle single File + if (value instanceof File) { + return [{ + name: value.name, + size: value.size, + type: value.type, + }]; + } + + // Return empty array for invalid input + return []; + } catch (error) { + return []; + } + }; + setValidation(validations: IValidationRule[] | undefined) { if (ELEMENTS[this.fieldType].regex) { this.regex = ELEMENTS[this.fieldType].regex; @@ -469,6 +532,21 @@ export default class IFrameFormElement extends EventEmitter { resp = false; } if (this.preserveFileName) vaildateFileNames = vaildateFileName(value.name); + } else if (this.fieldType === ElementType.MULTI_FILE_INPUT) { + const files = this.state.value instanceof FileList + ? Array.from(this.state.value) + : [this.state.value]; + for (let i = 0; i < files.length; i += 1) { + try { + resp = fileValidation(files[i], this.state.isRequired, { + allowedFileType: this.allowedFileType, + blockEmptyFiles: this.blockEmptyFiles, + }); + } catch (err) { + resp = false; + } + if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name); + } } else { // eslint-disable-next-line no-lonely-if if (this.regex && value) { @@ -591,6 +669,31 @@ export default class IFrameFormElement extends EventEmitter { event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } } else if ( data.options !== undefined @@ -673,23 +776,99 @@ export default class IFrameFormElement extends EventEmitter { event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } else if ( this.state.value && (this.fieldType === ELEMENTS.EXPIRATION_DATE.name || this.fieldType === ELEMENTS.EXPIRATION_MONTH.name - || this.fieldType === ELEMENTS.FILE_INPUT.name) + || this.fieldType === ELEMENTS.FILE_INPUT.name + || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) ) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } else if (!this.state.isEmpty) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, value: this.getStatus(), }); + if (this.containerType === ContainerType.COMPOSABLE) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: { + ...this.getStatus(), + value: '', + metaData: this.getFileDetails(this.getStatus().value), + }, + }, + }, this.metaData.clientDomain); + } else { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, + data: { + event: ELEMENT_EVENTS_TO_CLIENT.CHANGE, + name: this.iFrameName, + value: this.getStatus(), + }, + }, this.metaData.clientDomain); + } + } } this._emit(ELEMENT_EVENTS_TO_CLIENT.CHANGE, { diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index 8648eb8c..aca89ed3 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -89,15 +89,20 @@ export default class FrameElement { private selectedData?: number = undefined; + private clientDomain: string; + constructor( iFrameFormElement: IFrameFormElement, options: any, htmlDivElement: HTMLDivElement, + clientDomain: string = '', ) { + this.clientDomain = clientDomain; this.iFrameFormElement = iFrameFormElement; this.options = options; this.htmlDivElement = htmlDivElement; this.hasError = false; + this.mount(); this.iFrameFormElement.fieldName = options.column; this.iFrameFormElement.tableName = options.table; @@ -138,7 +143,6 @@ export default class FrameElement { this.inputParent = document.createElement('div'); this.inputParent.style.position = 'relative'; - const inputElement = document.createElement(type); this.domInput = inputElement; this.domInput.iFrameFormElement = this.iFrameFormElement; @@ -217,6 +221,9 @@ export default class FrameElement { if (state.value && this.iFrameFormElement.fieldType === ELEMENTS.FILE_INPUT.name) { this.focusChange(false); } + if (state.value && this.iFrameFormElement.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + this.focusChange(false); + } this.focusChange(false); if (state.error && this.domError) { @@ -454,6 +461,12 @@ export default class FrameElement { .emit(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.iFrameFormElement.iFrameName, { name: this.iFrameFormElement.iFrameName, }); + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.iFrameFormElement.iFrameName, + data: { + name: this.iFrameFormElement.iFrameName, + }, + }, this.clientDomain); this.updateStyleClasses(this.iFrameFormElement.getStatus()); }; @@ -567,6 +580,10 @@ export default class FrameElement { const target = event.target as HTMLFormElement; this.iFrameFormElement.setValue(target.files[0], target.checkValidity()); this.focusChange(true); + } else if (this.iFrameFormElement.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + const target = event.target as HTMLFormElement; + this.iFrameFormElement.setValue(target.files, target.checkValidity()); + this.focusChange(true); } else { const target = event.target as HTMLInputElement; const { mask } = this.iFrameFormElement; @@ -817,11 +834,21 @@ export default class FrameElement { }; onSubmit = () => { - bus - .emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, { - name: this.iFrameFormElement.iFrameName, - event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, - }); + if (this.iFrameFormElement.containerType === ContainerType.COMPOSABLE) { + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, + data: { + name: this.iFrameFormElement.iFrameName, + event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, + }, + }, this.clientDomain); + } else { + bus + .emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameFormElement.iFrameName, { + name: this.iFrameFormElement.iFrameName, + event: ELEMENT_EVENTS_TO_CLIENT.SUBMIT, + }); + } }; onArrowKeys = (keyBoardEvent: KeyboardEvent) => { diff --git a/src/core/internal/internal-types/index.ts b/src/core/internal/internal-types/index.ts index bb236b3f..5cec82cf 100644 --- a/src/core/internal/internal-types/index.ts +++ b/src/core/internal/internal-types/index.ts @@ -77,6 +77,7 @@ export interface ClientMetadata { } export interface Metadata extends ClientMetadata { + getSkyflowBearerToken: () => Promise | undefined | unknown; clientJSON: ClientToJSON; containerType: ContainerType; skyflowContainer: SkyflowContainer; diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 5611c51b..7fac88e1 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -15,17 +15,25 @@ import { RENDER_ELEMENT_IMAGE_STYLES, DEFAULT_FILE_RENDER_ERROR, ELEMENT_EVENTS_TO_CLIENT, + REVEAL_TYPES, } from '../../constants'; import getCssClassesFromJss, { generateCssWithoutClass } from '../../../libs/jss-styles'; import { printLog, parameterizedString, } from '../../../utils/logs-helper'; import logs from '../../../utils/logs'; -import { Context, MessageType } from '../../../utils/common'; +import { + Context, IRenderResponseType, IRevealRecord, MessageType, +} from '../../../utils/common'; import { constructMaskTranslation, - getAtobValue, getMaskedOutput, getValueFromName, handleCopyIconClick, styleToString, + formatRevealElementOptions, + getAtobValue, + getMaskedOutput, getValueFromName, handleCopyIconClick, styleToString, } from '../../../utils/helpers'; +import { formatForRenderClient, getFileURLFromVaultBySkyflowIDComposable } from '../../../core-utils/reveal'; +import Client from '../../../client'; +import properties from '../../../properties'; const { getType } = require('mime'); @@ -65,6 +73,8 @@ class RevealFrame { #skyflowContainerId: string = ''; + #client!: Client; + static init() { const url = window.location?.href; const configIndex = url.indexOf('?'); @@ -75,9 +85,9 @@ class RevealFrame { parsedRecord.context, skyflowContainerId); } - constructor(record, context, id) { + constructor(record, context, id, rootDiv?) { this.#skyflowContainerId = id; - this.#name = window.name; + this.#name = rootDiv ? record?.name : window.name; this.#containerId = getValueFromName(this.#name, 2); const encodedClientDomain = getValueFromName(this.#name, 4); const clientDomain = getAtobValue(encodedClientDomain); @@ -91,14 +101,14 @@ class RevealFrame { getCssClassesFromJss(REVEAL_ELEMENT_DIV_STYLE, 'div'); this.#labelElement = document.createElement('span'); - this.#labelElement.className = `SkyflowElement-label-${STYLE_TYPE.BASE}`; + this.#labelElement.className = `SkyflowElement-${this.#name}-label-${STYLE_TYPE.BASE}`; this.#dataElememt = document.createElement('span'); - this.#dataElememt.className = `SkyflowElement-content-${STYLE_TYPE.BASE}`; + this.#dataElememt.className = `SkyflowElement-${this.#name}-content-${STYLE_TYPE.BASE}`; this.#dataElememt.id = this.#name; this.#errorElement = document.createElement('span'); - this.#errorElement.className = `SkyflowElement-error-${STYLE_TYPE.BASE}`; + this.#errorElement.className = `SkyflowElement-${this.#name}-error-${STYLE_TYPE.BASE}`; if (this.#record.enableCopy) { this.domCopy = document.createElement('img'); @@ -125,13 +135,14 @@ class RevealFrame { ...REVEAL_ELEMENT_LABEL_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.labelStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#labelStyles, 'label'); + // getCssClassesFromJss(this.#labelStyles, 'label'); + getCssClassesFromJss(this.#labelStyles, `${this.#name}-label`); if (this.#record.labelStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.labelStyles[STYLE_TYPE.GLOBAL]); } } else { - getCssClassesFromJss(REVEAL_ELEMENT_LABEL_DEFAULT_STYLES, 'label'); + getCssClassesFromJss(REVEAL_ELEMENT_LABEL_DEFAULT_STYLES, `${this.#name}-label`); } } this.updateDataView(); @@ -140,7 +151,7 @@ class RevealFrame { this.#inputStyles[STYLE_TYPE.BASE] = { ...this.#record.inputStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#inputStyles, 'content'); + getCssClassesFromJss(this.#inputStyles, `${this.#name}-content`); if (this.#record.inputStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.inputStyles[STYLE_TYPE.GLOBAL]); } @@ -155,26 +166,68 @@ class RevealFrame { ...REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.errorTextStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#errorTextStyles, 'error'); + getCssClassesFromJss(this.#errorTextStyles, `${this.#name}-error`); if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { generateCssWithoutClass(this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]); } } else { getCssClassesFromJss( REVEAL_ELEMENT_ERROR_TEXT_DEFAULT_STYLES, - 'error', + `${this.#name}-error`, ); } this.#elementContainer.appendChild(this.#dataElememt); - document.body.append(this.#elementContainer); + if (rootDiv) rootDiv.append(this.#elementContainer); + else document.body.append(this.#elementContainer); + // document.body.append(this.#elementContainer); bus.emit(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#name, { name: this.#name }); - + if (rootDiv) { + this.getConfig(); + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#name, + data: { + name: this.#name, + }, + }, this.#clientDomain); + + window.parent.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { height: this.#elementContainer.scrollHeight, name: this.#name }, + }, this.#clientDomain); + } bus.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, (_, callback) => { callback({ height: this.#elementContainer.scrollHeight, name: this.#name }); }); + const sub2 = (responseUrl) => { + if (responseUrl.iframeName === this.#name) { + if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { + this.setRevealError(DEFAULT_FILE_RENDER_ERROR); + if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { + this.#dataElememt.innerText = this.#record.altText; + } + bus + .emit( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + { + height: this.#elementContainer.scrollHeight, + }, () => { + }, + ); + } else { + const ext = this.getExtension(responseUrl.url); + this.addFileRender(responseUrl.url, ext); + } + } + }; + bus + .target(window.location.origin) + .on( + ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY + this.#name, + sub2, + ); const sub = (data) => { if (Object.prototype.hasOwnProperty.call(data, this.#record.token)) { @@ -189,7 +242,7 @@ class RevealFrame { this.#dataElememt.innerText = formattedOutput; } printLog(parameterizedString(logs.infoLogs.ELEMENT_REVEALED, - CLASS_NAME, this.#record.token), MessageType.LOG, this.#context.logLevel); + CLASS_NAME, this.#record.token), MessageType.LOG, this.#context?.logLevel); // bus // .target(window.location.origin) @@ -220,35 +273,172 @@ class RevealFrame { if (data.isTriggerError) { this.setRevealError(data.clientErrorText as string); } else { this.setRevealError(''); } } }); + window.parent.postMessage( + { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#containerId, + data: { + name: window.name, + }, + }, this.#clientDomain, + ); this.updateRevealElementOptions(); - - const sub2 = (responseUrl) => { - if (responseUrl.iframeName === this.#name) { - if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { - this.setRevealError(DEFAULT_FILE_RENDER_ERROR); - if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { - this.#dataElememt.innerText = this.#record.altText; - } - bus - .emit( - ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, - { - height: this.#elementContainer.scrollHeight, - }, () => { - }, + window.addEventListener('message', (event) => { + 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) => { + const result = formatForRenderClient( + resolvedResult as IRenderResponseType, + this.#record?.column, ); - } else { - const ext = this.getExtension(responseUrl.url); - this.addFileRender(responseUrl.url, ext); + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, + data: { + type: REVEAL_TYPES.RENDER_FILE, + result, + }, + }, this.#clientDomain); + + window?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name, + }, properties?.IFRAME_SECURE_ORIGIN); + })?.catch((error) => { + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, + data: { + type: REVEAL_TYPES.RENDER_FILE, + result: { + errors: error, + }, + }, + }, this.#clientDomain); + + window?.postMessage({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + window?.name, + }, properties?.IFRAME_SECURE_ORIGIN); + }); } } - }; - bus - .target(window.location.origin) - .on( - ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY + this.#name, - sub2, + + if (event?.data?.type === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name) { + if (event?.data?.data?.height) { + window?.parent?.postMessage({ + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { + height: this.#elementContainer?.scrollHeight ?? 0, + name: this.#name, + }, + }, this.#clientDomain); + } + } + }); + } + + responseUpdate = (data) => { + if (data?.frameId === this.#record?.name && data?.error) { + if (!Object.prototype.hasOwnProperty.call(this.#record, 'skyflowID')) { + this.setRevealError(REVEAL_ELEMENT_ERROR_TEXT); + } + } else if (data?.frameId === this.#record?.name + && data?.[0]?.token + && this.#record?.token === data?.[0]?.token) { + const responseValue = data?.[0]?.value as string ?? ''; + this.#revealedValue = responseValue; + this.isRevealCalled = true; + this.#dataElememt.innerText = responseValue; + + if (this.#record?.mask) { + const { formattedOutput } = getMaskedOutput( + this.#dataElememt?.innerText ?? '', + this.#record?.mask?.[0], + constructMaskTranslation(this.#record?.mask), + ); + this.#dataElememt.innerText = formattedOutput ?? ''; + } + + printLog( + parameterizedString( + logs?.infoLogs?.ELEMENT_REVEALED, + CLASS_NAME, + this.#record?.token, + ), + MessageType.LOG, + this.#context?.logLevel, ); + } + + window?.parent?.postMessage( + { + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + data: { + height: this.#elementContainer?.scrollHeight ?? 0, + name: this.#name, + }, + }, + this.#clientDomain, + ); + }; + + getConfig = () => { + const url = window.location?.href; + const configIndex = url.indexOf('?'); + const encodedString = configIndex !== -1 ? decodeURIComponent(url.substring(configIndex + 1)) : ''; + const parsedRecord = encodedString ? JSON.parse(atob(encodedString)) : {}; + this.#clientDomain = parsedRecord.clientDomain || ''; + this.#containerId = parsedRecord.containerId; + }; + + getData = () => this.#record; + + private sub2 = (responseUrl) => { + if (responseUrl.iframeName === this.#name) { + if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { + this.setRevealError(DEFAULT_FILE_RENDER_ERROR); + if (Object.prototype.hasOwnProperty.call(this.#record, 'altText')) { + this.#dataElememt.innerText = this.#record.altText; + } + bus + .emit( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#name, + { + height: this.#elementContainer.scrollHeight, + }, () => { + }, + ); + } else { + const ext = this.getExtension(responseUrl.url); + this.addFileRender(responseUrl.url, ext); + } + } + }; + + private renderFile(data: IRevealRecord, clientConfig) { + this.#client = new Client(clientConfig, {}); + return new Promise((resolve, reject) => { + try { + getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) + .then((resolvedResult) => { + let url = ''; + if (resolvedResult.fields && data.column) { + url = resolvedResult.fields[data.column]; + } + this.sub2({ + url, + iframeName: this.#name, + }); + resolve(resolvedResult); + }, + (rejectedResult) => { + this.sub2({ + error: DEFAULT_FILE_RENDER_ERROR, + iframeName: this.#name, + }); + reject(rejectedResult); + }); + } catch (err) { + reject(err); + } + }); } // eslint-disable-next-line class-methods-use-this @@ -312,6 +502,31 @@ class RevealFrame { } private updateRevealElementOptions() { + window.addEventListener('message', (event) => { + if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + + this.#name) { + const data = event?.data; + if (data.updateType === REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS) { + const updatedValue = data.updatedValue as object; + this.#record = { + ...this.#record, + ...updatedValue, + ...formatRevealElementOptions(updatedValue), + }; + this.updateElementProps(); + if (this.isRevealCalled) { + if (this.#record?.mask) { + const { formattedOutput } = getMaskedOutput( + this.#revealedValue ?? '', + this.#record?.mask?.[0], + constructMaskTranslation(this.#record?.mask), + ); + this.#dataElememt.innerText = formattedOutput ?? ''; + } + } + } + } + }); bus .target(this.#clientDomain) .on(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#name, (data) => { @@ -355,7 +570,7 @@ class RevealFrame { ...this.#inputStyles, ...this.#record.inputStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#inputStyles, 'content'); + getCssClassesFromJss(this.#inputStyles, `${this.#name}-content`); if (this.#record.inputStyles[STYLE_TYPE.GLOBAL]) { const newInputGlobalStyles = { ...this.#inputStyles[STYLE_TYPE.GLOBAL], @@ -370,7 +585,7 @@ class RevealFrame { ...REVEAL_ELEMENT_LABEL_DEFAULT_STYLES[STYLE_TYPE.BASE], ...this.#record.labelStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#labelStyles, 'label'); + getCssClassesFromJss(this.#labelStyles, `${this.#name}-label`); if (this.#record.labelStyles[STYLE_TYPE.GLOBAL]) { const newLabelGlobalStyles = { @@ -386,7 +601,7 @@ class RevealFrame { ...this.#errorTextStyles[STYLE_TYPE.BASE], ...this.#record.errorTextStyles[STYLE_TYPE.BASE], }; - getCssClassesFromJss(this.#errorTextStyles, 'error'); + getCssClassesFromJss(this.#errorTextStyles, `${this.#name}-error`); if (this.#record.errorTextStyles[STYLE_TYPE.GLOBAL]) { const newErrorTextGlobalStyles = { ...this.#errorTextStyles[STYLE_TYPE.GLOBAL], diff --git a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts index 7b44335a..02b8fe72 100644 --- a/src/core/internal/skyflow-frame/skyflow-frame-controller.ts +++ b/src/core/internal/skyflow-frame/skyflow-frame-controller.ts @@ -106,11 +106,11 @@ class SkyflowFrameController { try { window.CoralogixRum.info(SDK_IFRAME_EVENT, data.event); printLog(parameterizedString(logs.infoLogs.METRIC_CAPTURE_EVENT), - MessageType.LOG, this.#context.logLevel); + MessageType.LOG, this.#context?.logLevel); } catch (err: any) { printLog(parameterizedString(logs.infoLogs.UNKNOWN_METRIC_CAPTURE_EVENT, err.toString()), - MessageType.LOG, this.#context.logLevel); + MessageType.LOG, this.#context?.logLevel); } } }, @@ -512,7 +512,8 @@ class SkyflowFrameController { if (inputElement) { if ( inputElement.iFrameFormElement.fieldType - !== ELEMENTS.FILE_INPUT.name + !== ELEMENTS.FILE_INPUT.name && inputElement.iFrameFormElement.fieldType + !== ELEMENTS.MULTI_FILE_INPUT.name ) { const { state, doesClientHasError, clientErrorText, errorText, onFocusChange, validations, @@ -552,6 +553,7 @@ class SkyflowFrameController { if ( inputElement.iFrameFormElement.fieldType !== ELEMENTS.FILE_INPUT.name + && inputElement.iFrameFormElement.fieldType !== ELEMENTS.MULTI_FILE_INPUT.name ) { if ( inputElement.iFrameFormElement.fieldType diff --git a/src/index-internal.ts b/src/index-internal.ts index 31274800..1cf7049f 100644 --- a/src/index-internal.ts +++ b/src/index-internal.ts @@ -4,6 +4,7 @@ Copyright (c) 2022 Skyflow, Inc. import 'core-js/stable'; import RevealFrame from './core/internal/reveal/reveal-frame'; import { + COMPOSABLE_REVEAL, FRAME_ELEMENT, FRAME_REVEAL, SKYFLOW_FRAME_CONTROLLER, @@ -18,6 +19,7 @@ import { } from './utils/logs-helper'; import { getAtobValue, getValueFromName } from './utils/helpers'; import FrameElementInit from './core/internal/frame-element-init'; +import RevealComposableFrameElementInit from './core/internal/composable-frame-element-init'; (function init(root: any) { try { @@ -26,6 +28,9 @@ import FrameElementInit from './core/internal/frame-element-init'; const frameId = getValueFromName(frameName, 1); if (frameType === SKYFLOW_FRAME_CONTROLLER) { SkyflowFrameController.init(frameId); + } else if (frameType === COMPOSABLE_REVEAL) { + root.Skyflow = RevealComposableFrameElementInit; + RevealComposableFrameElementInit.startFrameElement(); } else if (frameType === FRAME_ELEMENT) { const logLevel = getValueFromName(frameName, 4) || LogLevel.ERROR; printLog( diff --git a/src/libs/element-options.ts b/src/libs/element-options.ts index 360c6258..df162c82 100644 --- a/src/libs/element-options.ts +++ b/src/libs/element-options.ts @@ -380,7 +380,7 @@ export const formatOptions = ( break; } - case ELEMENTS.FILE_INPUT.name: { + case ELEMENTS.FILE_INPUT.name || ELEMENTS.MULTI_FILE_INPUT.name: { if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName')) { formattedOptions = { ...formattedOptions, preserveFileName: true }; } @@ -402,7 +402,7 @@ export const formatOptions = ( if (Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName') && !validateBooleanOptions(formattedOptions.preserveFileName)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['preserveFileName'], true); } - if (elementType === ELEMENTS.FILE_INPUT.name) { + if (elementType === ELEMENTS.FILE_INPUT.name || elementType === ELEMENTS.MULTI_FILE_INPUT.name) { if (options.allowedFileType) { if (!Array.isArray(options.allowedFileType)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ALLOWED_OPTIONS, [], true); diff --git a/src/skyflow.ts b/src/skyflow.ts index 447dff8c..456509f0 100644 --- a/src/skyflow.ts +++ b/src/skyflow.ts @@ -48,11 +48,13 @@ import ComposableContainer from './core/external/collect/compose-collect-contain import { validateComposableContainerOptions } from './utils/validators'; import ThreeDS from './core/external/threeds/threeds'; import { ClientMetadata, SkyflowElementProps } from './core/internal/internal-types'; +import ComposableRevealContainer from './core/external/reveal/composable-reveal-container'; export enum ContainerType { COLLECT = 'COLLECT', REVEAL = 'REVEAL', COMPOSABLE = 'COMPOSABLE', + COMPOSE_REVEAL = 'COMPOSABLE_REVEAL', } export interface SkyflowConfigOptions { logLevel?: LogLevel; @@ -173,9 +175,50 @@ class Skyflow { return skyflow; } + #getSkyflowBearerToken = () => new Promise((resolve, reject) => { + if ( + this.#client.config.getBearerToken + && (!this.#bearerToken || !isTokenValid(this.#bearerToken)) + ) { + this.#client.config + .getBearerToken() + .then((bearerToken) => { + if (isTokenValid(bearerToken)) { + printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + this.#bearerToken = bearerToken; + resolve(this.#bearerToken); + } else { + printLog(parameterizedString( + logs.errorLogs.INVALID_BEARER_TOKEN, + ), MessageType.ERROR, this.#logLevel); + reject({ + error: parameterizedString( + logs.errorLogs.INVALID_BEARER_TOKEN, + ), + }); + } + }) + .catch((err) => { + printLog(parameterizedString(logs.errorLogs.BEARER_TOKEN_REJECTED), MessageType.ERROR, + this.#logLevel); + reject({ error: err }); + }); + } else { + printLog(parameterizedString(logs.infoLogs.REUSE_BEARER_TOKEN, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + resolve(this.#bearerToken); + } + }); + container(type: ContainerType.COLLECT, options?: ContainerOptions): CollectContainer; container(type: ContainerType.COMPOSABLE, options?: ContainerOptions): ComposableContainer; container(type: ContainerType.REVEAL, options?: ContainerOptions): RevealContainer; + container(type: ContainerType.COMPOSE_REVEAL, + options?: ContainerOptions) + : ComposableRevealContainer; container(type: ContainerType, options?: ContainerOptions) { switch (type) { case ContainerType.COLLECT: { @@ -184,6 +227,7 @@ class Skyflow { clientJSON: this.#client.toJSON(), containerType: type, skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, }, this.#skyflowElements, { logLevel: this.#logLevel, env: this.#env }, options); @@ -198,6 +242,7 @@ class Skyflow { clientJSON: this.#client.toJSON(), containerType: type, skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, }, this.#skyflowElements, { logLevel: this.#logLevel, env: this.#env }, options); @@ -208,23 +253,38 @@ class Skyflow { } case ContainerType.COMPOSABLE: { validateComposableContainerOptions(options!); - const composableContainer = new ComposableContainer( - { - ...this.#metadata, - clientJSON: this.#client.toJSON(), - containerType: type, - skyflowContainer: this.#skyflowContainer, - }, - this.#skyflowElements, - { logLevel: this.#logLevel, env: this.#env }, - options!, - ); + const composableContainer = new ComposableContainer({ + ...this.#metadata, + clientJSON: this.#client.toJSON(), + containerType: type, + skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, + }, + this.#skyflowElements, + { logLevel: this.#logLevel, env: this.#env }, options!); printLog(parameterizedString(logs.infoLogs.COLLECT_CONTAINER_CREATED, CLASS_NAME), MessageType.LOG, this.#logLevel); return composableContainer; } + case ContainerType.COMPOSE_REVEAL: { + validateComposableContainerOptions(options!); + const revealComposableContainer = new ComposableRevealContainer(options, { + ...this.#metadata, + clientJSON: this.#client.toJSON(), + containerType: type, + skyflowContainer: this.#skyflowContainer, + getSkyflowBearerToken: this.#getSkyflowBearerToken, + }, + this.#skyflowElements, + { logLevel: this.#logLevel, env: this.#env }); + printLog(parameterizedString(logs.infoLogs.REVEAL_CONTAINER_CREATED, CLASS_NAME), + MessageType.LOG, + this.#logLevel); + return revealComposableContainer; + } + default: if (!type) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CONTAINER_TYPE, [], true); diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 6736d4a7..dec98386 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -75,6 +75,15 @@ export interface IRevealRecord { table?: string; } +export interface IRevealRecordComposable { + token?: string; + redaction?: RedactionType; + column?: string; + skyflowID?: string; + table?: string; + iframeName?: string; +} + export interface IInsertResponse { records: IInsertResponseReocrds[]; } @@ -309,6 +318,9 @@ export interface ICollectOptions { additionalFields?: IInsertRecordInput, upsert?: Array, } +export interface MetaData { + [key: string]: any, +} export interface UploadFilesResponse { fileUploadResponse?: Record, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index e80fcf2c..539ae099 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -4,6 +4,12 @@ Copyright (c) 2022 Skyflow, Inc. import logs from './logs'; const SKYFLOW_ERROR_CODE = { + MULTI_FILE_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.MULTI_FILE_NOT_SUPPORTED, + }, + INVALID_REVEAL_COMPOSABLE_INPUT: + { code: 400, description: logs.errorLogs.INVALID_REVEAL_COMPOSABLE_INPUT }, INVALID_FILE_NAME: { code: 400, description: logs.errorLogs.INVALID_FILE_NAME }, INVALID_FIELD: { code: 400, description: logs.errorLogs.INVALID_FIELD }, VAULTID_IS_REQUIRED: { code: 400, description: logs.errorLogs.VAULTID_IS_REQUIRED }, diff --git a/src/utils/logs.ts b/src/utils/logs.ts index 367ae2a8..8b5a75c0 100644 --- a/src/utils/logs.ts +++ b/src/utils/logs.ts @@ -10,6 +10,8 @@ const logs = { CREATE_COLLECT_CONTAINER: '%s1 - Creating Collect container.', COLLECT_CONTAINER_CREATED: '%s1 - Created Collect container successfully.', + INITIALIZE_COMPOSABLE_CLIENT: '%s1 - Initializing Composable container.', + CREATE_REVEAL_CONTAINER: '%s1 - Creating Reveal container.', REVEAL_CONTAINER_CREATED: '%s1 - Created Reveal container successfully.', @@ -26,6 +28,8 @@ const logs = { ELEMENT_REVEALED: '%s1 - %s2 Element revealed.', FILE_RENDERED: '%s1 - %s2 File rendered.', COLLECT_SUBMIT_SUCCESS: '%s1 - Data has been collected successfully.', + UPLOAD_FILES_SUCCESS: '%s1 - Files uploaded successfully.', + MULTI_UPLOAD_FILES_SUCCESS: '%s1 - Multiple files uploaded successfully.', REVEAL_SUBMIT_SUCCESS: '%s1 - Data has been revealed successfully.', RENDER_SUBMIT_SUCCESS: '%s1 - File download URL has been fetched successfully.', INSERT_DATA_SUCCESS: '%s1 - Data has been inserted successfully.', @@ -91,6 +95,8 @@ const logs = { VALIDATE_GET_BY_ID_INPUT: '%s1 - Validating getByID input.', }, errorLogs: { + MULTI_FILE_NOT_SUPPORTED: 'Multi file upload is only supported in MULT_FILE_INPUT element in composable container. Please use MULT_FILE_INPUT element for multi file upload.', + INVALID_REVEAL_COMPOSABLE_INPUT: 'Reveal composable input is invalid. Please provide a valid input.', NO_ELEMENTS_IN_COLLECT: 'Validation error. No elements found in collect container', NO_ELEMENTS_IN_COMPOSABLE: 'Validation error. No elements found in composable container', NO_ELEMENTS_IN_REVEAL: 'Validation error. No elements found in reveal container', From de5826b340856d9263668c687fc5ce5a9bfb0bee Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Tue, 29 Jul 2025 09:10:59 +0000 Subject: [PATCH 21/50] [AUTOMATED] Release - 2.5.0-beta.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 41c443e7..3bb4392e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.4", + "version": "2.5.0-beta.8", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 612083a33a5ad97745acc4a51a0030380f93eed1 Mon Sep 17 00:00:00 2001 From: skyflow-bharti <118584001+skyflow-bharti@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:50:44 +0530 Subject: [PATCH 22/50] Release/25.7.8.2 (#616) * SK-2177 support composable container in shadow-dom and normal dom * SK-2177 internal release * [AUTOMATED] Release - 2.5.0-beta.5-dev.3d22e02 * SK-2177 fix promises * [AUTOMATED] Release - 2.5.0-beta.5-dev.c97c0f5 * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.1a3ba57 * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.4f9c03b * SK-2177 mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.410308a * SK-2177 comment mount * [AUTOMATED] Release - 2.5.0-beta.5-dev.06519d4 * SK-2177 comment mounted * [AUTOMATED] Release - 2.5.0-beta.5-dev.9d975d3 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.1c981ce * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.deb34bf * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.7dda8b1 * SK-2177 add target * [AUTOMATED] Release - 2.5.0-beta.5-dev.a9e2143 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.d5616a2 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.a0c77c9 * SK-2177 client initialise * [AUTOMATED] Release - 2.5.0-beta.5-dev.69a8f50 * SK-2177 client fix * [AUTOMATED] Release - 2.5.0-beta.5-dev.9812099 * [AUTOMATED] Release - 2.5.0-beta.5-dev.0ee2970 * [AUTOMATED] Release - 2.5.0-beta.5-dev.ef404d4 * SK-2177 target test * [AUTOMATED] Release - 2.5.0-beta.5-dev.5f64ad2 * SK-2177 target tests * [AUTOMATED] Release - 2.5.0-beta.5-dev.25da8a5 * SK-2177 target tests * SK-2177 fix height * [AUTOMATED] Release - 2.5.0-beta.5-dev.36a43c5 * SK-2177 file render * [AUTOMATED] Release - 2.5.0-beta.5-dev.fc93609 * SKS-2117 WIP reveal composable changes. * SKS-2177: WIP added reveal composble element styles. * SK-2177 added event for reveal composable * SK-2177 added event for reveal composable * SK-2177 add reveal element input validations. * SK-2177 file render changes * SK-2177 file render changes * [AUTOMATED] Release - 2.5.0-beta.5-dev.50ad902 * SK-2177 fix height * SK-2177 fix render changes * [AUTOMATED] Release - 2.5.0-beta.5-dev.8fd34b7 * SK-2177 added error handling * SK-2177 trigger release internal * [AUTOMATED] Release - 2.5.0-beta.5-dev.de7a4f1 * SK-2177 add optional checks * [AUTOMATED] Release - 2.5.0-beta.5-dev.f8a1528 * SK-2191 add support for multiple file upload * [AUTOMATED] Release - 2.5.0-beta.6-dev.97db0e3 * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.e034fae * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.9003510 * SK-2191 fix drag and drop * [AUTOMATED] Release - 2.5.0-beta.6-dev.b79aa08 * [AUTOMATED] Release - 2.5.0-beta.6-dev.5974a3a * SK-2191 fix drag and drop * SK-2197 removed commented code * [AUTOMATED] Release - 2.5.0-beta.6-dev.19a5b44 * SK-2191 fix height * [AUTOMATED] Release - 2.5.0-beta.6-dev.7c41e89 * [AUTOMATED] Release - 2.5.0-beta.6-dev.a29ea92 * SK-2202 fix the listeners * [AUTOMATED] Release - 2.4.2-dev.eb6dbb3 * SK-2202 fix the submit * [AUTOMATED] Release - 2.4.2-dev.47ea8b2 * SK-2202 add optional check * [AUTOMATED] Release - 2.4.2-dev.141ab05 * [AUTOMATED] Release - 2.4.2-dev.6c87190 * SK-2202 event listener * [AUTOMATED] Release - 2.5.0-beta.8-dev.d79138c * [AUTOMATED] Release - 2.5.0-beta.8-dev.cc9dc5a --------- Co-authored-by: skyflow-bharti Co-authored-by: yaswanth-pula-skyflow --- package.json | 2 +- src/core/external/collect/collect-element.ts | 3 ++- src/core/internal/iframe-form/index.ts | 20 +++++++++++++------- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 3bb4392e..4ce21b38 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.8", + "version": "2.5.0-beta.8-dev.cc9dc5a", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 6d73de71..f97794b5 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -513,7 +513,8 @@ class CollectElement extends SkyflowElement { this.#states[index].isFocused = data.value.isFocused; this.#states[index].isRequired = data.value.isRequired; this.#states[index].selectedCardScheme = data?.value?.selectedCardScheme || ''; - if (element.elementType === ElementType.MULTI_FILE_INPUT) { + if (element.elementType === ElementType.MULTI_FILE_INPUT + || element.elementType === ElementType.FILE_INPUT) { this.#states[index].metaData = data?.value?.metaData || []; } if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index de56c27f..6da0eebb 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -220,7 +220,8 @@ export default class IFrameFormElement extends EventEmitter { value: { ...this.getStatus() }, }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -670,7 +671,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -777,7 +779,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -806,7 +809,8 @@ export default class IFrameFormElement extends EventEmitter { && (this.fieldType === ELEMENTS.EXPIRATION_DATE.name || this.fieldType === ELEMENTS.EXPIRATION_MONTH.name || this.fieldType === ELEMENTS.FILE_INPUT.name - || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) + || this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + ) ) { bus.emit(ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, { name: this.iFrameName, @@ -814,7 +818,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -845,7 +850,8 @@ export default class IFrameFormElement extends EventEmitter { value: this.getStatus(), }); if (this.containerType === ContainerType.COMPOSABLE) { - if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name) { + if (this.fieldType === ELEMENTS.MULTI_FILE_INPUT.name + || this.fieldType === ELEMENTS.FILE_INPUT.name) { window.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT + this.iFrameName, data: { @@ -905,4 +911,4 @@ export default class IFrameFormElement extends EventEmitter { this.resetData(); this.resetEvents(); } -} +} \ No newline at end of file From fd4c85f31475d2c45b7e494858cabf9277caa49c Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 16:57:33 +0530 Subject: [PATCH 23/50] SK-2239: add file metadata in file render and wcag keyboard actions --- package.json | 2 +- src/core-utils/reveal.ts | 3 +- src/core/external/collect/collect-element.ts | 9 +++--- src/core/external/common/iframe.ts | 1 + .../reveal/composable-reveal-container.ts | 4 +-- .../reveal/composable-reveal-internal.ts | 20 ++++++------- src/core/internal/iframe-form/index.ts | 20 ++++++------- src/core/internal/index.ts | 30 ++++++++++++++++++- src/core/internal/reveal/reveal-frame.ts | 4 ++- src/utils/common/index.ts | 1 + tests/utils/jwt-utils.test.js | 4 +-- 11 files changed, 65 insertions(+), 33 deletions(-) diff --git a/package.json b/package.json index 4ce21b38..3c308b17 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.8-dev.cc9dc5a", + "version": "2.4.3-dev.b8e2236", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", diff --git a/src/core-utils/reveal.ts b/src/core-utils/reveal.ts index f2087d84..bb334494 100644 --- a/src/core-utils/reveal.ts +++ b/src/core-utils/reveal.ts @@ -146,7 +146,7 @@ export const getFileURLForRender = ( paramList += `${skyflowIdRecord.skyflowID}?`; - paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}`; + paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}&returnFileMetadata=true`; const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}/${paramList}`; return client.request({ @@ -345,6 +345,7 @@ export const formatForRenderClient = (response: IRenderResponseType, column: str const successRecord = { skyflow_id: response.fields.skyflow_id, column, + fileMetadata: response.fileMetadata, }; formattedResponse.success = successRecord; } else if (response.errors) { diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index f97794b5..df18953c 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -469,7 +469,7 @@ class CollectElement extends SkyflowElement { } }); this.#elements.forEach((element1) => { - const isComposableContainer = this.#elements.length > 1; + const isComposableContainer = this.#metaData?.containerType === 'COMPOSABLE'; if (isComposableContainer) { window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.INPUT_EVENT @@ -483,7 +483,6 @@ class CollectElement extends SkyflowElement { ) { this.#eventEmitter._emit(ELEMENT_EVENTS_TO_CLIENT.READY); } else { - const isComposable = this.#elements.length > 1; this.#elements.forEach((element, index) => { if (data.name === element.elementName) { let emitEvent = ''; @@ -520,7 +519,7 @@ class CollectElement extends SkyflowElement { if (Object.prototype.hasOwnProperty.call(data.value, 'value')) this.#states[index].value = data.value.value; else this.#states[index].value = undefined; - emitEvent = isComposable ? `${emitEvent}:${data.name}` : emitEvent; + emitEvent = isComposableContainer ? `${emitEvent}:${data.name}` : emitEvent; this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, {}, (payload:any) => { @@ -532,12 +531,12 @@ class CollectElement extends SkyflowElement { ...this.#states[index], elementType: element.elementType, }; - if (isComposable) { + if (isComposableContainer) { this.#groupEmitter?._emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT, { iframeName: this.#iframe.name, }); } - if (isComposable && this.#groupEmitter) { + if (isComposableContainer && this.#groupEmitter) { this.#groupEmitter._emit(emitEvent, emitData); } else { this.#eventEmitter._emit(emitEvent, emitData); diff --git a/src/core/external/common/iframe.ts b/src/core/external/common/iframe.ts index defcf630..cde296f5 100644 --- a/src/core/external/common/iframe.ts +++ b/src/core/external/common/iframe.ts @@ -28,6 +28,7 @@ export default class IFrame { this.iframe = iframer({ name: this.name, referrer: clientDomain, + title: name.match(/^element:([^:]+):/)?.[1] ?? name, }); } diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index 2d829848..bf635096 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -187,9 +187,9 @@ class ComposableRevealContainer extends Container { let element = this.#elements[this.#tempElements.elementName]; if (element) { if (isSingleElementAPI) { - element.update(elements[0]); + // element.update(elements[0]); } else { - element.update(this.#tempElements); + // element.update(this.#tempElements); } } else { const elementId = uuid(); diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index a5584755..618f753e 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -108,12 +108,6 @@ class ComposableRevealInternalElement extends SkyflowElement { this.#iframe?.setIframeHeight(data?.height); }); - window?.addEventListener('message', (event) => { - if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { - this.#isComposableFrameReady = true; - } - }); window?.addEventListener('message', (event) => { if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + this.#iframe?.name) { @@ -136,6 +130,12 @@ class ComposableRevealInternalElement extends SkyflowElement { rows?.forEach((row, rowIndex) => { row?.elements?.forEach((element: any, elementIndex: number) => { if (!element?.name) return; + window?.addEventListener('message', (event) => { + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + element?.name) { + this.#isComposableFrameReady = true; + } + }); this.#eventEmitter?.on( `${ELEMENT_EVENTS_TO_IFRAME?.RENDER_FILE_REQUEST}:${element?.name}`, (data, callback) => { @@ -351,9 +351,9 @@ class ComposableRevealInternalElement extends SkyflowElement { MessageType.LOG, loglevel); validateRenderElementRecord(recordData); - window.addEventListener('message', (event) => { + window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { + + recordData?.name) { this.#isMounted = true; this.#getSkyflowBearerToken()?.then((authToken) => { printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), @@ -377,7 +377,7 @@ class ComposableRevealInternalElement extends SkyflowElement { window.addEventListener('message', (event1) => { if (event1?.data && event1?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE - + this.#iframe.name) { + + recordData.name) { if (event1?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { const revealData = event1?.data?.data?.result; if (revealData?.error) { @@ -592,7 +592,7 @@ class ComposableRevealInternalElement extends SkyflowElement { } else { window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED - + this.#containerId) { + + record?.name) { this.#emitEvent( ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record.name, { diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 6da0eebb..32d29010 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -313,9 +313,9 @@ export default class IFrameFormElement extends EventEmitter { } getFileDetails = (value: FileList | File | null): Array<{ - name: string; - size: number; - type: string; + fileName: string; + fileSizeKB: number; + fileType: string; }> => { // Return empty array if no value if (!value) return []; @@ -324,18 +324,18 @@ export default class IFrameFormElement extends EventEmitter { // Handle FileList if (value instanceof FileList) { return Array.from(value).map((file) => ({ - name: file.name, - size: file.size, - type: file.type, + fileName: file.name, + fileSizeKB: Math.ceil(file.size / 1024), + fileType: file.type, })); } // Handle single File if (value instanceof File) { return [{ - name: value.name, - size: value.size, - type: value.type, + fileName: value.name, + fileSizeKB: Math.ceil(value.size / 1024), + fileType: value.type, }]; } @@ -911,4 +911,4 @@ export default class IFrameFormElement extends EventEmitter { this.resetData(); this.resetEvents(); } -} \ No newline at end of file +} diff --git a/src/core/internal/index.ts b/src/core/internal/index.ts index aca89ed3..01878a53 100644 --- a/src/core/internal/index.ts +++ b/src/core/internal/index.ts @@ -145,6 +145,14 @@ export default class FrameElement { this.inputParent.style.position = 'relative'; const inputElement = document.createElement(type); this.domInput = inputElement; + inputElement.addEventListener('keydown', (event) => { + const keyboardEvent = event as KeyboardEvent; + if ((keyboardEvent.ctrlKey || keyboardEvent.metaKey) && keyboardEvent.key.toLowerCase() === 'z') { + keyboardEvent.preventDefault(); + this.setValue(''); + this.iFrameFormElement.setValue('', true); + } + }); this.domInput.iFrameFormElement = this.iFrameFormElement; inputElement.setAttribute(CUSTOM_ROW_ID_ATTRIBUTE, this.htmlDivElement?.id?.split(':')[0] || ''); this.inputParent.append(inputElement); @@ -164,6 +172,15 @@ export default class FrameElement { this.dropdownSelect = document.createElement('select'); this.dropdownSelect.setAttribute('style', this.options?.inputStyles?.dropdown ? (DROPDOWN_STYLES + styleToString(this.options.inputStyles.dropdown)) : DROPDOWN_STYLES); + this.dropdownSelect.addEventListener('focus', () => { + if (this.options?.inputStyles?.dropdownIcon?.focus) { + this.setDropdownIconStyle(this.options?.inputStyles?.dropdownIcon?.focus); + } + }); + + this.dropdownSelect.addEventListener('blur', () => { + this.setDropdownIconStyle(this.options?.inputStyles?.dropdownIcon); + }); this.dropdownSelect.addEventListener('change', (event:any) => { event.preventDefault(); @@ -888,7 +905,6 @@ export default class FrameElement { case INPUT_KEYBOARD_EVENTS.ENTER: this.onSubmit(); - keyBoardEvent.preventDefault(); break; default: break; @@ -1165,4 +1181,16 @@ export default class FrameElement { } } } + + private setDropdownIconStyle(styleObj?: any) { + if (this.dropdownIcon?.style.display === 'block') { + this.dropdownIcon.setAttribute( + 'style', + styleObj + ? DROPDOWN_ICON_STYLES + styleToString(styleObj) + : DROPDOWN_ICON_STYLES, + ); + this.dropdownIcon.style.display = 'block'; + } + } } diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 7fac88e1..71a268d3 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -275,7 +275,7 @@ class RevealFrame { }); window.parent.postMessage( { - type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#containerId, + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + this.#name, data: { name: window.name, }, @@ -291,6 +291,8 @@ class RevealFrame { resolvedResult as IRenderResponseType, this.#record?.column, ); + console.log("event name : ",ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name) + console.log("resultt : ", result); window?.parent?.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, data: { diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index dec98386..57737439 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -99,6 +99,7 @@ export interface IRevealResponseType { export interface IRenderResponseType { fields?: Record errors?: Record + fileMetadata?: Record } export interface IDetokenizeInput { diff --git a/tests/utils/jwt-utils.test.js b/tests/utils/jwt-utils.test.js index e6c4f52e..332e11c5 100644 --- a/tests/utils/jwt-utils.test.js +++ b/tests/utils/jwt-utils.test.js @@ -5,7 +5,7 @@ import isTokenValid from "../../src/utils/jwt-utils"; jest.mock('jwt-decode', () => () => ({exp: 123})) describe('Validation token', () => { - + test('empty token', () => { const res = isTokenValid("") expect(res).toBe(false) @@ -20,4 +20,4 @@ describe('Validation token', () => { const res = isTokenValid("token") expect(res).toBe(false) }) -}); \ No newline at end of file +}); From df2f10b975f474299b2b178116a5fa20d2f85447 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 11:28:22 +0000 Subject: [PATCH 24/50] [AUTOMATED] Release - 2.4.3-dev.c482a14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c308b17..991e2df5 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.b8e2236", + "version": "2.4.3-dev.c482a14", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From b4ad37424053da881745eb88d4e45a27df4a65a9 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 17:22:25 +0530 Subject: [PATCH 25/50] SK-2239: remove console log --- src/core/internal/reveal/reveal-frame.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 71a268d3..4476eded 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -291,8 +291,6 @@ class RevealFrame { resolvedResult as IRenderResponseType, this.#record?.column, ); - console.log("event name : ",ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name) - console.log("resultt : ", result); window?.parent?.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + this.#name, data: { From e149568fb0810adab06596933ea6db533ed1c2f7 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 11:53:08 +0000 Subject: [PATCH 26/50] [AUTOMATED] Release - 2.4.3-dev.15b2419 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 991e2df5..ee1cee60 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.c482a14", + "version": "2.4.3-dev.15b2419", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From c41bd4617706fc123c452302de5d90c2bb99d4f0 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Thu, 14 Aug 2025 12:14:13 +0000 Subject: [PATCH 27/50] [AUTOMATED] Release - 2.5.0-beta.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee1cee60..55bd50d4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.3-dev.15b2419", + "version": "2.5.0-beta.9", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 05841007b8a92db5ac6998fe67dc3d699bca9715 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 13:15:45 +0530 Subject: [PATCH 28/50] SK-2254: fix 0px height in composable container --- src/core/external/collect/collect-element.ts | 29 +++- src/core/internal/frame-element-init.ts | 141 +++++++++++-------- src/core/internal/internal-types/index.ts | 1 + src/utils/validators/index.ts | 4 - 4 files changed, 104 insertions(+), 71 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index df18953c..84d76c88 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -42,6 +42,7 @@ import { updateMetricObjectValue, } from '../../../metrics'; import { Metadata, ContainerProps, InternalState } from '../../internal/internal-types'; +import properties from '../../../properties'; const CLASS_NAME = 'Element'; class CollectElement extends SkyflowElement { @@ -139,6 +140,7 @@ class CollectElement extends SkyflowElement { name: element.elementName, isRequired: element.required, selectedCardScheme: '', + metaData: undefined, }); }); if (this.#elements && this.#elements.length && this.#elements.length > 1) { @@ -203,12 +205,27 @@ class CollectElement extends SkyflowElement { if (!domElement) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['CollectElement'], true); } - this.resizeObserver = new ResizeObserver(() => { - this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - {}, (payload:any) => { - this.#iframe.setIframeHeight(payload.height); - }); - }); + + if (domElement instanceof HTMLElement) { + this.resizeObserver = new ResizeObserver(() => { + if (domElement.getElementsByTagName('iframe')[0]?.contentWindow) { + const iframeElement = domElement.getElementsByTagName('iframe')[0]; + if (iframeElement.name === this.#iframe.name) { + iframeElement?.contentWindow?.postMessage({ + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }); + } else if (typeof domElement === 'string') { + this.resizeObserver = new ResizeObserver(() => { + this.#bus.emit(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + {}, (payload:any) => { + this.#iframe.setIframeHeight(payload.height); + }); + }); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElement); if ( this.#metaData?.clientJSON?.config?.options?.trackMetrics diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index 13c89945..a3b033ca 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -217,6 +217,9 @@ export default class FrameElementInit { const value: Blob = Object.values(fileUploadObject)[0] as Blob; + formData.append('columnName', column); + formData.append('tableName', tableName); + if (preserveFileName) { const isValidFileName = vaildateFileName(state.value.name); if (!isValidFileName) { @@ -224,10 +227,14 @@ export default class FrameElementInit { new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_NAME, [], true), ); } - formData.append(column, value); + formData.append('file', value); } else { const generatedFileName = generateUploadFileName(state.value.name); - formData.append(column, new File([value], generatedFileName, { type: state.value.type })); + formData.append('file', new File([value], generatedFileName, { type: state.value.type })); + } + + if (skyflowID) { + formData.append('skyflowID', skyflowID); } const client = this.#client; @@ -236,7 +243,7 @@ export default class FrameElementInit { .request({ body: formData, requestMethod: 'POST', - url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowID}/files`, + url: `${client.config.vaultURL}/v2/vaults/${client.config.vaultID}/files/upload`, headers: { authorization: `Bearer ${clientConfig.authToken}`, 'content-type': 'multipart/form-data', @@ -457,73 +464,90 @@ export default class FrameElementInit { // eslint-disable-next-line consistent-return private multipleUploadFiles = - (fileElement: IFrameFormElement, - clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + (fileElement: IFrameFormElement, clientConfig, metaData) => new Promise((rootResolve, rootReject) => { this.#client = new Client(clientConfig, {}); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); - const { - state, tableName, onFocusChange, preserveFileName, - } = fileElement; + + const { state, tableName, onFocusChange, preserveFileName } = fileElement; if (state.isRequired) { onFocusChange(false); } - if (fileElement.state.value === undefined || fileElement.state.value === null || fileElement.state.value === '') { + if (state.value === undefined || state.value === null || state.value === '') { rootReject({ error: 'No files selected' }); return; } - const files = state.value instanceof FileList - ? Array.from(state.value) - : [state.value]; + const files = state.value instanceof FileList ? Array.from(state.value) : [state.value]; this.validateFiles(files, state, fileElement); - const insertRequest = this.createInsertRequest(files.length, metaData); - this.insertDataCallInMultiFiles( - insertRequest, this.#client, tableName as string, clientConfig.authToken as string, - ).then((response: any) => { - const skyflowIDs = this.extractSkyflowIDs(response); - if (skyflowIDs.length === 0) { - rootReject({ error: 'No skyflow IDs returned from insert data' }); - return; + + const uploadFile = (file: File, skyflowID?: string) => { + const formData = new FormData(); + formData.append('columnName', state.name); + if (tableName) formData.append('tableName', tableName); + if (preserveFileName) { + formData.append('file', file); + } else { + const generatedFileName = generateUploadFileName(file.name); + formData.append('file', new File([file], generatedFileName, { type: file.type })); } - const promises: Promise[] = []; - - files.forEach((file, index) => { - const fileUploadObject: any = {}; - fileUploadObject[state.name] = file; - const formData = new FormData(); - const column = Object.keys(fileUploadObject)[0]; - const value: Blob = Object.values(fileUploadObject)[0] as Blob; - if (preserveFileName) { - formData.append(column, value); - } else { - const generatedFileName = generateUploadFileName(file.name); - formData.append(column, new File([value], generatedFileName, { type: file.type })); + if (skyflowID) formData.append('skyflowID', skyflowID); + const client = this.#client; + return this.#client.request({ + body: formData, + requestMethod: 'POST', + url: `${client.config.vaultURL}/v2/vaults/${this.#client.config.vaultID}/files/upload`, + headers: { + authorization: `Bearer ${clientConfig.authToken}`, + 'content-type': 'multipart/form-data', + }, + }); + }; + + if (metaData && Object.keys(metaData).length > 0) { + const insertRequest = this.createInsertRequest(files.length, metaData); + this.insertDataCallInMultiFiles( + insertRequest, this.#client, tableName as string, clientConfig.authToken as string, + ).then((response: any) => { + const skyflowIDs = this.extractSkyflowIDs(response); + if (skyflowIDs.length === 0) { + rootReject({ error: 'No skyflow IDs returned from insert data' }); + return; } - const client = this.#client; - const promise1 = new Promise((resolve, reject) => { - client - .request({ - body: formData, - requestMethod: 'POST', - url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${tableName}/${skyflowIDs[index]}/files`, - headers: { - authorization: `Bearer ${clientConfig.authToken}`, - 'content-type': 'multipart/form-data', - }, - }) - .then((response1) => { - resolve(response1); - }) - .catch((error) => { - reject(error); - }); + const promises = files.map((file, idx) => uploadFile(file, skyflowIDs[idx])); + Promise.allSettled(promises).then((resultSet) => { + const fileUploadResponse: any[] = []; + const errorResponse: any[] = []; + resultSet.forEach((result) => { + if (result.status === 'fulfilled') { + if (result.value !== undefined && result.value !== null) { + if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { + errorResponse.push(result.value); + } else { + const response1 = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(response1); + } + } + } else if (result.status === 'rejected') { + errorResponse.push({ error: result.reason }); + } + }); + if (errorResponse.length === 0) { + rootResolve({ fileUploadResponse }); + } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); + else rootReject({ fileUploadResponse, errorResponse }); + }); + }).catch((error) => { + printLog(`${error}`, MessageType.LOG, this.context?.logLevel); + rootReject({ + error: error?.error || error, }); - promises.push(promise1); }); - Promise.allSettled( - promises, - ).then((resultSet) => { + } else { + const promises = files.map((file) => uploadFile(file)); + Promise.allSettled(promises).then((resultSet) => { const fileUploadResponse: any[] = []; const errorResponse: any[] = []; resultSet.forEach((result) => { @@ -547,12 +571,7 @@ export default class FrameElementInit { } else if (fileUploadResponse.length === 0) rootReject({ errorResponse }); else rootReject({ fileUploadResponse, errorResponse }); }); - }).catch((error) => { - printLog(`${error}`, MessageType.LOG, this.context?.logLevel); - rootReject({ - error: error?.error || error, - }); - }); + } }); private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => { diff --git a/src/core/internal/internal-types/index.ts b/src/core/internal/internal-types/index.ts index 5cec82cf..96b90abd 100644 --- a/src/core/internal/internal-types/index.ts +++ b/src/core/internal/internal-types/index.ts @@ -47,6 +47,7 @@ export interface RevealContainerProps { } export interface InternalState { + metaData: any; isEmpty: boolean, isValid: boolean, isFocused: boolean, diff --git a/src/utils/validators/index.ts b/src/utils/validators/index.ts index 5630f9d7..3d181ec8 100644 --- a/src/utils/validators/index.ts +++ b/src/utils/validators/index.ts @@ -562,10 +562,6 @@ export const validateCollectElementInput = (input: CollectElementInput, logLevel if (Object.prototype.hasOwnProperty.call(input, 'skyflowID') && !(typeof input.skyflowID === 'string')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_SKYFLOWID_IN_COLLECT, [], true); } - if (input.type === ElementType.FILE_INPUT - && !Object.keys(input).includes('skyflowID')) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_SKYFLOWID_IN_COLLECT, [], true); - } }; export const validateUpsertOptions = (upsertOptions) => { From ad9d4ce6d87159c1198741a0f5431f5d87878d0b Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 07:46:37 +0000 Subject: [PATCH 29/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.99fa815 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 55bd50d4..c086c343 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9", + "version": "2.5.0-beta.9-dev.99fa815", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From bbaf520b429daaf3f29005b26adb6040adb994c7 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 14:33:13 +0530 Subject: [PATCH 30/50] SK-2254: fix 0px height in composable reveal --- .../reveal/composable-reveal-internal.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index 618f753e..2e5a2cbf 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -54,6 +54,8 @@ class ComposableRevealInternalElement extends SkyflowElement { #elementId: string; + resizeObserver: ResizeObserver | null; + #readyToMount: boolean = false; #eventEmitter: EventEmitter; @@ -83,6 +85,7 @@ class ComposableRevealInternalElement extends SkyflowElement { super(); this.#elementId = elementId; this.#metaData = metaData; + this.resizeObserver = null; this.#clientId = this.#metaData?.uuid; this.#isSingleElementAPI = isSingleElementAPI; this.#recordData = recordGroup; @@ -185,6 +188,20 @@ class ComposableRevealInternalElement extends SkyflowElement { if (!domElementSelector) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['RevealElement'], true); } + + if(domElementSelector instanceof HTMLElement){ + this.resizeObserver = new ResizeObserver(() => { + if (domElementSelector.getElementsByTagName('iframe')[0]?.contentWindow) { + const iframeElement = domElementSelector.getElementsByTagName('iframe')[0] + if(iframeElement.name === this.#iframe.name){ + iframeElement?.contentWindow?.postMessage({ + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, properties.IFRAME_SECURE_ORIGIN); + } + } + }); + } + updateMetricObjectValue(this.#elementId, METRIC_TYPES.DIV_ID, domElementSelector); if ( this.#metaData?.clientJSON?.config?.options?.trackMetrics @@ -193,6 +210,15 @@ class ComposableRevealInternalElement extends SkyflowElement { pushElementEventWithTimeout(this.#elementId); } + if (typeof domElementSelector === 'string') { + const targetElement = document.querySelector(domElementSelector); + if (targetElement) { + this.resizeObserver?.observe(targetElement); + } + } else if (domElementSelector instanceof HTMLElement) { + this.resizeObserver?.observe(domElementSelector); + } + this.#readyToMount = true; if (this.#readyToMount) { this.#iframe.mount(domElementSelector, undefined, { From 159dda9097e050a3a700de405f269c593362fdc6 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 09:04:49 +0000 Subject: [PATCH 31/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.fcfbd99 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c086c343..325b02da 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.99fa815", + "version": "2.5.0-beta.9-dev.fcfbd99", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 9a7a6cc2184b8ad7f3c9d374a719621c42993baf Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 16:13:36 +0530 Subject: [PATCH 32/50] SK-2254: add resize observer for multiple iframes --- src/core/external/collect/collect-element.ts | 20 +++++++++++++------ .../reveal/composable-reveal-internal.ts | 19 ++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 84d76c88..5cc87639 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -208,12 +208,20 @@ class CollectElement extends SkyflowElement { if (domElement instanceof HTMLElement) { this.resizeObserver = new ResizeObserver(() => { - if (domElement.getElementsByTagName('iframe')[0]?.contentWindow) { - const iframeElement = domElement.getElementsByTagName('iframe')[0]; - if (iframeElement.name === this.#iframe.name) { - iframeElement?.contentWindow?.postMessage({ - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, properties.IFRAME_SECURE_ORIGIN); + const iframeElements = domElement.getElementsByTagName('iframe'); + // eslint-disable-next-line no-plusplus + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name + && iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + 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 2e5a2cbf..f9b26bc6 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -191,12 +191,19 @@ class ComposableRevealInternalElement extends SkyflowElement { if(domElementSelector instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { - if (domElementSelector.getElementsByTagName('iframe')[0]?.contentWindow) { - const iframeElement = domElementSelector.getElementsByTagName('iframe')[0] - if(iframeElement.name === this.#iframe.name){ - iframeElement?.contentWindow?.postMessage({ - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, properties.IFRAME_SECURE_ORIGIN); + const iframeElements = domElementSelector.getElementsByTagName('iframe'); + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + properties.IFRAME_SECURE_ORIGIN + ); } } }); From 1170930ae81585d65620ebb8154996792ebb8324 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 10:44:46 +0000 Subject: [PATCH 33/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.ff71850 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 325b02da..a818b825 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.fcfbd99", + "version": "2.5.0-beta.9-dev.ff71850", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 2328f56387dc6a0a7f24157b6d9a9057aa781dd8 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 16:57:19 +0530 Subject: [PATCH 34/50] SK-2254: add undefined check for resize observer for multiple iframes --- src/core/external/collect/collect-element.ts | 28 ++++++++++--------- .../reveal/composable-reveal-internal.ts | 26 +++++++++-------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/core/external/collect/collect-element.ts b/src/core/external/collect/collect-element.ts index 5cc87639..7856b62c 100644 --- a/src/core/external/collect/collect-element.ts +++ b/src/core/external/collect/collect-element.ts @@ -209,19 +209,21 @@ class CollectElement extends SkyflowElement { if (domElement instanceof HTMLElement) { this.resizeObserver = new ResizeObserver(() => { const iframeElements = domElement.getElementsByTagName('iframe'); - // eslint-disable-next-line no-plusplus - for (let i = 0; i < iframeElements.length; i++) { - const iframeElement = iframeElements[i]; - if ( - iframeElement.name === this.#iframe.name - && iframeElement.contentWindow - ) { - iframeElement?.contentWindow?.postMessage( - { - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, - properties.IFRAME_SECURE_ORIGIN, - ); + if (iframeElements && iframeElements.length > 0) { + // eslint-disable-next-line no-plusplus + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name + && iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + 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 f9b26bc6..9453a06a 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -192,18 +192,20 @@ class ComposableRevealInternalElement extends SkyflowElement { if(domElementSelector instanceof HTMLElement){ this.resizeObserver = new ResizeObserver(() => { const iframeElements = domElementSelector.getElementsByTagName('iframe'); - for (let i = 0; i < iframeElements.length; i++) { - const iframeElement = iframeElements[i]; - if ( - iframeElement.name === this.#iframe.name && - iframeElement.contentWindow - ) { - iframeElement?.contentWindow?.postMessage( - { - name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, - }, - properties.IFRAME_SECURE_ORIGIN - ); + if (iframeElements && iframeElements.length > 0) { + for (let i = 0; i < iframeElements.length; i++) { + const iframeElement = iframeElements[i]; + if ( + iframeElement.name === this.#iframe.name && + iframeElement.contentWindow + ) { + iframeElement?.contentWindow?.postMessage( + { + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, + }, + properties.IFRAME_SECURE_ORIGIN + ); + } } } }); From 844a1517ebdccf78dbd0c65a8fa305a5115a6d04 Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 11:28:25 +0000 Subject: [PATCH 35/50] [AUTOMATED] Release - 2.5.0-beta.9-dev.3d1ed74 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a818b825..d677e338 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.ff71850", + "version": "2.5.0-beta.9-dev.3d1ed74", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From df87b1a098d69df3093a02ecb4be6b6e6d19da4d Mon Sep 17 00:00:00 2001 From: saileshwar-skyflow Date: Tue, 19 Aug 2025 18:25:59 +0000 Subject: [PATCH 36/50] [AUTOMATED] Release - 2.5.0-beta.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d677e338..6d849e7a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.9-dev.3d1ed74", + "version": "2.5.0-beta.10", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 31f06b9fa2b099d76af31266d2fc4221be55d3fc Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 16 Oct 2025 18:39:26 +0530 Subject: [PATCH 37/50] SK-2330 fix the lint error --- src/core/internal/frame-element-init.ts | 7 +++++-- tests/core/external/collect/composable-container.test.js | 4 +++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index a3b033ca..c615d1e9 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -464,11 +464,14 @@ export default class FrameElementInit { // eslint-disable-next-line consistent-return private multipleUploadFiles = - (fileElement: IFrameFormElement, clientConfig, metaData) => new Promise((rootResolve, rootReject) => { + (fileElement: IFrameFormElement, + clientConfig, metaData) => new Promise((rootResolve, rootReject) => { this.#client = new Client(clientConfig, {}); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); - const { state, tableName, onFocusChange, preserveFileName } = fileElement; + const { + state, tableName, onFocusChange, preserveFileName, + } = fileElement; if (state.isRequired) { onFocusChange(false); } diff --git a/tests/core/external/collect/composable-container.test.js b/tests/core/external/collect/composable-container.test.js index c77561f2..2977ccf1 100644 --- a/tests/core/external/collect/composable-container.test.js +++ b/tests/core/external/collect/composable-container.test.js @@ -18,7 +18,7 @@ const bus = require('framebus'); iframerUtils.getIframeSrc = jest.fn(() => ('https://google.com')); -const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve()); +const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve('token')); const mockUuid = '1234'; jest.mock('../../../../src/libs/uuid', () => ({ @@ -57,6 +57,7 @@ EventEmitter.mockImplementation(()=>({ const metaData = { + getSkyflowBearerToken: getBearerToken, skyflowContainer:{ isControllerFrameReady: true }, @@ -78,6 +79,7 @@ const metaData = { }, }; const metaData2 = { + getSkyflowBearerToken: getBearerToken, skyflowContainer:{ isControllerFrameReady: false }, From 17347101a103f81d8937a8afe4722ada5ec8b78f Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 16 Oct 2025 13:10:13 +0000 Subject: [PATCH 38/50] [AUTOMATED] Release - 2.5.0-beta.10-dev.08ce547 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d849e7a..5f39e47d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.10", + "version": "2.5.0-beta.10-dev.08ce547", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 379940c1fdcb6cbd3e45f624c043d7eccd44add9 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 30 Oct 2025 04:24:12 +0000 Subject: [PATCH 39/50] [AUTOMATED] Release - 2.4.4-dev.9309abf --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5f39e47d..63e8e772 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.5.0-beta.10-dev.08ce547", + "version": "2.4.4-dev.9309abf", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From af7492239304d500781c81843d1b99952801a9fd Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 30 Oct 2025 10:15:07 +0530 Subject: [PATCH 40/50] SK-2360 fix errors --- .../external/reveal/composable-reveal-internal.ts | 14 +++++++------- src/core/internal/internal-types/index.ts | 2 +- src/skyflow.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index 9453a06a..d2e65238 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -111,7 +111,6 @@ class ComposableRevealInternalElement extends SkyflowElement { this.#iframe?.setIframeHeight(data?.height); }); - window?.addEventListener('message', (event) => { if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + this.#iframe?.name) { this.#iframe?.setIframeHeight(event?.data?.data?.height); @@ -189,21 +188,22 @@ class ComposableRevealInternalElement extends SkyflowElement { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ELEMENT_IN_MOUNT, ['RevealElement'], true); } - if(domElementSelector instanceof HTMLElement){ + if (domElementSelector instanceof HTMLElement) { this.resizeObserver = new ResizeObserver(() => { const iframeElements = domElementSelector.getElementsByTagName('iframe'); if (iframeElements && iframeElements.length > 0) { - for (let i = 0; i < iframeElements.length; i++) { + // eslint-disable-next-line no-plusplus + for (let i = 0; i < iframeElements.length; i++) { const iframeElement = iframeElements[i]; if ( - iframeElement.name === this.#iframe.name && - iframeElement.contentWindow + iframeElement.name === this.#iframe.name + && iframeElement.contentWindow ) { iframeElement?.contentWindow?.postMessage( { name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe.name, }, - properties.IFRAME_SECURE_ORIGIN + properties.IFRAME_SECURE_ORIGIN, ); } } @@ -386,7 +386,7 @@ class ComposableRevealInternalElement extends SkyflowElement { MessageType.LOG, loglevel); validateRenderElementRecord(recordData); - window.addEventListener('message', (event) => { + window.addEventListener('message', (event) => { if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + recordData?.name) { this.#isMounted = true; diff --git a/src/core/internal/internal-types/index.ts b/src/core/internal/internal-types/index.ts index 0b71577f..fd594d78 100644 --- a/src/core/internal/internal-types/index.ts +++ b/src/core/internal/internal-types/index.ts @@ -78,7 +78,7 @@ export interface ClientMetadata { } export interface Metadata extends ClientMetadata { - getSkyflowBearerToken: () => Promise | undefined; + getSkyflowBearerToken: () => Promise; clientJSON: ClientToJSON; containerType: ContainerType; skyflowContainer: SkyflowContainer; diff --git a/src/skyflow.ts b/src/skyflow.ts index 456509f0..92e63db7 100644 --- a/src/skyflow.ts +++ b/src/skyflow.ts @@ -175,7 +175,7 @@ class Skyflow { return skyflow; } - #getSkyflowBearerToken = () => new Promise((resolve, reject) => { + #getSkyflowBearerToken: () => Promise = () => new Promise((resolve, reject) => { if ( this.#client.config.getBearerToken && (!this.#bearerToken || !isTokenValid(this.#bearerToken)) From e37a584e45147b563802fa2442230ea6879cd2a5 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 30 Oct 2025 04:45:47 +0000 Subject: [PATCH 41/50] [AUTOMATED] Release - 2.4.4-dev.af74922 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63e8e772..aaf05319 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.4-dev.9309abf", + "version": "2.4.4-dev.af74922", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js", From 761cf54e36c44eef29dd86f14fe23df02fc48e2c Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 30 Oct 2025 10:28:57 +0530 Subject: [PATCH 42/50] SK-2360 fix errors --- src/core-utils/collect.ts | 8 ++++---- .../internal/composable-frame-element-init.ts | 5 ++++- src/core/internal/frame-element-init.ts | 15 ++++++++++++--- src/core/internal/internal-types/index.ts | 2 +- src/core/internal/reveal/reveal-frame.ts | 5 ++++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/core-utils/collect.ts b/src/core-utils/collect.ts index a0fbb98d..47384540 100644 --- a/src/core-utils/collect.ts +++ b/src/core-utils/collect.ts @@ -347,9 +347,9 @@ export const insertDataInCollect = async ( let insertErrorResponse: any; client ?.request({ - body: { + body: JSON.stringify({ records, - }, + }), requestMethod: 'POST', url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, headers: { @@ -391,9 +391,9 @@ export const insertDataInMultipleFiles = async ( let insertErrorResponse: any; client ?.request({ - body: { + body: JSON.stringify({ records, - }, + }), requestMethod: 'POST', url: `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}`, headers: { diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts index 51210e5a..9f97a0d9 100644 --- a/src/core/internal/composable-frame-element-init.ts +++ b/src/core/internal/composable-frame-element-init.ts @@ -68,7 +68,10 @@ export default class RevealComposableFrameElementInit { const data = event?.data?.data ?? {}; const elementIds = data?.elementIds ?? []; const revealDataInput: IRevealRecordComposable[] = []; - this.#client = new Client(event?.data?.clientConfig ?? {}, {}); + this.#client = new Client(event?.data?.clientConfig ?? {}, { + uuid: '', + clientDomain: '', + }); 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 c615d1e9..342b05f8 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -187,7 +187,10 @@ export default class FrameElementInit { }); uploadFiles = (fileElement, clientConfig) => { - this.#client = new Client(clientConfig, {}); + this.#client = new Client(clientConfig, { + uuid: '', + clientDomain: '', + }); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const fileUploadObject: any = {}; @@ -391,7 +394,10 @@ export default class FrameElementInit { error: error?.message, }); } - this.#client = new Client(clientConfig, {}); + this.#client = new Client(clientConfig, { + uuid: '', + clientDomain: '', + }); const client = this.#client; const sendRequest = () => new Promise((rootResolve, rootReject) => { const insertPromiseSet: Promise[] = []; @@ -466,7 +472,10 @@ export default class FrameElementInit { private multipleUploadFiles = (fileElement: IFrameFormElement, clientConfig, metaData) => new Promise((rootResolve, rootReject) => { - this.#client = new Client(clientConfig, {}); + this.#client = new Client(clientConfig, { + uuid: '', + clientDomain: '', + }); if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const { diff --git a/src/core/internal/internal-types/index.ts b/src/core/internal/internal-types/index.ts index fd594d78..2dbcb7ac 100644 --- a/src/core/internal/internal-types/index.ts +++ b/src/core/internal/internal-types/index.ts @@ -78,8 +78,8 @@ export interface ClientMetadata { } export interface Metadata extends ClientMetadata { - getSkyflowBearerToken: () => Promise; clientJSON: ClientToJSON; containerType: ContainerType; skyflowContainer: SkyflowContainer; + getSkyflowBearerToken: () => Promise; } diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index 4476eded..b45b3d78 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -413,7 +413,10 @@ class RevealFrame { }; private renderFile(data: IRevealRecord, clientConfig) { - this.#client = new Client(clientConfig, {}); + this.#client = new Client(clientConfig, { + uuid: '', + clientDomain: '', + }); return new Promise((resolve, reject) => { try { getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) From 097701eabb1689ef66f7666333051e36d4d49517 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Fri, 31 Oct 2025 15:50:57 +0530 Subject: [PATCH 43/50] SK-2360 apply internal typescript changes --- .../reveal/composable-reveal-container.ts | 24 ++++++++++------- .../reveal/composable-reveal-element.ts | 6 ++--- .../reveal/composable-reveal-internal.ts | 27 +++++-------------- src/core/external/reveal/reveal-container.ts | 1 + .../internal/composable-frame-element-init.ts | 7 ++--- src/core/internal/iframe-form/index.ts | 2 +- src/core/internal/internal-types/index.ts | 1 + src/core/internal/reveal/reveal-frame.ts | 15 ++++++----- src/skyflow.ts | 4 +-- src/utils/common/index.ts | 5 ++++ src/utils/helpers/index.ts | 3 +++ 11 files changed, 47 insertions(+), 48 deletions(-) diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index bf635096..fd27a1f2 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -33,10 +33,12 @@ import { import Container from '../common/container'; import ComposableRevealElement from './composable-reveal-element'; -import { RevealElementInput, RevealResponse } from '../../../index-node'; +import { ContainerOptions, RevealElementInput, RevealResponse } from '../../../index-node'; import { IRevealElementInput, IRevealElementOptions } from './reveal-container'; import ComposableRevealInternalElement from './composable-reveal-internal'; import { formatRevealElementOptions } from '../../../utils/helpers'; +import { Metadata, SkyflowElementProps } from '../../internal/internal-types'; +import ComposableContainer, { ComposableElementGroup } from '../collect/compose-collect-container'; const CLASS_NAME = 'ComposableRevealContainer'; class ComposableRevealContainer extends Container { @@ -44,15 +46,15 @@ class ComposableRevealContainer extends Container { #elements: Record = {}; - #metaData: any; + #metaData: Metadata; #elementGroup: any = { rows: [] }; #elementsList:any = []; - #context:Context; + #context: Context; - #skyflowElements:any; + #skyflowElements: Array; #eventEmitter: EventEmitter; @@ -80,7 +82,12 @@ class ComposableRevealContainer extends Container { #getSkyflowBearerToken: () => Promise | undefined; - constructor(options, metaData, skyflowElements, context) { + constructor( + metaData: Metadata, + skyflowElements:Array, + context: Context, + options?: ContainerOptions, + ) { super(); this.#containerId = uuid(); this.#metaData = { @@ -88,7 +95,7 @@ class ComposableRevealContainer extends Container { clientJSON: { ...metaData.clientJSON, config: { - ...metaData.clientJSON.config, + ...metaData.clientJSON?.config, options: { ...metaData.clientJSON.config?.options, ...options, @@ -154,9 +161,9 @@ class ComposableRevealContainer extends Container { }; #createMultipleElement = ( - multipleElements: any, + multipleElements: ComposableElementGroup, isSingleElementAPI: boolean = false, - ) => { + ): ComposableContainer => { try { const elements: any[] = []; this.#tempElements = deepClone(multipleElements); @@ -204,7 +211,6 @@ class ComposableRevealContainer extends Container { type: this.type, eventEmitter: this.#eventEmitter, }, - true, this.#context, ); this.#elements[this.#tempElements.elementName] = element; diff --git a/src/core/external/reveal/composable-reveal-element.ts b/src/core/external/reveal/composable-reveal-element.ts index 0f3f328d..539a017d 100644 --- a/src/core/external/reveal/composable-reveal-element.ts +++ b/src/core/external/reveal/composable-reveal-element.ts @@ -15,9 +15,9 @@ class ComposableRevealElement { #isMounted: boolean = false; - constructor(name, eventEmitter, iframeName) { - this.#elementName = name ?? ''; - this.#iframeName = iframeName ?? ''; + constructor(name: string, eventEmitter: EventEmitter, iframeName: string) { + this.#elementName = name; + this.#iframeName = iframeName; this.#eventEmitter = eventEmitter; this.#eventEmitter?.on?.(`${EventName?.READY ?? 'READY'}:${this.#elementName}`, () => { this.#isMounted = true; diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index d2e65238..77d75a01 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -29,6 +29,7 @@ import properties from '../../../properties'; import { validateInitConfig, validateRenderElementRecord } from '../../../utils/validators'; import EventEmitter from '../../../event-emitter'; import { formatRevealElementOptions } from '../../../utils/helpers'; +import { Metadata, RevealContainerProps } from '../../internal/internal-types'; const CLASS_NAME = 'RevealElementInteranalElement'; @@ -40,11 +41,11 @@ export interface RevealComposableGroup{ class ComposableRevealInternalElement extends SkyflowElement { #iframe: IFrame; - #metaData: any; + #metaData: Metadata; #recordData: any; - #containerId: any; + #containerId: string; #isMounted:boolean = false; @@ -60,34 +61,21 @@ class ComposableRevealInternalElement extends SkyflowElement { #eventEmitter: EventEmitter; - #isFrameReady: boolean; - - #domSelecter: string; - - #clientId: string; - - #isSkyflowFrameReady: boolean = false; - - #isSingleElementAPI: boolean; - #shadowRoot: ShadowRoot | null = null; #getSkyflowBearerToken: () => Promise | undefined; - #composableIframeName!: string; - #isComposableFrameReady: boolean = false; constructor(elementId: string, recordGroup: RevealComposableGroup[], - metaData: any, container: any, isSingleElementAPI: boolean = false, + metaData: Metadata, + container: RevealContainerProps, context: Context) { super(); this.#elementId = elementId; this.#metaData = metaData; this.resizeObserver = null; - this.#clientId = this.#metaData?.uuid; - this.#isSingleElementAPI = isSingleElementAPI; this.#recordData = recordGroup; this.#containerId = container?.containerId; this.#readyToMount = container?.isMounted ?? true; @@ -101,11 +89,8 @@ class ComposableRevealInternalElement extends SkyflowElement { this.#context?.logLevel, ); - this.#domSelecter = ''; - this.#isFrameReady = false; this.#readyToMount = true; this.#getSkyflowBearerToken = metaData?.getSkyflowBearerToken; - this.#isSkyflowFrameReady = metaData?.skyflowContainer?.isControllerFrameReady ?? false; bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + this.#iframe?.name, (data) => { this.#iframe?.setIframeHeight(data?.height); @@ -304,7 +289,7 @@ class ComposableRevealInternalElement extends SkyflowElement { } }; - renderFile(recordData): Promise { + renderFile(recordData: any): Promise { let altText = ''; if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { altText = recordData.altText; diff --git a/src/core/external/reveal/reveal-container.ts b/src/core/external/reveal/reveal-container.ts index fac8027a..a15a7fd0 100644 --- a/src/core/external/reveal/reveal-container.ts +++ b/src/core/external/reveal/reveal-container.ts @@ -150,6 +150,7 @@ class RevealContainer extends Container { containerId: this.#containerId, isMounted: this.#isMounted, eventEmitter: this.#eventEmmiter, + type: ContainerType.REVEAL, }, elementId, this.#context); this.#revealElements.push(revealElement); this.#skyflowElements[elementId] = revealElement; diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts index 9f97a0d9..8e3093a3 100644 --- a/src/core/internal/composable-frame-element-init.ts +++ b/src/core/internal/composable-frame-element-init.ts @@ -187,7 +187,7 @@ export default class RevealComposableFrameElementInit { RevealComposableFrameElementInit.frameEle = new RevealComposableFrameElementInit(); }; - revealData(revealRecords: IRevealRecordComposable[], containerId, authToken) { + revealData(revealRecords: IRevealRecordComposable[], containerId: string, authToken: string) { return new Promise((resolve, reject) => { fetchRecordsByTokenIdComposable(revealRecords, this.#client, authToken)?.then( (resolvedResult) => { @@ -207,8 +207,7 @@ export default class RevealComposableFrameElementInit { styles, errorTextStyles, } = this.group ?? {}; - - const isComposableContainer = getContainerType(window?.name) === ContainerType?.COMPOSABLE; + const isComposableContainer = getContainerType(window?.name) === ContainerType?.COMPOSE_REVEAL; this.group.spacing = getValueAndItsUnit(this.group?.spacing)?.join('') ?? ''; this.rootDiv = document?.createElement('div'); this.rootDiv.className = 'container'; @@ -231,7 +230,6 @@ export default class RevealComposableFrameElementInit { row.spacing = getValueAndItsUnit(row?.spacing)?.join('') ?? ''; const rowDiv = document?.createElement('div'); rowDiv.id = `row-${rowIndex}`; - const intialRowStyles = { 'align-items': row?.alignItems ?? 'stretch', 'justify-content': row?.justifyContent ?? 'flex-start', @@ -262,7 +260,6 @@ export default class RevealComposableFrameElementInit { ...(errorTextStyles?.[STYLE_TYPE?.BASE] ?? {}), }, }; - getCssClassesFromJss?.(errorStyles, 'row-error'); if (errorTextStyles?.[STYLE_TYPE?.GLOBAL]) { generateCssWithoutClass?.(errorTextStyles?.[STYLE_TYPE?.GLOBAL]); diff --git a/src/core/internal/iframe-form/index.ts b/src/core/internal/iframe-form/index.ts index 32d29010..1c2de7ba 100644 --- a/src/core/internal/iframe-form/index.ts +++ b/src/core/internal/iframe-form/index.ts @@ -113,7 +113,7 @@ export default class IFrameFormElement extends EventEmitter { blockEmptyFiles: boolean = false; - constructor(name: string, label: string, metaData, context: Context, skyflowID?: string) { + constructor(name: string, label: string, metaData: any, context: Context, skyflowID?: string) { super(); const frameValues = name.split(':'); const fieldType = frameValues[1]; diff --git a/src/core/internal/internal-types/index.ts b/src/core/internal/internal-types/index.ts index 2dbcb7ac..6b5d08aa 100644 --- a/src/core/internal/internal-types/index.ts +++ b/src/core/internal/internal-types/index.ts @@ -44,6 +44,7 @@ export interface RevealContainerProps { containerId: string; isMounted: boolean; eventEmitter: EventEmitter; + type: string; } export interface InternalState { diff --git a/src/core/internal/reveal/reveal-frame.ts b/src/core/internal/reveal/reveal-frame.ts index b45b3d78..973e0a2a 100644 --- a/src/core/internal/reveal/reveal-frame.ts +++ b/src/core/internal/reveal/reveal-frame.ts @@ -85,7 +85,7 @@ class RevealFrame { parsedRecord.context, skyflowContainerId); } - constructor(record, context, id, rootDiv?) { + constructor(record, context: Context, id: string, rootDiv?: HTMLDivElement) { this.#skyflowContainerId = id; this.#name = rootDiv ? record?.name : window.name; this.#containerId = getValueFromName(this.#name, 2); @@ -390,7 +390,7 @@ class RevealFrame { getData = () => this.#record; - private sub2 = (responseUrl) => { + private sub2 = (responseUrl: { iframeName?: string; error?: string; url?: string }) => { if (responseUrl.iframeName === this.#name) { if (Object.prototype.hasOwnProperty.call(responseUrl, 'error') && responseUrl.error === DEFAULT_FILE_RENDER_ERROR) { this.setRevealError(DEFAULT_FILE_RENDER_ERROR); @@ -406,13 +406,14 @@ class RevealFrame { }, ); } else { - const ext = this.getExtension(responseUrl.url); - this.addFileRender(responseUrl.url, ext); + const ext = this.getExtension(responseUrl.url as string); + this.addFileRender(responseUrl.url as string, ext); } } }; - private renderFile(data: IRevealRecord, clientConfig) { + private renderFile(data: IRevealRecord, clientConfig): + Promise | undefined { this.#client = new Client(clientConfig, { uuid: '', clientDomain: '', @@ -445,7 +446,7 @@ class RevealFrame { } // eslint-disable-next-line class-methods-use-this - private getExtension(url) { + private getExtension(url: string) { try { const params = new URL(url).searchParams; const name = params.get('response-content-disposition'); @@ -459,7 +460,7 @@ class RevealFrame { } } - private addFileRender(responseUrl, ext) { + private addFileRender(responseUrl: string, ext: string) { let tag = ''; if (typeof ext === 'string' && ext.includes('image')) { tag = 'img'; diff --git a/src/skyflow.ts b/src/skyflow.ts index 92e63db7..1a9980c2 100644 --- a/src/skyflow.ts +++ b/src/skyflow.ts @@ -270,7 +270,7 @@ class Skyflow { case ContainerType.COMPOSE_REVEAL: { validateComposableContainerOptions(options!); - const revealComposableContainer = new ComposableRevealContainer(options, { + const revealComposableContainer = new ComposableRevealContainer({ ...this.#metadata, clientJSON: this.#client.toJSON(), containerType: type, @@ -278,7 +278,7 @@ class Skyflow { getSkyflowBearerToken: this.#getSkyflowBearerToken, }, this.#skyflowElements, - { logLevel: this.#logLevel, env: this.#env }); + { logLevel: this.#logLevel, env: this.#env }, options); printLog(parameterizedString(logs.infoLogs.REVEAL_CONTAINER_CREATED, CLASS_NAME), MessageType.LOG, this.#logLevel); diff --git a/src/utils/common/index.ts b/src/utils/common/index.ts index 57737439..851285a4 100644 --- a/src/utils/common/index.ts +++ b/src/utils/common/index.ts @@ -322,6 +322,11 @@ export interface ICollectOptions { export interface MetaData { [key: string]: any, } +export interface EventConfig{ + authToken: string, + vaultURL: string, + vaultID: string, +} export interface UploadFilesResponse { fileUploadResponse?: Record, diff --git a/src/utils/helpers/index.ts b/src/utils/helpers/index.ts index 6f86f350..58fa0a43 100644 --- a/src/utils/helpers/index.ts +++ b/src/utils/helpers/index.ts @@ -231,6 +231,9 @@ export const styleToString = (style) => Object.keys(style).reduce((acc, key) => export const getContainerType = (frameName:string):ContainerType => { const frameNameParts = frameName.split(':'); + if (frameNameParts[0] === 'reveal-composable') { + return ContainerType.COMPOSE_REVEAL; + } return (frameNameParts[1] === 'group') ? ContainerType.COMPOSABLE : ContainerType.COLLECT; From ec39118052d9c835b7fd7bc097c8a3abaab390ab Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Thu, 6 Nov 2025 18:37:51 +0530 Subject: [PATCH 44/50] SK-2360 update import statements --- src/index-node.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/index-node.ts b/src/index-node.ts index a9b55d8e..08f5ea04 100644 --- a/src/index-node.ts +++ b/src/index-node.ts @@ -68,5 +68,6 @@ export { default as ComposableElement } from './core/external/collect/compose-co export { default as RevealContainer } from './core/external/reveal/reveal-container'; export { default as RevealElement } from './core/external/reveal/reveal-element'; export { default as ThreeDS } from './core/external/threeds/threeds'; - +export { default as ComposableRevealContainer } from './core/external/reveal/composable-reveal-container'; +export { default as ComposableRevealElement } from './core/external/reveal/composable-reveal-element'; export default Skyflow; From d179f4392aff235d82c97c042c9feae8050dfc07 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Fri, 14 Nov 2025 21:49:55 +0530 Subject: [PATCH 45/50] SK-2360 added unit tests --- .../collect/compose-collect-container.ts | 2 +- .../collect/compose-collect-element.ts | 4 +- .../reveal/composable-reveal-container.ts | 16 +- .../reveal/composable-reveal-internal.ts | 163 +---- src/core/external/reveal/reveal-container.ts | 2 +- tests/core-utils/reveal.test.js | 310 +++++++- .../collect/collect-container.test.js | 7 +- .../collect/collect-container.test.ts | 1 + .../collect/composable-container.test.js | 125 +++- .../collect/composable-container.test.ts | 33 +- .../reveal-composable-container.test.js | 503 +++++++++++++ .../reveal/reveal-composable-element.test.js | 222 ++++++ .../reveal/reveal-composable-internal.test.js | 669 ++++++++++++++++++ .../external/reveal/reveal-container.test.ts | 52 +- .../external/reveal/reveal-element.test.js | 13 +- .../external/reveal/reveal-element.test.ts | 64 +- .../core/internal/frame-element-init.test.js | 55 +- tests/core/internal/frame-elements.test.js | 39 +- .../internal/iframe-form/iframe-form.test.js | 11 +- tests/core/internal/internal-index.test.js | 8 +- .../core/internal/reveal/reveal-frame.test.js | 79 ++- 21 files changed, 2061 insertions(+), 317 deletions(-) create mode 100644 tests/core/external/reveal/reveal-composable-container.test.js create mode 100644 tests/core/external/reveal/reveal-composable-element.test.js create mode 100644 tests/core/external/reveal/reveal-composable-internal.test.js diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index d63ab17e..29d28c57 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -360,7 +360,7 @@ class ComposableContainer extends Container { authToken, }, options: { - ...data.options, + ...data?.options, }, }, ); diff --git a/src/core/external/collect/compose-collect-element.ts b/src/core/external/collect/compose-collect-element.ts index e0696be3..b5aa0837 100644 --- a/src/core/external/collect/compose-collect-element.ts +++ b/src/core/external/collect/compose-collect-element.ts @@ -40,8 +40,8 @@ class ComposableElement { }); this.#metaData = metaData; this.#context = { - logLevel: this.#metaData.clientJSON?.config?.options?.logLevel, - env: this.#metaData.clientJSON?.config?.options?.env, + logLevel: this.#metaData?.clientJSON?.config?.options?.logLevel, + env: this.#metaData?.clientJSON?.config?.options?.env, }; this.#elementType = this.#metaData?.type as ElementType; } diff --git a/src/core/external/reveal/composable-reveal-container.ts b/src/core/external/reveal/composable-reveal-container.ts index fd27a1f2..4eb2cb14 100644 --- a/src/core/external/reveal/composable-reveal-container.ts +++ b/src/core/external/reveal/composable-reveal-container.ts @@ -93,17 +93,17 @@ class ComposableRevealContainer extends Container { this.#metaData = { ...metaData, clientJSON: { - ...metaData.clientJSON, + ...metaData?.clientJSON, config: { - ...metaData.clientJSON?.config, + ...metaData?.clientJSON?.config, options: { - ...metaData.clientJSON.config?.options, + ...metaData?.clientJSON?.config?.options, ...options, }, }, }, }; - this.#getSkyflowBearerToken = metaData.getSkyflowBearerToken; + this.#getSkyflowBearerToken = metaData?.getSkyflowBearerToken; this.#skyflowElements = skyflowElements; this.#context = context; this.#options = options; @@ -431,7 +431,7 @@ class ComposableRevealContainer extends Container { MessageType.LOG, this.#context.logLevel); window.addEventListener('message', (messagEevent) => { - if (messagEevent.data.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + if (messagEevent?.data?.type === ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#containerId) { this.#emitEvent( ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + this.#containerId, { @@ -449,10 +449,10 @@ class ComposableRevealContainer extends Container { }, ); window.addEventListener('message', (event) => { - if (event.data.type + if (event?.data?.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + this.#containerId) { - const revealData = event.data.data; - if (revealData.errors) { + const revealData = event?.data?.data; + if (revealData?.errors) { printLog(parameterizedString(logs.errorLogs.FAILED_REVEAL), MessageType.ERROR, this.#context.logLevel); reject(revealData); diff --git a/src/core/external/reveal/composable-reveal-internal.ts b/src/core/external/reveal/composable-reveal-internal.ts index 77d75a01..bce2fe92 100644 --- a/src/core/external/reveal/composable-reveal-internal.ts +++ b/src/core/external/reveal/composable-reveal-internal.ts @@ -68,7 +68,7 @@ class ComposableRevealInternalElement extends SkyflowElement { #isComposableFrameReady: boolean = false; constructor(elementId: string, - recordGroup: RevealComposableGroup[], + recordGroup, metaData: Metadata, container: RevealContainerProps, context: Context) { @@ -294,7 +294,7 @@ class ComposableRevealInternalElement extends SkyflowElement { if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { altText = recordData.altText; } - this.setAltText('loading...'); + this.setAltText('loading...', recordData); const loglevel = this.#context.logLevel; if (this.#isComposableFrameReady) { return new Promise((resolve, reject) => { @@ -304,7 +304,6 @@ class ComposableRevealInternalElement extends SkyflowElement { MessageType.LOG, loglevel); validateRenderElementRecord(recordData); - this.#getSkyflowBearerToken()?.then((authToken) => { printLog(parameterizedString(logs.infoLogs.BEARER_TOKEN_RESOLVED, CLASS_NAME), MessageType.LOG, @@ -335,7 +334,7 @@ class ComposableRevealInternalElement extends SkyflowElement { ), MessageType.ERROR, this.#context.logLevel); if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { - this.setAltText(altText); + this.setAltText(altText, recordData); } reject(revealData?.error || revealData?.errors); } else { @@ -400,15 +399,15 @@ class ComposableRevealInternalElement extends SkyflowElement { + recordData.name) { if (event1?.data?.data?.type === REVEAL_TYPES.RENDER_FILE) { const revealData = event1?.data?.data?.result; - if (revealData?.error) { + if (revealData?.error || revealData?.errors) { printLog(parameterizedString( logs.errorLogs.FAILED_RENDER, ), MessageType.ERROR, this.#context.logLevel); if (Object.prototype.hasOwnProperty.call(recordData, 'altText')) { - this.setAltText(altText); + this.setAltText(altText, recordData); } - reject(revealData); + reject(revealData?.error || revealData?.errors); } else { // eslint-disable-next-line max-len printLog(parameterizedString(logs.infoLogs.RENDER_SUBMIT_SUCCESS, CLASS_NAME), @@ -447,11 +446,6 @@ class ComposableRevealInternalElement extends SkyflowElement { return this.#isMounted; } - hasToken():boolean { - if (this.#recordData.token) return true; - return false; - } - isClientSetError():boolean { return this.#isClientSetError; } @@ -460,134 +454,51 @@ class ComposableRevealInternalElement extends SkyflowElement { return this.#recordData; } + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars setErrorOverride(clientErrorText: string) { - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: true, - clientErrorText, - }); - } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: true, - clientErrorText, - }); - }); - } - this.#isClientSetError = true; + // to be implemented } + // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars setError(clientErrorText:string) { - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: true, - clientErrorText, - }); - } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - this.#isMounted = true; - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: true, - clientErrorText, - }); - }); - } - this.#isClientSetError = true; + // to be implemented } + // eslint-disable-next-line class-methods-use-this resetError() { - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: false, - }); - } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - this.#isMounted = true; - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR + this.#iframe.name, { - name: this.#iframe.name, - isTriggerError: false, - }); - }); - } - this.#isClientSetError = false; + // to be implemented } - setAltText(altText:string) { - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, - updatedValue: altText, - }); + setAltText(altText:string, record) { + if (this.#isComposableFrameReady) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record?.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record?.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }, + ); } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - this.#isMounted = true; - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, - updatedValue: altText, - }); - }); - } - } - - clearAltText() { - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, - updatedValue: null, + window.addEventListener('message', (event) => { + if (event.data.type === ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + + record?.name) { + this.#emitEvent( + ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record?.name, + { + name: ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + record?.name, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, + updatedValue: altText, + }, + ); + } }); - } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - this.#isMounted = true; - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, - updatedValue: null, - }); - }); } } - setToken(token:string) { - this.#recordData = { - ...this.#recordData, - token, - }; - if (this.#isMounted) { - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, - updatedValue: token, - }); - } else { - bus - .target(properties.IFRAME_SECURE_ORIGIN) - .on(ELEMENT_EVENTS_TO_CLIENT.MOUNTED + this.#iframe.name, () => { - this.#isMounted = true; - bus.emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + this.#iframe.name, { - name: this.#iframe.name, - updateType: REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, - updatedValue: token, - }); - }); - } + // eslint-disable-next-line class-methods-use-this + clearAltText() { + // to be implemented } unmount() { diff --git a/src/core/external/reveal/reveal-container.ts b/src/core/external/reveal/reveal-container.ts index a15a7fd0..f4e1b80b 100644 --- a/src/core/external/reveal/reveal-container.ts +++ b/src/core/external/reveal/reveal-container.ts @@ -79,7 +79,7 @@ class RevealContainer extends Container { options?: ContainerOptions, ) { super(); - this.#isSkyflowFrameReady = metaData.skyflowContainer.isControllerFrameReady; + this.#isSkyflowFrameReady = metaData?.skyflowContainer?.isControllerFrameReady; this.#metaData = { ...metaData, clientJSON: { diff --git a/tests/core-utils/reveal.test.js b/tests/core-utils/reveal.test.js index 5ced5b97..1e45bb22 100644 --- a/tests/core-utils/reveal.test.js +++ b/tests/core-utils/reveal.test.js @@ -2,9 +2,11 @@ Copyright (c) 2022 Skyflow, Inc. */ import Skyflow from '../../src/skyflow'; -import {formatRecordsForClient,formatRecordsForIframe, formatRecordsForRender, formatForRenderClient, getFileURLFromVaultBySkyflowID, getFileURLForRender} from "../../src/core-utils/reveal"; +import {formatRecordsForClient, formatRecordsForClientComposable, formatRecordsForIframe, formatRecordsForRender, formatForRenderClient, getFileURLFromVaultBySkyflowID, getFileURLForRender, getFileURLFromVaultBySkyflowIDComposable, fetchRecordsByTokenIdComposable} from "../../src/core-utils/reveal"; import { Env, LogLevel } from '../../src/utils/common'; +import { getAccessToken } from '../../src/utils/bus-events'; import Client from '../../src/client'; +import { url } from 'inspector'; const testTokenId = '1677f7bd-c087-4645-b7da-80a6fd1a81a4'; const testInvalidTokenId = '80a6fd1a81a4-b7da-c087-4645'; @@ -57,7 +59,11 @@ const skyflow = Skyflow.init({ }); jest.setTimeout(15000); - +jest.mock('../../src/utils/bus-events', () => ({ + getAccessToken: jest.fn( + () => Promise.resolve('mockAccessToken') + ), +})); describe('Reveal PureJs- get() Method Input', () => { test('should throw error for Empty Input Array', (done) => { skyflow.detokenize([]).catch((err) => { @@ -82,19 +88,7 @@ describe('Reveal PureJs- get() Method Input', () => { expect(err).toBeDefined(); done(); }); - }); - // test('should throw error for Missing redaction Property', (done) => { - // skyflow.detokenize({ - // records: [ - // { - // token: testTokenId, - // }, - // ], - // }).catch((err) => { - // expect(err).toBeDefined(); - // done(); - // }); - // }); + }); test('should throw error for Empty string in id value', (done) => { skyflow.detokenize({ records: [ @@ -129,6 +123,29 @@ describe("formatRecordsForClient fn test",()=>{ expect(fnResponse.success).toBeUndefined(); }); }); +describe("formatRecordsForClientComposable fn test",()=>{ + test("only success records",()=>{ + const testInput = {"records":[{"token":"7402-2242-2342-232","value":"231", "valueType" : "STRING"}] } + const fnResponse = formatRecordsForClientComposable(testInput, {"7402-2242-2342-232": "231"}); + expect(fnResponse.success.length).toBe(1); + expect(fnResponse.errors).toBeUndefined(); + }); + test("both success and error records",()=>{ + const testInput = {"records":[{"token":"7402-2242-2342-232","value":"231", "valueType" : "STRING"}],"errors":[{"token":"3232-6434-3253-4221"}]}; + const fnResponse = formatRecordsForClientComposable(testInput,{"7402-2242-2342-232": "231"}); + expect(fnResponse.errors.length).toBe(1); + expect(fnResponse.success.length).toBe(1); + }); + test("only error records",()=>{ + const testInput = {"errors":[{ + "token":"3232-6434-3253-4221", + "error":"token not found" + }]}; + const fnResponse = formatRecordsForClientComposable(testInput); + expect(fnResponse.errors.length).toBe(1); + expect(fnResponse.success).toBeUndefined(); + }); +}); describe("formatRecordsForIframe fn test",()=>{ test("no records should return empty object",()=>{ @@ -164,7 +181,7 @@ describe("formatRecordsForRender fn test",()=>{ "url": "http://dummy.com",}); }); }); -describe("formatRecordsForIframe fn test",()=>{ +describe("formatForRenderClient fn test",()=>{ test("no records should return empty object",()=>{ const testInput = {}; const fnResponse = formatForRenderClient(testInput, 'col'); @@ -178,9 +195,27 @@ describe("formatRecordsForIframe fn test",()=>{ const fnResponse = formatForRenderClient(testInput, 'col'); expect(fnResponse).toStrictEqual({ success :{"column": "col", "skyflow_id": "id", + "fileMetadata": undefined, }}); }); + test("errors case", ()=>{ + const errorResponse = { + "errors": { + "skyflowId" : "id", + "column": "col", + "error": "token not found" + } + + } + const fnResponse = formatForRenderClient(errorResponse, 'col'); + expect(fnResponse).toStrictEqual({ errors :{ + "skyflowId" : "id", + "column": "col", + "error": "token not found" + }}); + }) +}); describe('getFileURLFromVaultBySkyflowID', () => { it('should resolve with the file URL when the promise is resolved', async () => { const mockSkyflowIdRecord = { @@ -193,7 +228,7 @@ describe('getFileURLFromVaultBySkyflowID', () => { // console.log(mockClient.toJSON().metaData); const result = getFileURLFromVaultBySkyflowID(mockSkyflowIdRecord, mockClient); - console.log(result); + jest.spyOn(mockClient, 'request').mockResolvedValue('mockFileURL'); expect(result).toBeDefined(); }); @@ -205,11 +240,247 @@ describe('getFileURLFromVaultBySkyflowID', () => { }; const mockClient = Client.fromJSON(clientData.clientJSON); - // console.log(mockClient.toJSON().metaData); + jest.spyOn(mockClient, 'request').mockRejectedValue({error: { + code: '500', + description: 'Internal Server Error', + } + }); + + await expect(getFileURLFromVaultBySkyflowID(mockSkyflowIdRecord, mockClient)).rejects.toEqual({ + error: { + code: '500', + description: 'Internal Server Error', + }, + column: 'mockColumn', + skyflowId: 'mockSkyflowID', + }); + }); + it('should reject with an error when the promise is root rejected', async () => { + const mockSkyflowIdRecord = { + column: 'mockColumn', + skyflowID: 'mockSkyflowID', + table: 'mockTable', + }; + + const mockClient = Client.fromJSON(clientData.clientJSON); + jest.spyOn(mockClient, 'request').mockImplementation(() => { + throw { + error: { + code: '500', + description: 'Internal Server Error', + } + }; + }); - expect(getFileURLFromVaultBySkyflowID(mockSkyflowIdRecord, mockClient)).rejects.toThrow(); + await expect(getFileURLFromVaultBySkyflowID(mockSkyflowIdRecord, mockClient)).rejects.toEqual({ + error: { + code: '500', + description: 'Internal Server Error', + }, + }); }); }); + +describe('getFileURLFromVaultBySkyflowID for composable reveal', () => { + it('should resolve with the file URL when the promise is resolved', async () => { + const mockSkyflowIdRecord = { + column: 'mockColumn', + skyflowID: 'mockSkyflowID', + table: 'mockTable', + }; + + const mockClient = Client.fromJSON(clientData.clientJSON); + // mock getFileURLForRender + jest.spyOn(mockClient, 'request').mockResolvedValue('mockFileURL'); + + const result = getFileURLFromVaultBySkyflowIDComposable(mockSkyflowIdRecord, mockClient, "token"); + console.log('result',result); + expect(result).toBeDefined(); + }); + + it('should reject with an error when the promise is rejected', async () => { + const mockSkyflowIdRecord = { + column: 'mockColumn', + skyflowID: 'mockSkyflowID', + table: 'mockTable', + }; + + const mockClient = Client.fromJSON(clientData.clientJSON); + jest.spyOn(mockClient, 'request').mockRejectedValue({error: { + code: '500', + description: 'Internal Server Error', + } + }); + + await expect(getFileURLFromVaultBySkyflowIDComposable(mockSkyflowIdRecord, mockClient, "token")).rejects.toEqual({ + error: { + code: '500', + description: 'Internal Server Error', + }, + column: 'mockColumn', + skyflowId: 'mockSkyflowID', + }); + }); + it('should reject with an error when the promise is root rejected', async () => { + const mockSkyflowIdRecord = { + column: 'mockColumn', + skyflowID: 'mockSkyflowID', + table: 'mockTable', + }; + + const mockClient = Client.fromJSON(clientData.clientJSON); + jest.spyOn(mockClient, 'request').mockImplementation(() => { + throw { + error: { + code: '500', + description: 'Internal Server Error', + } + }; + }); + + await expect(getFileURLFromVaultBySkyflowIDComposable(mockSkyflowIdRecord, mockClient, "token")).rejects.toEqual({ + error: { + code: '500', + description: 'Internal Server Error', + }, + }); + }); +}); + +describe('fetchRecordsByTokenIdComposable', () => { + it('should resolve with records when all tokens are successfully detokenized', async () => { + const mockTokenRecords = [ + { token: 'token1', iframeName: 'iframe1', redaction: 'PLAIN_TEXT' }, + { token: 'token2', iframeName: 'iframe2', redaction: 'PLAIN_TEXT' }, + ]; + + const mockClient = Client.fromJSON(clientData.clientJSON); + + jest.spyOn(mockClient, 'request') + .mockResolvedValueOnce({ records: [{ token: 'token1', value: 'value1', valueType: 'STRING' }] }) + .mockResolvedValueOnce({ records: [{ token: 'token2', value: 'value2', valueType: 'STRING' }] }); + + const result = await fetchRecordsByTokenIdComposable(mockTokenRecords, mockClient, 'mockToken'); + + expect(result).toBeDefined(); + expect(result.records).toHaveLength(2); + expect(mockClient.request).toHaveBeenCalledTimes(2); + }); + + it('should reject with errors when all tokens fail to detokenize', async () => { + const mockTokenRecords = [ + { token: 'token1', iframeName: 'iframe1', redaction: 'PLAIN_TEXT' }, + ]; + + const mockClient = Client.fromJSON(clientData.clientJSON); + + jest.spyOn(mockClient, 'request').mockRejectedValue({ + error: { + code: '404', + description: 'Token not found', + }, + }); + + await expect(fetchRecordsByTokenIdComposable(mockTokenRecords, mockClient, 'mockToken')) + .rejects.toEqual({ + errors: expect.arrayContaining([ + expect.objectContaining({ + token: 'token1', + error: expect.any(Object), + }), + ]), + }); + }); + + it('should reject with both records and errors when some tokens succeed and some fail', async () => { + const mockTokenRecords = [ + { token: 'token1', iframeName: 'iframe1', redaction: 'PLAIN_TEXT' }, + { token: 'token2', iframeName: 'iframe2', redaction: 'PLAIN_TEXT' }, + ]; + + const mockClient = Client.fromJSON(clientData.clientJSON); + + jest.spyOn(mockClient, 'request') + .mockResolvedValueOnce({ records: [{ token: 'token1', value: 'value1', valueType: 'STRING' }] }) + .mockRejectedValueOnce({ + error: { + code: '404', + description: 'Token not found', + }, + }); + + await expect(fetchRecordsByTokenIdComposable(mockTokenRecords, mockClient, 'mockToken')) + .rejects.toEqual({ + records: expect.any(Array), + errors: expect.any(Array), + }); + }); + + it('should use default PLAIN_TEXT redaction when redaction is not provided', async () => { + const mockTokenRecords = [ + { token: 'token1', iframeName: 'iframe1' }, // No redaction + ]; + + const mockClient = Client.fromJSON(clientData.clientJSON); + + jest.spyOn(mockClient, 'request').mockResolvedValue({ + records: [{ token: 'token1', value: 'value1', valueType: 'STRING' }], + }); + + await fetchRecordsByTokenIdComposable(mockTokenRecords, mockClient, 'mockToken'); + + expect(mockClient.request).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.stringContaining('PLAIN_TEXT'), + }) + ); + }); + + it('should handle empty token records array', async () => { + const mockClient = Client.fromJSON(clientData.clientJSON); + + const result = await fetchRecordsByTokenIdComposable([], mockClient, 'mockToken'); + + expect(result).toEqual({ records: [] }); + }); + + it('should include frameId in the response', async () => { + const mockTokenRecords = [ + { token: 'token1', iframeName: 'iframe1', redaction: 'PLAIN_TEXT' }, + ]; + + const mockClient = Client.fromJSON(clientData.clientJSON); + + jest.spyOn(mockClient, 'request').mockResolvedValue({ + records: [{ token: 'token1', value: 'value1', valueType: 'STRING' }], + }); + + const result = await fetchRecordsByTokenIdComposable(mockTokenRecords, mockClient, 'mockToken'); + + expect(result.records[0]).toHaveProperty('frameId', 'iframe1'); + }); +}); +describe("formatRecordsForClient fn test",()=>{ + test("only success records",()=>{ + const testInput = {"records":[{"token":"7402-2242-2342-232","value":"231", "valueType" : "STRING"}] } + const fnResponse = formatRecordsForClient(testInput, {"7402-2242-2342-232": "231"}); + expect(fnResponse.success.length).toBe(1); + expect(fnResponse.errors).toBeUndefined(); + }); + test("both success and error records",()=>{ + const testInput = {"records":[{"token":"7402-2242-2342-232","value":"231", "valueType" : "STRING"}],"errors":[{"token":"3232-6434-3253-4221"}]}; + const fnResponse = formatRecordsForClient(testInput,{"7402-2242-2342-232": "231"}); + expect(fnResponse.errors.length).toBe(1); + expect(fnResponse.success.length).toBe(1); + }); + test("only error records",()=>{ + const testInput = {"errors":[{"token":"3232-6434-3253-4221"}]}; + const fnResponse = formatRecordsForClient(testInput); + expect(fnResponse.errors.length).toBe(1); + expect(fnResponse.success).toBeUndefined(); + }); +}); + describe('getFileURLForRender', () => { it('should return the file URL when the request is successful', async () => { const mockSkyflowIdRecord = { @@ -240,4 +511,3 @@ describe('getFileURLForRender', () => { expect(getFileURLForRender(mockSkyflowIdRecord, mockClient, 'mockAuthToken')).rejects.toThrow(); }); }); -}); diff --git a/tests/core/external/collect/collect-container.test.js b/tests/core/external/collect/collect-container.test.js index cbb0d666..6876f546 100644 --- a/tests/core/external/collect/collect-container.test.js +++ b/tests/core/external/collect/collect-container.test.js @@ -1618,7 +1618,7 @@ describe('Collect container', () => { expect(err).toBeDefined(); }) }); - it('test collect and additional fields duplicate elements',(done)=>{ + it('test collect and additional fields duplicate elements',()=>{ const div1 = document.createElement('div'); const div2 = document.createElement('div'); @@ -1632,11 +1632,12 @@ describe('Collect container', () => { done(res) }).catch((err)=>{ expect(err).toBeDefined(); - done(); + // done(); }); + }catch(err){ expect(err).toBeDefined(); - done(err) + // done() } }); diff --git a/tests/core/external/collect/collect-container.test.ts b/tests/core/external/collect/collect-container.test.ts index fd778506..fbbf0954 100644 --- a/tests/core/external/collect/collect-container.test.ts +++ b/tests/core/external/collect/collect-container.test.ts @@ -62,6 +62,7 @@ const metaData: Metadata = { skyflowContainer: { isControllerFrameReady: true, } as unknown as SkyflowContainer, + getSkyflowBearerToken: getBearerToken, }; const metaData2: Metadata = { diff --git a/tests/core/external/collect/composable-container.test.js b/tests/core/external/collect/composable-container.test.js index 2977ccf1..3229ced1 100644 --- a/tests/core/external/collect/composable-container.test.js +++ b/tests/core/external/collect/composable-container.test.js @@ -1,6 +1,7 @@ import { COLLECT_FRAME_CONTROLLER, - ELEMENT_EVENTS_TO_IFRAME + ELEMENT_EVENTS_TO_IFRAME, + ElementType } from '../../../../src/core/constants'; import * as iframerUtils from '../../../../src/iframe-libs/iframer'; import { LogLevel, Env, ValidationRuleType } from '../../../../src/utils/common'; @@ -139,9 +140,16 @@ const cardNumberElement = { column: 'primary_card.card_number', type: 'CARD_NUMBER', ...collectStylesOptions, - }; +const FileInuptElement = { + table: 'pii_fields', + column: 'profile_picture', + type: ElementType.FILE_INPUT, + skyflowID:'id1', + ...collectStylesOptions, +} + const ExpirationDateElement = { table: 'pii_fields', column: 'primary_card.expiry', @@ -176,6 +184,7 @@ describe('test composable container class',()=>{ let targetSpy; let onSpy; let eventEmitterSpy; + let windowSpy; beforeEach(() => { emitSpy = jest.spyOn(bus, 'emit'); targetSpy = jest.spyOn(bus, 'target'); @@ -185,6 +194,7 @@ describe('test composable container class',()=>{ on, off: jest.fn() }); + windowSpy = jest.spyOn(window, "window", "get"); }); @@ -242,10 +252,6 @@ describe('test composable container class',()=>{ }); it('test collect with success and error scenarios', async () => { - // let readyCb; - // on.mockImplementation((name, cb) => { - // readyCb = cb; - // }); const div = document.createElement('div'); div.id = 'composable'; @@ -286,19 +292,29 @@ describe('test composable container class',()=>{ ], }; - const collectPromiseSuccess = container.collect(options); - - const collectCb1 = emitSpy.mock.calls[0][2]; - collectCb1(collectResponse); - + const collectPromiseSuccess = + container.collect(options); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + '1234', // containerId + data: {...collectResponse} + } + })); + const successResult = await collectPromiseSuccess; expect(successResult).toEqual(collectResponse); - - const collectPromiseError = container.collect(options); - const collectCb2 = emitSpy.mock.calls[1][2]; - collectCb2({ error: 'Error occurred' }); - - await expect(collectPromiseError).rejects.toEqual('Error occurred'); + + const collectPromiseError = + container.collect(options); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + '1234', // containerId + data: { error: "Error occured"} + } + })); + await expect(collectPromiseError).rejects.toEqual("Error occured"); + }); it('test collect when isMount is false', async () => { let readyCb; @@ -546,5 +562,80 @@ describe('test composable container class',()=>{ container.on("CHANGE",()=>{}); expect(element).toBeInstanceOf(ComposableElement); }); + it('test upload FILES with success and error scenarios', async () => { + const div = document.createElement('div'); + div.id = 'composable'; + document.body.append(div); + + const container = new ComposableContainer( + metaData, + {}, + context, + { layout: [1], styles: { base: { width: '100px' } } } + ); + + const element1 = container.create(FileInuptElement); + container.mount('#composable'); + const options = {}; + + const collectPromiseSuccess = container.uploadFiles(options); + + // Wait for the bearer token promise to resolve and event listener to be set up + await Promise.resolve('token'); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + '1234', // containerId + data: { fileUploadResponse: [{ skyflow_id: 'id1' }] } + } + })); + + const successResult = await collectPromiseSuccess; + expect(successResult).toEqual({ fileUploadResponse: [{ skyflow_id: 'id1' }] }); + // Test error scenario + const collectPromiseError = container.uploadFiles(options); + + await Promise.resolve('token'); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + '1234', // containerId + data: { error: "Error occured"} + } + })); + + await expect(collectPromiseError).rejects.toEqual("Error occured"); + + // Test error scenario case 2 - no fileUploadResponse and no error + const collectPromiseError2 = container.uploadFiles(options); + + await Promise.resolve('token'); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + '1234', // containerId + data: { errors: "Error occured"} + } + })); + + await expect(collectPromiseError2).rejects.toEqual({ errors: "Error occured"}); + }); + it('test upload FILES when bearer token fails', async () => { + const getBearerTokenFail = jest.fn().mockRejectedValue({ error: 'token generation failed' }); + const metaDataFail = { + ...metaData, + getSkyflowBearerToken: getBearerTokenFail, + }; + + const div = document.createElement('div'); + div.id = 'composable2'; + document.body.append(div); + + const container = new ComposableContainer(metaDataFail, {}, context, { layout: [1] }); + const element1 = container.create(FileInuptElement); + container.mount('#composable2'); + + await expect(container.uploadFiles({})).rejects.toEqual({ error: 'token generation failed' }); + }); }); \ No newline at end of file diff --git a/tests/core/external/collect/composable-container.test.ts b/tests/core/external/collect/composable-container.test.ts index 94eee38d..f2af6f76 100644 --- a/tests/core/external/collect/composable-container.test.ts +++ b/tests/core/external/collect/composable-container.test.ts @@ -24,6 +24,13 @@ import SkyflowError from "../../../../src/libs/skyflow-error"; import SkyflowContainer from "../../../../src/core/external/skyflow-container"; import { ContainerType } from "../../../../src/skyflow"; import { Metadata } from "../../../../src/core/internal/internal-types"; +import IFrame from "../../../../src/core/external/common/iframe"; + +global.ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn(), + unobserve: jest.fn(), +})); const bus = require("framebus"); @@ -37,7 +44,7 @@ jest.mock("../../../../src/iframe-libs/iframer", () => { return mockedModule; }); -const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve()); +const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve("token")); const mockUuid = "1234"; jest.mock("../../../../src/libs/uuid", () => ({ @@ -96,6 +103,7 @@ const metaData: Metadata = { clientDomain: "http://abc.com", }, }, + getSkyflowBearerToken: getBearerToken, skyflowContainer: { isControllerFrameReady: true, } as unknown as SkyflowContainer, @@ -257,7 +265,6 @@ describe("test composable container class", () => { const div = document.createElement("div"); div.id = "composable"; document.body.append(div); - const container = new ComposableContainer(metaData, [], context, { layout: [2], styles: { base: { width: "100px" } }, @@ -267,7 +274,6 @@ describe("test composable container class", () => { const element2 = container.create(cardNumberElement); container.mount("#composable"); - const options: ICollectOptions = { tokens: true, additionalFields: { @@ -290,19 +296,26 @@ describe("test composable container class", () => { const collectPromiseSuccess: Promise = container.collect(options); - - const collectCb1 = emitSpy.mock.calls[0][2]; - collectCb1(collectResponse); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + '1234', // containerId + data: {...collectResponse} + } + })); const successResult = await collectPromiseSuccess; expect(successResult).toEqual(collectResponse); const collectPromiseError: Promise = container.collect(options); - const collectCb2 = emitSpy.mock.calls[1][2]; - collectCb2({ error: "Error occurred" }); - - await expect(collectPromiseError).rejects.toEqual("Error occurred"); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + '1234', // containerId + data: { error: "Error occured"} + } + })); + await expect(collectPromiseError).rejects.toEqual("Error occured"); }); it("tests collect when isMount is false", async () => { diff --git a/tests/core/external/reveal/reveal-composable-container.test.js b/tests/core/external/reveal/reveal-composable-container.test.js new file mode 100644 index 00000000..76ca5333 --- /dev/null +++ b/tests/core/external/reveal/reveal-composable-container.test.js @@ -0,0 +1,503 @@ +/* +Copyright (c) 2022 Skyflow, Inc. +*/ +import RevealContainer from "../../../../src/core/external/reveal/reveal-container"; +import { ComposableRevealContainer, ComposableRevealElement } from "../../../../src/index-node"; +import { ELEMENT_EVENTS_TO_CLIENT, ELEMENT_EVENTS_TO_CONTAINER, ELEMENT_EVENTS_TO_IFRAME, REVEAL_FRAME_CONTROLLER, REVEAL_TYPES } from "../../../../src/core/constants"; +import bus from "framebus"; +import { LogLevel,Env } from "../../../../src/utils/common"; +import RevealElement from "../../../../src/core/external/reveal/reveal-element"; +import * as iframerUtils from '../../../../src/iframe-libs/iframer'; +import SKYFLOW_ERROR_CODE from "../../../../src/utils/constants"; +import { parameterizedString } from "../../../../src/utils/logs-helper"; +import SkyflowError from "../../../../src/libs/skyflow-error"; +import logs from "../../../../src/utils/logs"; + +iframerUtils.getIframeSrc = jest.fn(() => ('https://google.com')); +const mockUuid = '1234'; +jest.mock('../../../../src/libs/uuid',()=>({ + __esModule: true, + default:jest.fn(()=>(mockUuid)), +})); + +const on = jest.fn(); +const off = jest.fn(); +jest.setTimeout(40000); +describe("Reveal Composable Container Class", () => { + let emitSpy; + let targetSpy; + beforeEach(() => { + jest.clearAllMocks(); + emitSpy = jest.spyOn(bus, 'emit'); + targetSpy = jest.spyOn(bus, 'target'); + targetSpy.mockReturnValue({ + on, + off + }); + }); + const getBearerToken = jest.fn().mockImplementation(() => Promise.resolve('token')); + const testMetaData = { + getSkyflowBearerToken: getBearerToken, + skyflowContainer: { + isControllerFrameReady: true, + }, + uuid: "123", + config: { + vaultID: "vault123", + vaultURL: "https://sb.vault.dev.com", + getBearerToken, + }, + metaData: { + clientDomain: "http://abc.com", + }, + }; + const testRevealContainer = new ComposableRevealContainer(testMetaData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] +}); + const skyflowConfig = { + vaultID: 'e20afc3ae1b54f0199f24130e51e0c11', + vaultURL: 'https://testurl.com', + getBearerToken, + }; + + const clientData = { + getSkyflowBearerToken: getBearerToken, + skyflowContainer: { + isControllerFrameReady: false, + }, + uuid: '1234', + client: { + config: { ...skyflowConfig }, + metaData: { + uuid: "1234", + }, + }, + clientJSON: { + config: { + vaultID: 'vault123', + vaultURL: 'https://sb.vault.dev', + getBearerToken, + }, + }, + } + + const clientData2 = { + skyflowContainer: { + isControllerFrameReady: true, + }, + uuid: '1234', + client: { + config: { ...skyflowConfig }, + metaData: { + uuid: "1234", + }, + }, + clientJSON:{ + context: { logLevel: LogLevel.ERROR,env:Env.PROD}, + metaData: { + uuid: "1234", + }, + config:{ + ...skyflowConfig, + getBearerToken + } + } + } + + const testRecord = { + token: "1677f7bd-c087-4645-b7da-80a6fd1a81a4", + // redaction: RedactionType.PLAIN_TEXT, + label: "", + styles: { + base: { + color: "#32ce21", + }, + }, + }; + test("reveal should throw error with no elements", (done) => { + const container = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }); + container.reveal().catch((error) => { + done(); + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(SkyflowError); + expect(error.error.code).toEqual(400); + expect(error.error.description).toEqual(logs.errorLogs.NO_ELEMENTS_IN_COMPOSABLE); + }) + }); + + test("constructor", () => { + expect(testRevealContainer).toBeInstanceOf(ComposableRevealContainer); + expect(testRevealContainer).toBeInstanceOf(Object); + expect(testRevealContainer).toHaveProperty("create"); + expect(testRevealContainer).toHaveProperty("reveal"); + expect(testRevealContainer).toHaveProperty("type"); + }); + test("create() will return a Reveal composable element", () => { + const testRevealElement = testRevealContainer.create(testRecord); + expect(testRevealElement).toBeInstanceOf(ComposableRevealElement); + }); + test("create() will throw error if record id invalid", () => { + try { + testRevealContainer.create({ + token: "", + // redaction: RedactionType.REDACTED, + }); + } catch (error) { + expect(error.message).toBe("Invalid Token Id "); + } + try { + testRevealContainer.create({ + token: true, + // redaction: RedactionType.PLAIN_TEXT, + }); + } catch (error) { + expect(error.message).toBe("Invalid Token Id true"); + } + }); + + test('create() will throw error for invalid input format options',(done)=>{ + try { + testRevealContainer.create({ + token: "1244", + },{ + format:undefined + }); + done('should throw error'); + } catch (error) { + expect(error.error.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_INPUT_OPTIONS_FORMAT.description)); + done(); + } + }); + + test("on container mounted call back",()=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + const div = document.createElement('div'); + div.id = 'container'; + document.body.appendChild(div); + testRevealContainer.mount('#container'); + }); +// test("on container mounted call back 5",()=>{ +// const testRevealContainer = new RevealContainer(clientData, {}, { logLevel: LogLevel.ERROR,env:Env.PROD }); +// testRevealContainer.create({ +// token: "token", +// }); +// const data = { +// token: "1815-6223-1073-1425", +// containerId:mockUuid +// } +// const eventName = ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED+mockUuid +// bus.emit(eventName,data); +// const onCbName = on.mock.calls[0][0]; +// expect(onCbName).toBe(eventName); +// const onCb = on.mock.calls[0][1]; +// onCb(data); +// testRevealContainer.reveal(); +// const frameEventName = ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + mockUuid +// const onframeEvent = on.mock.calls[1][0]; +// expect(frameEventName).toBe(onframeEvent); +// const onCbFrame = on.mock.calls[1][1]; +// onCbFrame({}); +// const emitEventName = emitSpy.mock.calls[1][0]; +// const emitCb = emitSpy.mock.calls[1][2]; +// expect(emitEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS+mockUuid); +// emitCb({"success":[{token:"1815-6223-1073-1425"}]}); +// }); + +// test("on container mounted else call back",()=>{ +// const testRevealContainer = new RevealContainer(clientData, {}, { logLevel: LogLevel.ERROR,env:Env.PROD }); +// testRevealContainer.create({ +// token: "1815-6223-1073-1425", +// }); +// const data = { +// token: "1815-6223-1073-1425", +// containerId:mockUuid +// } + +// testRevealContainer.reveal().catch(err => { +// console.log(err); +// }); +// const eventName = ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED+mockUuid +// bus.emit(eventName,data); +// const onCbName = on.mock.calls[0][0]; +// expect(onCbName).toBe(eventName); +// const onCb = on.mock.calls[0][1]; +// onCb(data); + +// const frameEventName = ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + mockUuid +// const onframeEvent = on.mock.calls[1][0]; +// expect(frameEventName).toBe(onframeEvent); +// const onCbFrame = on.mock.calls[1][1]; +// onCbFrame({}); + +// const emitEventName = emitSpy.mock.calls[1][0]; +// const emitCb = emitSpy.mock.calls[1][2]; +// expect(emitEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS+mockUuid); +// emitCb({error:{code:404,description:"Not Found"}}); +// }); +// test("on container mounted else call back 1",()=>{ +// const testRevealContainer = new RevealContainer(clientData, {}, { logLevel: LogLevel.ERROR,env:Env.PROD }); +// testRevealContainer.create({ +// token: "1815-6223-1073-1425", +// }); +// const data = { +// token: "1815-6223-1073-1425", +// containerId:mockUuid +// } + + +// testRevealContainer.reveal(); +// const eventName = ELEMENT_EVENTS_TO_CONTAINER.ELEMENT_MOUNTED+mockUuid +// bus.emit(eventName,data); + +// const onCbName = on.mock.calls[0][0]; +// expect(onCbName).toBe(eventName); +// const onCb = on.mock.calls[0][1]; +// onCb(data); +// const frameEventName = ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY + mockUuid +// const onframeEvent = on.mock.calls[1][0]; +// expect(frameEventName).toBe(onframeEvent); +// const onCbFrame = on.mock.calls[1][1]; +// onCbFrame({}); + +// const emitEventName = emitSpy.mock.calls[1][0]; +// const emitCb = emitSpy.mock.calls[1][2]; +// expect(emitEventName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_REQUESTS+mockUuid); +// emitCb({"success":[{token:"1815-6223-1073-1425"}]}); +// }); + test("reveal before skyflow frame ready event",async ()=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + + const res = testRevealContainer.reveal(); + await Promise.resolve('token'); + expect(res).toBeInstanceOf(Promise); //ELEMENT_EVENTS_TO_CLIENT.MOUNTED + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: data + } + })); + await Promise.resolve(); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + mockUuid, + data: {"success":[{token:"1815-6223-1073-1425"}]} + } + })); + + await expect(res).resolves.toEqual({"success":[{token:"1815-6223-1073-1425"}]}); + }); + test("reveal before skyflow frame ready event, Error case",async ()=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + + const res = testRevealContainer.reveal(); + await Promise.resolve('token'); + expect(res).toBeInstanceOf(Promise); //ELEMENT_EVENTS_TO_CLIENT.MOUNTED + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: data + } + })); + await Promise.resolve(); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + mockUuid, + data: {"errors":{ + code:404, + description:"Not Found" + }} + } + })); + + await expect(res).rejects.toEqual({"errors":{code:404,description:"Not Found"}}); + }); + test("reveal before skyflow frame ready event, Error case when bearer token step failed",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 + } + + const res = testRevealContainer.reveal(); + + await expect(res).rejects.toEqual({errors:{code:400,description:"Failed to fetch bearer token"}}); + }); + + /// frame ready event + test("reveal before skyflow frame ready event",async ()=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: data + } + })); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + + const res = testRevealContainer.reveal(); + await Promise.resolve('token'); + expect(res).toBeInstanceOf(Promise); //ELEMENT_EVENTS_TO_CLIENT.MOUNTED + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + mockUuid, + data: {"success":[{token:"1815-6223-1073-1425"}]} + } + })); + + await expect(res).resolves.toEqual({"success":[{token:"1815-6223-1073-1425"}]}); + }); + test("reveal before skyflow frame ready event, Error case",async ()=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: data + } + })); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + + const res = testRevealContainer.reveal(); + await Promise.resolve('token'); + expect(res).toBeInstanceOf(Promise); //ELEMENT_EVENTS_TO_CLIENT.MOUNTED + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + mockUuid, + data: {"errors":{ + code:404, + description:"Not Found" + }} + } + })); + + await expect(res).rejects.toEqual({"errors":{code:404,description:"Not Found"}}); + }); + test("reveal before skyflow frame ready event, Error case when bearer token step failed",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] + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: data + } + })); + testRevealContainer.create({ + token: "1815-6223-1073-1425", + }); + const data = { + token: "1815-6223-1073-1425", + containerId:mockUuid + } + + const res = testRevealContainer.reveal(); + + await expect(res).rejects.toEqual({errors:{code:400,description:"Failed to fetch bearer token"}}); + }); + + test("reveal when elment is empty when skyflow ready",(done)=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + mockUuid, + data: 'data' + } + })); + testRevealContainer.reveal().catch((error) => { + done(); + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(SkyflowError); + expect(error.error.code).toEqual(400); + expect(error.error.description).toEqual(logs.errorLogs.NO_ELEMENTS_IN_COMPOSABLE); + }) + }); + test("reveal when elment is empty when skyflow frame not ready",(done)=>{ + const testRevealContainer = new ComposableRevealContainer(clientData, [], { logLevel: LogLevel.ERROR,env:Env.PROD }, { + layout:[1] + }); + testRevealContainer.reveal().catch((error) => { + done(); + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(SkyflowError); + expect(error.error.code).toEqual(400); + expect(error.error.description).toEqual(logs.errorLogs.NO_ELEMENTS_IN_COMPOSABLE); + }) + }); +}); diff --git a/tests/core/external/reveal/reveal-composable-element.test.js b/tests/core/external/reveal/reveal-composable-element.test.js new file mode 100644 index 00000000..47a7df73 --- /dev/null +++ b/tests/core/external/reveal/reveal-composable-element.test.js @@ -0,0 +1,222 @@ +/* +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 SkyflowContainer from '../../../../src/core/external/skyflow-container'; +import Client from '../../../../src/client'; +import EventEmitter from "../../../../src/event-emitter"; +import * as busEvents from '../../../../src/utils/bus-events'; +import ComposableRevealInternalElement from "../../../../src/core/external/reveal/composable-reveal-internal"; + +import bus from "framebus"; +import { JSDOM } from 'jsdom'; +import { ComposableRevealElement, EventName, RedactionType } from "../../../../src/index-node"; + +busEvents.getAccessToken = jest.fn(() => Promise.reject('access token')); + +const mockUuid = '1234'; +jest.mock('../../../../src/libs/uuid',()=>({ + __esModule: true, + default:jest.fn(()=>(mockUuid)), +})); +// const _on = jest.fn(); +// const _off = jest.fn(); +// const _emit = jest.fn(); +const getBearerToken = jest.fn(); + +const groupEmittFn = jest.fn(); +let groupOnCb; +jest.mock('../../../../src/libs/jss-styles', () => { + return { + __esModule: true, + default: jest.fn(), + generateCssWithoutClass: jest.fn(), + getCssClassesFromJss: jest.fn().mockReturnValue({ + base: { color: 'red' }, + global: { backgroundColor: 'black' } + }) + }; +}); +jest.mock('../../../../src/core/external/skyflow-container', () => { + return { + __esModule: true, + default: jest.fn(), + } +}) + +// jest.mock('../../../../src/core/external/reveal/composable-reveal-internal') + +// bus.on = _on; +// bus.target = jest.fn().mockReturnValue({ +// on: _on, +// }); +// bus.off = _off; +// bus.emit = _emit; + +const clientDomain = "http://abc.com"; +const skyflowConfig = { + vaultID: 'e20afc3ae1b54f0199f24130e51e0c11', + vaultURL: 'https://testurl.com', + getBearerToken: jest.fn(), + options: { trackMetrics: true, trackingKey: "key" } +}; +let controller = new SkyflowContainer(client,{ + logLevel:LogLevel.DEBUG, + env:Env.DEV +}); + +const clientData = { + uuid: '123', + client: { + config: { ...skyflowConfig }, + metadata: { uuid :'123', + skyflowContainer: controller, + }, + }, + clientJSON:{ + context: { logLevel: LogLevel.ERROR,env:Env.PROD}, + config:{ + ...skyflowConfig, + getBearerToken:jest.fn().toString() + } + }, + skyflowContainer: { + isControllerFrameReady: true + }, + clientDomain: clientDomain, +} +const client = new Client(clientData.client.config, clientData); + +const on = jest.fn(); +const off = jest.fn(); +let skyflowContainer; +describe("Reveal Composable Element Class", () => { + let emitSpy; + let targetSpy; + beforeEach(() => { + jest.clearAllMocks(); + emitSpy = jest.spyOn(bus, 'emit'); + targetSpy = jest.spyOn(bus, 'target'); + targetSpy.mockReturnValue({ + on, + off + }); + const client = new Client(clientData.client.config, clientData); + skyflowContainer = new SkyflowContainer(client, { logLevel: LogLevel.DEBUG, env: Env.PROD }); + }); + + test("constructor", () => { + const eventEmitter = new EventEmitter(); + const testRevealElement = new ComposableRevealElement( + "name", + eventEmitter, + '123', + ); + eventEmitter._emit(`${EventName.READY}:name`, {}); + expect(testRevealElement).toBeInstanceOf(ComposableRevealElement); + + }); + test("iframe name", () => { + const testRevealElement = new ComposableRevealElement( + "name", + new EventEmitter(), + '123', + ); + expect(testRevealElement.iframeName()).toBe('123'); + }); + test("getID", () => { + const testRevealElement = new ComposableRevealElement( + "name", + new EventEmitter(), + '123', + ); + expect(testRevealElement.getID()).toBe('name'); + }); + test("file render call success case", 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({ success: { skyflow_id: '1244', column: 'column' } }); + }); + 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).resolves.toEqual({ success: { skyflow_id: '1244', column: 'column' } }); + }); + test("file render call error case 1", 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({ errors: { skyflow_id: '1244', column: 'column', error:{ + code: 400, description: "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": {"column": "column", "error": {"code": 400, "description": "No Records Found"}, "skyflow_id": "1244"}}); + }); + 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" + } + }); + }); + 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( + "name", + eventEmitter, + '123', + ); + eventEmitter.on(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ':name', (data, cb) => { + expect(data).toEqual({ + options: {redaction: RedactionType.MASKED}, + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS + }); + }); + const testEmptyDiv = document.createElement("div"); + testEmptyDiv.setAttribute("id", "testDiv"); + document.body.appendChild(testEmptyDiv); + expect(document.getElementById("testDiv")).not.toBeNull(); + + testRevealElement.update({redaction: RedactionType.MASKED}) + }); +}); diff --git a/tests/core/external/reveal/reveal-composable-internal.test.js b/tests/core/external/reveal/reveal-composable-internal.test.js new file mode 100644 index 00000000..94e30695 --- /dev/null +++ b/tests/core/external/reveal/reveal-composable-internal.test.js @@ -0,0 +1,669 @@ +/* +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 RevealElement from "../../../../src/core/external/reveal/reveal-element"; +import SkyflowContainer from '../../../../src/core/external/skyflow-container'; +import Client from '../../../../src/client'; +import ComposableRevealInternalElement from "../../../../src/core/external/reveal/composable-reveal-internal"; +import * as busEvents from '../../../../src/utils/bus-events'; + + + +import bus from "framebus"; +import { JSDOM } from 'jsdom'; +import EventEmitter from "../../../../src/event-emitter"; +import { error } from "console"; + +busEvents.getAccessToken = jest.fn(() => Promise.reject('access token')); + +const mockUuid = '1234'; +const elementId = 'id'; +jest.mock('../../../../src/libs/uuid',()=>({ + __esModule: true, + default:jest.fn(()=>(mockUuid)), +})); +// const _on = jest.fn(); +// const _off = jest.fn(); +// const _emit = jest.fn(); +const getBearerToken = jest.fn().mockResolvedValue('token'); + +const groupEmittFn = jest.fn(); +let groupOnCb; +const groupEmiitter = { + _emit: groupEmittFn, + on:jest.fn().mockImplementation((args,cb)=>{ + groupOnCb = cb; + }) +} +jest.mock('../../../../src/libs/jss-styles', () => { + return { + __esModule: true, + default: jest.fn(), + generateCssWithoutClass: jest.fn(), + getCssClassesFromJss: jest.fn().mockReturnValue({ + base: { color: 'red' }, + global: { backgroundColor: 'black' } + }) + }; +}); +jest.mock('../../../../src/core/external/skyflow-container', () => { + return { + __esModule: true, + default: jest.fn(), + } +}) + +// bus.on = _on; +// bus.target = jest.fn().mockReturnValue({ +// on: _on, +// }); +// bus.off = _off; +// bus.emit = _emit; + +const clientDomain = "http://abc.com"; +const metaData = { + uuid: "123", + skyflowContainer: { + isControllerFrameReady: true, + }, + config: { + vaultID: "vault123", + vaultURL: "https://sb.vault.dev", + getAccessToken: getBearerToken, + }, + metaData: { + vaultID: "vault123", + vaultURL: "https://sb.vault.dev" + }, + clientDomain: clientDomain, + getSkyflowBearerToken: getBearerToken, + clientJSON:{ + context: { logLevel: LogLevel.ERROR,env:Env.PROD}, + config:{ + ...skyflowConfig, + getBearerToken:jest.fn().toString() + } + }, +}; +const skyflowConfig = { + vaultID: 'e20afc3ae1b54f0199f24130e51e0c11', + vaultURL: 'https://testurl.com', + getBearerToken: jest.fn(), + options: { trackMetrics: true, trackingKey: "key" } +}; +let controller = new SkyflowContainer(client,{ + logLevel:LogLevel.DEBUG, + env:Env.DEV +}); + +const clientData = { + uuid: '123', + client: { + config: { ...skyflowConfig }, + metadata: { uuid :'123', + skyflowContainer: controller, + }, + }, + clientJSON:{ + context: { logLevel: LogLevel.ERROR,env:Env.PROD}, + config:{ + ...skyflowConfig, + getBearerToken:jest.fn().toString() + } + }, + skyflowContainer: { + isControllerFrameReady: true + }, + clientDomain: clientDomain, + getSkyflowBearerToken: getBearerToken, +} +const client = new Client(clientData?.client?.config, clientData); + +const on = jest.fn(); +const off = jest.fn(); +let skyflowContainer; +describe("Reveal Element Class", () => { + let emitSpy; + let targetSpy; + beforeEach(() => { + jest.clearAllMocks(); + emitSpy = jest.spyOn(bus, 'emit'); + targetSpy = jest.spyOn(bus, 'target'); + targetSpy.mockReturnValue({ + on, + off + }); + const client = new Client(clientData.client.config, clientData); + skyflowContainer = new SkyflowContainer(client, { logLevel: LogLevel.DEBUG, env: Env.PROD }); + }); + + const containerId = mockUuid; + test("constructor", () => { + const testRevealElement = new ComposableRevealInternalElement( + elementId, + [], + metaData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + expect(testRevealElement).toBeInstanceOf(ComposableRevealInternalElement); + }); + test("Mount Method", () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element1" + }] + }] + }; + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + metaData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element1", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + }); + test("file render call success case", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element1", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element1", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { + expect(response).toBeDefined(); + expect(response).toEqual({ success: { skyflow_id: '1244', column: 'file' } }); + }); + await Promise.resolve('token'); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + "element1", + containerId: mockUuid, + data: { + result: { success: { skyflow_id: '1244', column: 'file' } }, + type: REVEAL_TYPES.RENDER_FILE, + } + } + })); + }); + test("file render call error case", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element1", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element1", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { + expect(response).toBeDefined(); + expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); + }); + await Promise.resolve('token'); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + "element1", + containerId: mockUuid, + data: { + result: { errors: { skyflow_id: '1244', column: 'file', error:{ + code: 400, + description: "No Records Found", + } } }, + type: REVEAL_TYPES.RENDER_FILE, + } + } + })); + }); + test("file render call error case", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element1", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element1", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { + expect(response).toBeDefined(); + expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); + }); + await Promise.resolve('token'); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + "element1", + containerId: mockUuid, + data: { + result: { errors: { skyflow_id: '1244', column: 'file', error:{ + code: 400, + description: "No Records Found", + } } }, + type: REVEAL_TYPES.RENDER_FILE, + } + } + })); + }); + test("file render call success case when mount event not happened ", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { + expect(response).toBeDefined(); + expect(response).toEqual({ success: { skyflow_id: '1244', column: 'file' } }); + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + await Promise.resolve('token'); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + "element2", + containerId: mockUuid, + data: { + result: { success: { skyflow_id: '1244', column: 'file' } }, + type: REVEAL_TYPES.RENDER_FILE, + } + } + })); + }); + test("file render call error case when mount event not happened ", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { + expect(response).toBeDefined(); + expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + await Promise.resolve('token'); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_CALL_RESPONSE + "element2", + containerId: mockUuid, + data: { + result: { errors: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }, + type: REVEAL_TYPES.RENDER_FILE, + } + } + })); + }); + test("file render call error case when mount event not happened when bearer token call rejected", async () => { + // Mock the bearer token to reject + const getBearerTokenFail = jest.fn().mockRejectedValue({ + error: {code: 400, description: "BEARER TOKEN FAILED"} + }); + + const clientDataFail = { + ...clientData, + getSkyflowBearerToken: getBearerTokenFail, + }; + + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientDataFail, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + // Capture the callback response + let callbackResponse; + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { + callbackResponse = response; + console.log('Callback response received:', response); + }); + + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + + // Wait for the async bearer token call to complete + await new Promise(resolve => setTimeout(resolve, 50)); + + // Assert the callback was called with the error + expect(callbackResponse).toBeDefined(); + expect(callbackResponse).toEqual({error: {error: {code: 400, description: "BEARER TOKEN FAILED"}}}); + }); + test("file render call error case when bearer token call rejected", async () => { + // Mock the bearer token to reject + const getBearerTokenFail = jest.fn().mockRejectedValue({ + error: {code: 400, description: "BEARER TOKEN FAILED"} + }); + + const clientDataFail = { + ...clientData, + getSkyflowBearerToken: getBearerTokenFail, + }; + + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientDataFail, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + // Capture the callback response + let callbackResponse; + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { + callbackResponse = response; + console.log('Callback response received:', response); + }); + + + // Wait for the async bearer token call to complete + await new Promise(resolve => setTimeout(resolve, 50)); + + // Assert the callback was called with the error + expect(callbackResponse).toBeDefined(); + expect(callbackResponse).toEqual({error: {error: {code: 400, description: "BEARER TOKEN FAILED"}}}); + }); + test("update call error case when container is mounted", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ":element2", { + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + options: { altText: "Updated Alt Text" } + }); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + }); + test("update call error case when container is not mounted", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + 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 = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(document.querySelector("iframe")?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ":element2", { + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + options: { altText: "Updated Alt Text" } + }); + + + }); +}); \ No newline at end of file diff --git a/tests/core/external/reveal/reveal-container.test.ts b/tests/core/external/reveal/reveal-container.test.ts index 59f39777..e6516402 100644 --- a/tests/core/external/reveal/reveal-container.test.ts +++ b/tests/core/external/reveal/reveal-container.test.ts @@ -63,6 +63,7 @@ const testMetaData: Metadata = { skyflowContainer: { isControllerFrameReady: true, } as unknown as SkyflowContainer, + getSkyflowBearerToken: getBearerToken, }; const testMetaData2: Metadata = { @@ -135,6 +136,23 @@ describe("Reveal Container Class", () => { expect(testRevealContainer1).toHaveProperty("reveal"); expect(testRevealContainer1).toHaveProperty("type"); }); + test("reveal when elment is empty when skyflow ready", async() => { + const errPromise = testRevealContainer2.reveal() + await expect(errPromise).rejects.toEqual(new Error(logs.errorLogs.NO_ELEMENTS_IN_REVEAL)) + }); + + test("reveal when element is empty when skyflow frame not ready", (done) => { + testRevealContainer1.reveal().catch((error: RevealResponse) => { + done(); + expect(error).toBeDefined(); + expect(error).toBeInstanceOf(SkyflowError); + expect(error?.error).toBeDefined(); + expect(error?.error?.code).toEqual(400); + expect(error?.error?.description).toEqual( + logs.errorLogs.NO_ELEMENTS_IN_REVEAL + ); + }); + }); test("create() will return a Reveal Element", () => { const testRevealElement = testRevealContainer1.create(testRecord); @@ -388,11 +406,12 @@ describe("Reveal Container Class", () => { done(); expect(error).toBeDefined(); expect(error.errors).toBeDefined(); - expect(error.errors![0].error.code).toEqual(400); - expect(error.errors![0].error.description).toEqual( + expect(error.errors![0].code).toEqual(400); + expect(error.errors![0].description).toEqual( logs.errorLogs.REVEAL_ELEMENT_ERROR_STATE ); }); + element.resetError(); }); test("reveal before skyflow frame ready", (done) => { @@ -403,35 +422,10 @@ describe("Reveal Container Class", () => { done(); expect(error).toBeDefined(); expect(error.errors).toBeDefined(); - expect(error.errors![0].error.code).toEqual(400); - expect(error.errors![0].error.description).toEqual( + expect(error.errors![0].code).toEqual(400); + expect(error.errors![0].description).toEqual( logs.errorLogs.REVEAL_ELEMENT_ERROR_STATE ); }); }); - - test("reveal when elment is empty when skyflow ready", (done) => { - testRevealContainer2.reveal().catch((error: RevealResponse) => { - done(); - expect(error).toBeDefined(); - expect(error.errors).toBeDefined(); - expect(error.errors![0].error.code).toEqual(400); - expect(error.errors![0].error.description).toEqual( - logs.errorLogs.NO_ELEMENTS_IN_REVEAL - ); - }); - }); - - test("reveal when element is empty when skyflow frame not ready", (done) => { - testRevealContainer1.reveal().catch((error: RevealResponse) => { - done(); - expect(error).toBeDefined(); - expect(error).toBeInstanceOf(SkyflowError); - expect(error.errors).toBeDefined(); - expect(error.errors![0].error.code).toEqual(400); - expect(error.errors![0].error.description).toEqual( - logs.errorLogs.NO_ELEMENTS_IN_REVEAL - ); - }); - }); }); diff --git a/tests/core/external/reveal/reveal-element.test.js b/tests/core/external/reveal/reveal-element.test.js index b7dc57d7..f0c380cd 100644 --- a/tests/core/external/reveal/reveal-element.test.js +++ b/tests/core/external/reveal/reveal-element.test.js @@ -438,22 +438,13 @@ describe("Reveal Element Class", () => { data => console.log('data', data) ).catch ( (error) => { - expect(error).toEqual({ - "errors": { - "grpc_code": 5, - "http_code": 404, - "message": "No Records Found", - "http_status": "Not Found", - "details": [] - } - } - ); + 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}); const emitCb = emitSpy.mock.calls[3][2]; - emitCb({ errors: { grpc_code: 5, http_code: 404, message: "No Records Found", http_status: "Not Found", details: [] } }); + emitCb({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); }); test("Mount Method with ready to mount false", () => { diff --git a/tests/core/external/reveal/reveal-element.test.ts b/tests/core/external/reveal/reveal-element.test.ts index 35ecdb71..fa0872bf 100644 --- a/tests/core/external/reveal/reveal-element.test.ts +++ b/tests/core/external/reveal/reveal-element.test.ts @@ -8,6 +8,7 @@ import { ELEMENT_EVENTS_TO_CLIENT, REVEAL_TYPES, REVEAL_ELEMENT_OPTIONS_TYPES, + ElementType, } from "../../../../src/core/constants"; import RevealElement from "../../../../src/core/external/reveal/reveal-element"; import SkyflowContainer from "../../../../src/core/external/skyflow-container"; @@ -95,6 +96,7 @@ const metaData: Metadata = { clientDomain: clientDomain, }, }, + getSkyflowBearerToken: getBearerToken, skyflowContainer: { isControllerFrameReady: true, } as unknown as SkyflowContainer, @@ -193,6 +195,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -209,6 +212,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -256,6 +260,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -303,6 +308,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -380,6 +386,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -435,6 +442,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -489,6 +497,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: true, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -517,45 +526,17 @@ describe("Reveal Element Class", () => { }); expect(testRevealElement.isMounted()).toBe(true); expect(testRevealElement.iframeName()).toBe(testIframeName); - testRevealElement - .renderFile() - .then((data) => console.log("data", data)) - .catch((error) => { - expect(error).toEqual({ - errors: { - grpc_code: 5, - http_code: 404, - message: "No Records Found", - http_status: "Not Found", - details: [], - }, - }); - }); - - 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, + 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}); const emitCb = emitSpy.mock.calls[3][2]; - emitCb({ - errors: { - grpc_code: 5, - http_code: 404, - message: "No Records Found", - http_status: "Not Found", - details: [], - }, - }); + emitCb({ errors: { skyflowId:'1244', error: "No Records Found", column: "Not column" } }); }); test("Mount method with ready to mount false", () => { @@ -567,6 +548,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -599,6 +581,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -631,6 +614,7 @@ describe("Reveal Element Class", () => { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, + type: ContainerType.REVEAL }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } @@ -647,7 +631,8 @@ describe("Reveal Element Methods", () => { }, undefined, metaData, - { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter }, + { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, type: ContainerType.REVEAL + }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } ); @@ -690,7 +675,8 @@ describe("Reveal Element Methods", () => { }, undefined, metaData, - { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter }, + { containerId: containerId, isMounted: false, eventEmitter: groupEmiitter, type: ContainerType.REVEAL + }, elementId, { logLevel: LogLevel.ERROR, env: Env.PROD } ); diff --git a/tests/core/internal/frame-element-init.test.js b/tests/core/internal/frame-element-init.test.js index 5a696f65..e68934cb 100644 --- a/tests/core/internal/frame-element-init.test.js +++ b/tests/core/internal/frame-element-init.test.js @@ -96,18 +96,31 @@ describe('FrameElementInit Additional Test Cases', () => { name: ``, location: { href: `http://localhost/?${btoa(JSON.stringify({record:element, metaData: {clientDomain: 'http://localhost.com'}}))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })) const onSpy = jest.spyOn(bus, 'on'); const frameElement = new FrameElementInit('CARD_NUMBER') expect(() => FrameElementInit.startFrameElement('CARD_NUMBER')).not.toThrow(); - console.log('mock calsss====', on.mock.calls) }); test('should handle invalid base64 encoded URL data', () => { windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`, - location: { href: 'http://localhost/?invalid_base64_data' } + location: { href: 'http://localhost/?invalid_base64_data' }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); expect(() => FrameElementInit.startFrameElement()).toThrowError({'message':'The string to be decoded contains invalid characters.'}); @@ -121,7 +134,14 @@ describe('FrameElementInit Additional Test Cases', () => { name: id, // Fix constant reference location: { href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); FrameElementInit.startFrameElement(); @@ -143,7 +163,14 @@ describe('FrameElementInit Additional Test Cases', () => { windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`, - location: { href: `http://localhost/?${btoa(JSON.stringify(mockData))}` } + location: { href: `http://localhost/?${btoa(JSON.stringify(mockData))}` }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); expect(() => FrameElementInit.startFrameElement()).not.toThrow(); @@ -158,7 +185,14 @@ describe('FrameElementInit Additional Test Cases', () => { href: `http://localhost/?${btoa( JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }) )}` - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); expect(() => FrameElementInit.startFrameElement()).not.toThrow(); @@ -179,7 +213,14 @@ describe('FrameElementInit Additional Test Cases', () => { href: `http://localhost/?${btoa( JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }) )}` - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); FrameElementInit.startFrameElement(); diff --git a/tests/core/internal/frame-elements.test.js b/tests/core/internal/frame-elements.test.js index 9e3dc6bf..f85bf2bd 100644 --- a/tests/core/internal/frame-elements.test.js +++ b/tests/core/internal/frame-elements.test.js @@ -76,15 +76,23 @@ const element = { describe('test frame elements', () => { let emitSpy; let windowSpy; + let windowSpy1; beforeEach(() => { windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(() => ({ name: `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`, location: { href: `http://localhost/?${btoa(JSON.stringify({record:element, metaData: {clientDomain: 'https://demo.com'}}))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + console.log("postMessage called with:", message, targetOrigin, args); + } + }, + addEventListener: jest.fn(), })); - emitSpy = jest.spyOn(bus, 'emit'); }) test('FrameElementInit constructor : empty path', () => { @@ -93,7 +101,14 @@ describe('test frame elements', () => { name: `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`, location: { href: `http://localhost/?${btoa(JSON.stringify({record:element, metaData: {clientDomain: 'https://demo.com'}}))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })) const onSpy = jest.spyOn(bus, 'on'); FrameElementInit.startFrameElement('123') @@ -155,7 +170,14 @@ describe('test composable frame elements', () => { name: `${FRAME_ELEMENT}:group:${btoa('123')}:ERROR:`, location: { href: `http://localhost/?${btoa(JSON.stringify({record:element, metaData: {clientDomain: 'https://demo.com'}}))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })); emitSpy = jest.spyOn(bus, 'emit'); @@ -166,7 +188,14 @@ describe('test composable frame elements', () => { name: `${FRAME_ELEMENT}:CARD_NUMBER:${btoa('123')}:ERROR:`, location: { href: `http://localhost/?${btoa(JSON.stringify({record:element, metaData: {clientDomain: 'https://demo.com'}}))}`, - } + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), })) const onSpy = jest.spyOn(bus, 'on'); FrameElementInit.group = []; diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index 0862c049..7f452ea9 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -67,14 +67,17 @@ describe('test iframeFormelement', () => { targetSpy.mockReturnValue({ on, }); - windowSpy = jest.spyOn(window,'parent','get'); + windowSpy = jest.spyOn(global, 'window', 'get'); windowSpy.mockImplementation(()=>({ - - frames:{ + parent:{ + frames:{ 'element:CARD_NUMBER:${tableCol}':{document:{ getElementById:()=>({value:testValue}) }} - } + }, + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), })); }); afterEach(() => { diff --git a/tests/core/internal/internal-index.test.js b/tests/core/internal/internal-index.test.js index a36f4a33..ce646dc5 100644 --- a/tests/core/internal/internal-index.test.js +++ b/tests/core/internal/internal-index.test.js @@ -42,7 +42,13 @@ describe('domReady function - FrameElement', () => { on, }); windowSpy = jest.spyOn(window,'parent','get'); - + const originalPostMessage = window.postMessage; + window.postMessage = (message, targetOrigin, ...args) => { + if (!targetOrigin) { + targetOrigin = "*"; + } + return originalPostMessage.call(window, message, targetOrigin, ...args); + }; mockIFrameFormElement = { resetEvents: jest.fn(), on: jest.fn(), diff --git a/tests/core/internal/reveal/reveal-frame.test.js b/tests/core/internal/reveal/reveal-frame.test.js index e9c0e2ff..31f006c7 100644 --- a/tests/core/internal/reveal/reveal-frame.test.js +++ b/tests/core/internal/reveal/reveal-frame.test.js @@ -55,6 +55,20 @@ const defineUrl = (url) => { value: "reveal:1234", writable: true, }); + Object.defineProperty(window, "parent", { + value: { + frames: { + "element:CARD_NUMBER:${tableCol}": { + document: { + getElementById: () => ({ value: testValue }), + }, + }, + }, + postMessage: jest.fn(), + addEventListener: jest.fn(), + }, + writable: true, + }); }; const elementName = "reveal:1234" @@ -171,10 +185,10 @@ name: elementName, // // reveal response ready - const onRevealResponseName = on.mock.calls[0][0]; + const onRevealResponseName = on.mock.calls[1][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY); - const onRevealResponseCb = on.mock.calls[0][1]; + const onRevealResponseCb = on.mock.calls[1][1]; onRevealResponseCb({"1815-6223-1073-1425":"card_value"}) }); @@ -213,10 +227,10 @@ name: elementName, // reveal response ready - const onRevealResponseName = on.mock.calls[0][0]; + const onRevealResponseName = on.mock.calls[1][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY); - const onRevealResponseCb = on.mock.calls[0][1]; + const onRevealResponseCb = on.mock.calls[1][1]; onRevealResponseCb({"1815":"1234"}) }); test("init callback after reveal without value",()=>{ @@ -257,10 +271,10 @@ name: elementName, // reveal response ready - const onRevealResponseName = on.mock.calls[0][0]; + const onRevealResponseName = on.mock.calls[1][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY); - const onRevealResponseCb = on.mock.calls[0][1]; + const onRevealResponseCb = on.mock.calls[1][1]; onRevealResponseCb({}); }); @@ -305,10 +319,10 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName); - const onSetErrorName = on.mock.calls[1][0]; + const onSetErrorName = on.mock.calls[2][0]; // undefined since with jest window.name will be emptyString("") expect(onSetErrorName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR+ elementName); - const onSetErrorCb = on.mock.calls[1][1]; + const onSetErrorCb = on.mock.calls[2][1]; onSetErrorCb({ name:elementName, isTriggerError: true, @@ -355,10 +369,10 @@ name: elementName, // reveal response ready - const onRevealResponseName = on.mock.calls[1][0]; + const onRevealResponseName = on.mock.calls[2][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_SET_ERROR+ elementName);; - const onRevealResponseCb = on.mock.calls[1][1]; + const onRevealResponseCb = on.mock.calls[2][1]; onRevealResponseCb({ name: elementName, isTriggerError: false, @@ -401,10 +415,10 @@ name: elementName, const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const onRevealResponseName = on.mock.calls[2][0]; + const onRevealResponseName = on.mock.calls[3][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS+ elementName); - const onRevealResponseCb = on.mock.calls[2][1]; + const onRevealResponseCb = on.mock.calls[3][1]; onRevealResponseCb({ name: elementName, updateType:REVEAL_ELEMENT_OPTIONS_TYPES.TOKEN, @@ -449,10 +463,10 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const onRevealResponseName = on.mock.calls[2][0]; + const onRevealResponseName = on.mock.calls[3][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS+ elementName); - const onRevealResponseCb = on.mock.calls[2][1]; + const onRevealResponseCb = on.mock.calls[3][1]; onRevealResponseCb({ name: elementName, updateType:REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, @@ -497,10 +511,10 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const onRevealResponseName = on.mock.calls[2][0]; + const onRevealResponseName = on.mock.calls[3][0]; // undefined since with jest window.name will be emptyString("") expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS+ elementName); - const onRevealResponseCb = on.mock.calls[2][1]; + const onRevealResponseCb = on.mock.calls[3][1]; onRevealResponseCb({ name: elementName, updateType:REVEAL_ELEMENT_OPTIONS_TYPES.ALT_TEXT, @@ -671,9 +685,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const onRevealResponseName = on.mock.calls[2][0]; + const onRevealResponseName = on.mock.calls[3][0]; expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS+ elementName); - const onRevealResponseCb = on.mock.calls[2][1]; + const onRevealResponseCb = on.mock.calls[3][1]; onRevealResponseCb({ name: elementName, updateType:REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, @@ -712,9 +726,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = on.mock.calls[3][0]; + const eventRenderResponse = on.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY+ elementName); - const callback = on.mock.calls[3][1]; + const callback = on.mock.calls[0][1]; callback( { url: "https://fileurl?response-content-disposition=inline%3B%20filename%3Ddummylicence.png&X-Amz-Signature=4a19c53917cc21df2bd05bc28e4e316ffc36c208d005d8f3f50631", iframeName: elementName, @@ -764,9 +778,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = on.mock.calls[3][0]; + const eventRenderResponse = on.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY+ elementName); - const callback = on.mock.calls[3][1]; + const callback = on.mock.calls[0][1]; callback({ url: "https://url?response-content-disposition=inline%3B%20filename%3Ddummylicence.pdf&X-Amz-Signature=4a19c53917cc21df2bd05bc28e4e316ffc36c208d005d8f3f50631", iframeName: elementName, @@ -789,9 +803,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = on.mock.calls[3][0]; + const eventRenderResponse = on.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY+ elementName); - const callback = on.mock.calls[3][1]; + const callback = on.mock.calls[0][1]; callback({ url: "https://fileurl?response-content-disposition=inline%3B%20filename%3Ddummylicence.png&X-Amz-Signature=4a19c53917cc21df2bd05bc28e4e316ffc36c208d005d8f3f50631", iframeName: elementName, @@ -829,9 +843,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = on.mock.calls[3][0]; + const eventRenderResponse = on.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY+ elementName); - const callback = on.mock.calls[3][1]; + const callback = on.mock.calls[0][1]; callback({ url: "https://fileurl?filename%3Ddummylicence.pdf&X-Amz-Signature=4a19c53917cc21df2bd05bc28e4e316ffc36c208d005d8f3f50631", iframeName: elementName, @@ -870,9 +884,9 @@ name: elementName, expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = on.mock.calls[3][0]; + const eventRenderResponse = on.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY+ elementName); - const callback = on.mock.calls[3][1]; + const callback = on.mock.calls[0][1]; callback({ error: DEFAULT_FILE_RENDER_ERROR, iframeName: elementName, @@ -993,9 +1007,9 @@ describe("Reveal Frame Class", () => { expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; console.log('======>>>>>>>>>', emitSpy.mock.calls, onMock.mock.calls); // Verify reveal response ready - const onRevealResponseName = onMock.mock.calls[0][0]; + const onRevealResponseName = onMock.mock.calls[1][0]; expect(onRevealResponseName).toBe(ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY); - const onRevealResponseCb = onMock.mock.calls[0][1]; + const onRevealResponseCb = onMock.mock.calls[1][1]; onRevealResponseCb({ "1815-6223-1073-1425": "card_value" }); }); @@ -1028,9 +1042,9 @@ describe("Reveal Frame Class", () => { const emittedEventName = emitSpy.mock.calls[0][0]; const emitCb = emitSpy.mock.calls[0][2]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; - const eventRenderResponse = onMock.mock.calls[3][0]; + const eventRenderResponse = onMock.mock.calls[0][0]; expect(eventRenderResponse).toBe(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_RESPONSE_READY + elementName); - const callback = onMock.mock.calls[3][1]; + const callback = onMock.mock.calls[0][1]; callback({ error: DEFAULT_FILE_RENDER_ERROR, iframeName: elementName, @@ -1073,7 +1087,6 @@ describe("Reveal Frame Class", () => { defineUrl("http://localhost/?" + btoa(JSON.stringify(data))); const testFrame = RevealFrame.init(); const emittedEventName = emitSpy.mock.calls[0][0]; - console.log("testFrame===>", emitSpy.mock.calls); const emittedData = emitSpy.mock.calls[0][1]; expect(emittedEventName).toBe(ELEMENT_EVENTS_TO_CLIENT.MOUNTED+ elementName);; expect(emittedData).toEqual({name : elementName}) From 4d1d0a909e49fc1e8b2e2fc1d4ec3ccd52eda0b8 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Fri, 14 Nov 2025 22:05:00 +0530 Subject: [PATCH 46/50] SK-2360 fix unit tests --- .../external/reveal/reveal-composable-internal.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/core/external/reveal/reveal-composable-internal.test.js b/tests/core/external/reveal/reveal-composable-internal.test.js index 94e30695..0c0c3c37 100644 --- a/tests/core/external/reveal/reveal-composable-internal.test.js +++ b/tests/core/external/reveal/reveal-composable-internal.test.js @@ -8,9 +8,6 @@ import SkyflowContainer from '../../../../src/core/external/skyflow-container'; import Client from '../../../../src/client'; import ComposableRevealInternalElement from "../../../../src/core/external/reveal/composable-reveal-internal"; import * as busEvents from '../../../../src/utils/bus-events'; - - - import bus from "framebus"; import { JSDOM } from 'jsdom'; import EventEmitter from "../../../../src/event-emitter"; @@ -127,6 +124,7 @@ let skyflowContainer; describe("Reveal Element Class", () => { let emitSpy; let targetSpy; + let windowSpy; beforeEach(() => { jest.clearAllMocks(); emitSpy = jest.spyOn(bus, 'emit'); @@ -135,9 +133,13 @@ describe("Reveal Element Class", () => { on, off }); + windowSpy = jest.spyOn(window, "window", "get"); const client = new Client(clientData.client.config, clientData); skyflowContainer = new SkyflowContainer(client, { logLevel: LogLevel.DEBUG, env: Env.PROD }); }); + afterEach(() => { + windowSpy.mockRestore(); + }); const containerId = mockUuid; test("constructor", () => { From 11155c332497731a3298252230a33bbb2059e1ff Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Fri, 14 Nov 2025 22:17:47 +0530 Subject: [PATCH 47/50] SK-2360 fix unit tests --- .../reveal/reveal-composable-internal.test.js | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/tests/core/external/reveal/reveal-composable-internal.test.js b/tests/core/external/reveal/reveal-composable-internal.test.js index 0c0c3c37..87a15061 100644 --- a/tests/core/external/reveal/reveal-composable-internal.test.js +++ b/tests/core/external/reveal/reveal-composable-internal.test.js @@ -224,6 +224,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { expect(response).toBeDefined(); expect(response).toEqual({ success: { skyflow_id: '1244', column: 'file' } }); @@ -277,6 +283,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { expect(response).toBeDefined(); expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); @@ -333,6 +345,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element1", {}, (response) => { expect(response).toBeDefined(); expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); @@ -383,6 +401,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { expect(response).toBeDefined(); expect(response).toEqual({ success: { skyflow_id: '1244', column: 'file' } }); @@ -436,6 +460,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { expect(response).toBeDefined(); expect(response).toEqual({ error: { skyflow_id: '1244', column: 'file', error:{code : 400, description: "No Records Found"} } }); @@ -499,11 +529,16 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + // Capture the callback response let callbackResponse; groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { callbackResponse = response; - console.log('Callback response received:', response); }); window.dispatchEvent(new MessageEvent('message', { @@ -567,11 +602,16 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + // Capture the callback response let callbackResponse; groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.RENDER_FILE_REQUEST + ":element2", {}, (response) => { callbackResponse = response; - console.log('Callback response received:', response); }); @@ -613,6 +653,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ":element2", { updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, options: { altText: "Updated Alt Text" } @@ -661,6 +707,12 @@ describe("Reveal Element Class", () => { const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; expect(document.querySelector("iframe")?.name).toBe(testIframeName); + // Mock iframe postMessage to prevent CI errors + const iframe = document.querySelector("iframe"); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ":element2", { updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, options: { altText: "Updated Alt Text" } From 014608d98f7a52b402fb6636667571ff357f25ed Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Mon, 17 Nov 2025 16:12:40 +0530 Subject: [PATCH 48/50] SK-2360 added uni tests --- .../collect/compose-collect-container.ts | 4 +- .../internal/composable-frame-element-init.ts | 6 +- src/core/internal/frame-element-init.ts | 18 +- .../reveal/reveal-composable-internal.test.js | 64 + .../external/reveal/reveal-container.test.js | 41 - .../composable-frame-element-init.test.js | 1231 +++++++++++++++++ .../core/internal/frame-element-init.test.js | 559 +++++++- .../internal/iframe-form/iframe-form.test.js | 52 - tests/skyflow.test.js | 4 + 9 files changed, 1870 insertions(+), 109 deletions(-) create mode 100644 tests/core/internal/composable-frame-element-init.test.js diff --git a/src/core/external/collect/compose-collect-container.ts b/src/core/external/collect/compose-collect-container.ts index 29d28c57..d8915d0f 100644 --- a/src/core/external/collect/compose-collect-container.ts +++ b/src/core/external/collect/compose-collect-container.ts @@ -355,8 +355,8 @@ class ComposableContainer extends Container { containerId: this.#containerId, }, clientConfig: { - vaultURL: this.#metaData.clientJSON.config.vaultURL, - vaultID: this.#metaData.clientJSON.config.vaultID, + vaultURL: this.#metaData?.clientJSON?.config?.vaultURL, + vaultID: this.#metaData?.clientJSON?.config?.vaultID, authToken, }, options: { diff --git a/src/core/internal/composable-frame-element-init.ts b/src/core/internal/composable-frame-element-init.ts index 8e3093a3..f78a091c 100644 --- a/src/core/internal/composable-frame-element-init.ts +++ b/src/core/internal/composable-frame-element-init.ts @@ -317,7 +317,7 @@ export default class RevealComposableFrameElementInit { name: window?.name, }, }, - this.clientMetaData.clientDomain, + this.clientMetaData?.clientDomain, ); bus?.on(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window?.name, (data, callback) => { @@ -335,7 +335,7 @@ export default class RevealComposableFrameElementInit { name: window?.name, }, }, - this.clientMetaData.clientDomain, + this.clientMetaData?.clientDomain, ); window?.addEventListener('message', (event) => { @@ -361,7 +361,7 @@ export default class RevealComposableFrameElementInit { name: window?.name, }, }, - this.clientMetaData.clientDomain, + this.clientMetaData?.clientDomain, ); } }); diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index 342b05f8..20ab5659 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -102,21 +102,21 @@ export default class FrameElementInit { }); // if (event.origin === this.clientMetaData.clientDomain) { - if (event.data && event.data.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_REQUESTS + 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) + if (event?.data?.data && event?.data?.data?.type === COLLECT_TYPES.COLLECT) { + this.tokenize(event?.data?.data, event?.data?.clientConfig) .then((response: any) => { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, data: response, - }, this.clientMetaData.clientDomain); + }, this.clientMetaData?.clientDomain); }) .catch((error) => { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CALL_RESPONSE + this.containerId, data: error, - }, this.clientMetaData.clientDomain); + }, this.clientMetaData?.clientDomain); }); } else if (event.data.data && event.data.data.type === COLLECT_TYPES.FILE_UPLOAD) { this.parallelUploadFiles(event.data.data, event.data.clientConfig) @@ -124,17 +124,17 @@ export default class FrameElementInit { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, data: response, - }, this.clientMetaData.clientDomain); + }, this.clientMetaData?.clientDomain); }) .catch((error) => { window?.parent.postMessage({ type: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_FILE_CALL_RESPONSE + this.containerId, data: error, - }, this.clientMetaData.clientDomain); + }, this.clientMetaData?.clientDomain); }); } } - if (event.data.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId) { + if (event?.data?.name === ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_CONTAINER + this.containerId) { const data = event.data; data.client.config = { ...data.client.config, @@ -844,7 +844,7 @@ export default class FrameElementInit { this.clientMetaData.clientDomain, ); window.addEventListener('message', (event) => { - if (event.data.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name) { + if (event?.data?.name === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + window.name) { window.parent.postMessage( { type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + window.name, diff --git a/tests/core/external/reveal/reveal-composable-internal.test.js b/tests/core/external/reveal/reveal-composable-internal.test.js index 87a15061..a7407759 100644 --- a/tests/core/external/reveal/reveal-composable-internal.test.js +++ b/tests/core/external/reveal/reveal-composable-internal.test.js @@ -21,6 +21,11 @@ jest.mock('../../../../src/libs/uuid',()=>({ __esModule: true, default:jest.fn(()=>(mockUuid)), })); +global.ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn(), + unobserve: jest.fn(), +})); // const _on = jest.fn(); // const _off = jest.fn(); // const _emit = jest.fn(); @@ -720,4 +725,63 @@ describe("Reveal Element Class", () => { }); + test("update call error case when container is not mounted case 2", async () => { + const elementArray = { + rows:[{ + elements : [{ + "column": "file", + "table": "table6", + "altText": "Alt text 1", + "name": "element2", + "skyflowID": "id1" + }] + }] + }; + const groupEmiitter = new EventEmitter(); + const testRevealElement = new ComposableRevealInternalElement( + elementId, + elementArray, + clientData, + {containerId:containerId,isMounted:false,eventEmitter:groupEmiitter}, + { logLevel: LogLevel.ERROR,env:Env.PROD } + ); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_IFRAME.RENDER_MOUNTED + "element2", + containerId: mockUuid, + } + })); + + + // create shadow dom and add testDiv inside shadow dom + const hostElement = document.createElement('div'); + document.body.appendChild(hostElement); + hostElement.attachShadow({ mode: 'open' }); + + const testEmptyDiv = document.createElement("div"); + testEmptyDiv.setAttribute("id", "testDiv"); + hostElement.shadowRoot.appendChild(testEmptyDiv); + expect(hostElement.shadowRoot.getElementById("testDiv")).not.toBeNull(); + expect(testRevealElement.isMounted()).toBe(false); + + testRevealElement.mount(hostElement.shadowRoot.getElementById('testDiv')); + + // Mock iframe postMessage to prevent CI errors + const iframe = hostElement.shadowRoot.getElementById('testDiv').querySelector('iframe'); + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage = jest.fn(); + } + + const divInShadow = hostElement.shadowRoot.getElementById('testDiv').querySelector('iframe'); + expect(divInShadow).not.toBeNull(); + + expect(hostElement.shadowRoot.getElementById('testDiv').querySelector('iframe')).toBeTruthy(); + const testIframeName = `${COMPOSABLE_REVEAL}:${btoa(mockUuid)}:${containerId}:ERROR:${btoa(clientDomain)}`; + expect(hostElement.shadowRoot.getElementById('testDiv').querySelector('iframe')?.name).toBe(testIframeName); + + groupEmiitter._emit(ELEMENT_EVENTS_TO_IFRAME.REVEAL_ELEMENT_UPDATE_OPTIONS + ":element2", { + updateType: REVEAL_ELEMENT_OPTIONS_TYPES.ELEMENT_PROPS, + options: { altText: "Updated Alt Text" } + }); + }); }); \ No newline at end of file diff --git a/tests/core/external/reveal/reveal-container.test.js b/tests/core/external/reveal/reveal-container.test.js index 0b515317..146c916b 100644 --- a/tests/core/external/reveal/reveal-container.test.js +++ b/tests/core/external/reveal/reveal-container.test.js @@ -166,47 +166,6 @@ describe("Reveal Container Class", () => { done(); } }); - - // test.only("on skyflow frame ready call back",()=>{ - // const testRevealContainer = new RevealContainer(clientData, {}, { logLevel: LogLevel.ERROR,env:Env.PROD }); - // const data = { - // name:REVEAL_FRAME_CONTROLLER, - // }; - // const emitterCb = jest.fn(); - // console.log('data is on.mock.calls', on.mock.calls); - // const eventName = ELEMENT_EVENTS_TO_IFRAME.SKYFLOW_FRAME_CONTROLLER_READY+mockUuid - // const onCbName = on.mock.calls[1][0]; - // expect(onCbName).toBe(eventName); - // const onCb = on.mock.calls[1][1]; - // onCb(data, emitterCb); - // bus.emit(eventName,data,emitterCb); - // const data1 = { - // "client": { - // "config": { - // "vaultID": "e20afc3ae1b54f0199f24130e51e0c11", - // "vaultURL": "https://testurl.com", - // "getBearerToken": getBearerToken, - // "options": {} - // }, - - // "metaData": { - // "uuid": "1234", - // }, - // "context": { - // "env": "PROD", - // "logLevel": "ERROR" - // }, - // }, - // "context": { - // "env": "PROD", - // "logLevel": "ERROR" - // }, - // } - - // expect(emitterCb).toBeCalledTimes(1); - // expect(emitterCb).toBeCalledWith(data1); - // }); - test("on container mounted call back",()=>{ const testRevealContainer = new RevealContainer(clientData, {}, { logLevel: LogLevel.ERROR,env:Env.PROD }); testRevealContainer.create({ diff --git a/tests/core/internal/composable-frame-element-init.test.js b/tests/core/internal/composable-frame-element-init.test.js new file mode 100644 index 00000000..1970f194 --- /dev/null +++ b/tests/core/internal/composable-frame-element-init.test.js @@ -0,0 +1,1231 @@ +// import FrameElementInit from '../../../src/core/internal/frame-element-init'; +import RevealComposableFrameElementInit from '../../../src/core/internal/composable-frame-element-init'; +import { ELEMENT_EVENTS_TO_IFRAME, COMPOSABLE_REVEAL, ELEMENT_EVENTS_TO_CLIENT, FRAME_REVEAL, REVEAL_TYPES } from '../../../src/core/constants'; +import bus from 'framebus'; +import SkyflowError from '../../../src/libs/skyflow-error'; +import { fetchRecordsByTokenIdComposable, formatRecordsForClientComposable } from '../../../src/core-utils/reveal'; + +// Create a mock function that can be controlled per test +const mockFetchRecordsByTokenIdComposable = jest.fn(); + +// mock fetchRecordsByTokenIdComposable with a jest.fn() so we can control it per test +jest.mock('../../../src/core-utils/reveal', () => { + const actual = jest.requireActual('../../../src/core-utils/reveal'); + return { + ...actual, + fetchRecordsByTokenIdComposable: (...args) => mockFetchRecordsByTokenIdComposable(...args), + }; +}); + + +const stylesOptions = { + inputStyles: { + base: { + border: "1px solid #eae8ee", + padding: "10px 16px", + borderRadius: "4px", + color: "#1d1d1d", + marginTop: "4px" + }, + complete: { + color: "#4caf50" + }, + empty: {}, + focus: {}, + invalid: { + color: "#f44336" + }, + cardIcon: { + position: "absolute", + left: "8px", + top: "calc(50% - 10px)", + }, + copyIcon: { + position: "absolute", + right: "8px", + top:"calc(50% - 10px)", + cursor: "pointer", + } + }, + labelStyles: { + base: { + fontSize: "16px", + fontWeight: "bold" + } + }, + errorTextStyles: { + base: { + color: "#f44336" + } + } +}; +const element = { + elementName: "element:group:W29iamVjdCBPYmplY3Rd", + rows: [{ + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:123`, + name: `reveal-composable:123`, + table: 'patients', + column: 'card_number', + token: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + }, + ] + }], + errorTextStyles:{ + base: { + color: "#f44336", + fontFamily:'Inter', + }, + global:{ + '@import':'https://font-url.com/Inter' + } + }, + clientDomain: 'http://localhost.com' +} +const element2 = { + elementName: "element:group:W29iamVjdCBPYmplY3Rd", + rows: [{ + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:123`, + name: `reveal-composable:123`, + table: 'patients', + column: 'card_number', + token: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + }, + { + elementType: 'REVEAL', + elementName: `reveal-composable:124`, + name: `reveal-composable:124`, + table: 'patients', + column: 'card_number', + token: 'skyflow-id-2', + elementId: 'element-id-2', + ...stylesOptions + }, + ] + }], + errorTextStyles:{ + base: { + color: "#f44336", + fontFamily:'Inter', + }, + global:{ + '@import':'https://font-url.com/Inter' + } + }, + clientDomain: 'http://localhost.com' +} +const on = jest.fn(); +const emit = jest.fn(); +describe('composableFrameElementInit Additional Test Cases', () => { + let emitSpy; + let windowSpy; + let targetSpy; + + beforeEach(() => { + windowSpy = jest.spyOn(global, 'window', 'get'); + jest.clearAllMocks() + emitSpy = jest.spyOn(bus, 'emit'); + targetSpy = jest.spyOn(bus, 'target'); + targetSpy.mockReturnValue({ + on, + emit + }); + + // Set default successful response for fetchRecordsByTokenIdComposable + mockFetchRecordsByTokenIdComposable.mockResolvedValue({ + records: [{ + token: 'skyflow-id-1', + value: '4111111111111111', + valueType: 'STRING', + frameId: 'reveal-composable:123', + }] + }); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('should handle missing window name gracefully', () => { + windowSpy = jest.spyOn(global, 'window', 'get'); + windowSpy.mockImplementation(() => ({ + name: ``, + location: { + href: `http://localhost/?${btoa(JSON.stringify({record:element, clientJSON: {metaData: {clientDomain: 'http://localhost.com'}}}))}`, + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), + })) + // const onSpy = jest.spyOn(bus, 'on'); + const frameElement = new RevealComposableFrameElementInit() + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle invalid base64 encoded URL data', () => { + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:123:ERROR:`, + location: { href: 'http://localhost/?invalid_base64_data' }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).toThrowError({'message':'The string to be decoded contains invalid characters.'}); + }); + + test('should correctly bind multiple event listeners', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:123:ERROR:` + windowSpy.mockImplementation(() => ({ + name: id, // Fix constant reference + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }))}`, + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + window.dispatchEvent(new MessageEvent('message', { + data: { + type: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, + payload: { + name: id, + }, + }, + origin: 'http://localhost.com', + })); + + expect(onSpy).toHaveBeenCalledWith(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, expect.any(Function) + ) + expect(onSpy).toHaveBeenCalledWith(ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, expect.any(Function)); + }); + + test('should correctly extract record and metadata from URL', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:123:ERROR:` + windowSpy.mockImplementation(() => ({ + name: id, // Fix constant reference + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }))}`, + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should correctly initialize composable frame elements', () => { + RevealComposableFrameElementInit.group = []; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:group:123:ERROR:`, + location: { + href: `http://localhost/?${btoa( + JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }) + )}` + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + } + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should throw error for incorrect element type', () => { + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:UNKNOWN_TYPE:123:ERROR:`, + location: { href: `http://localhost/?${btoa(JSON.stringify({ record: {}, metaData: {} }))}` } + })); + expect(() => RevealComposableFrameElementInit.startFrameElement()).toThrow(TypeError); + }); + + test('should handle malformed window name', () => { + windowSpy.mockImplementation(() => ({ + name: 'INVALID_NAME_FORMAT', + location: { href: 'http://localhost/' } + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).toThrow(TypeError); + }); + + test('should not crash on undefined window object', () => { + windowSpy.mockImplementation(() => undefined); + + expect(() => RevealComposableFrameElementInit.startFrameElement('')).toThrow(TypeError); + }); + + test('composable reveal frame element init reveal events', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:123:ERROR:` + const containerId = '123'; + windowSpy.mockImplementation(() => ({ + name: id, // Fix constant reference + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' }, containerId:containerId }))}`, + }, + parent: { + postMessage: (message, targetOrigin, ...args) => { + if (!targetOrigin) targetOrigin = "*"; + // Optionally, call a jest mock here + }, + // addEventListener: jest.fn(), + // dispatchEvent: jest.fn(), + }, + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + // expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + RevealComposableFrameElementInit.startFrameElement(); + window.dispatchEvent(new MessageEvent('message', { + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: ['element-id-1'], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + })); + + }); + + test('should handle HEIGHT_CALLBACK_COMPOSABLE event', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:123:ERROR:` + const containerId = '123'; + const postMessageSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId:containerId }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Trigger HEIGHT event + window.dispatchEvent(new MessageEvent('message', { + data:{ + name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, + } + })); + + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + id, + data: expect.objectContaining({ + name: id, + }), + }), + 'http://localhost.com' + ); + }); + + test('should handle HEIGHT_CALLBACK_COMPOSABLE message event', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:456:ERROR:` + const containerId = '456'; + const postMessageSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId:containerId }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: jest.fn(), + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Trigger HEIGHT_CALLBACK_COMPOSABLE event + window.dispatchEvent(new MessageEvent('message', { + data:{ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK_COMPOSABLE + id, + } + })); + + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + id, + data: expect.objectContaining({ + name: id, + }), + }), + 'http://localhost.com' + ); + }); + + test('should handle multiple rows in group element', () => { + const multiRowElement = { + ...element, + rows: [ + { + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:row1-elem1`, + name: `reveal-composable:row1-elem1`, + table: 'patients', + column: 'first_name', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + } + ] + }, + { + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:row2-elem1`, + name: `reveal-composable:row2-elem1`, + table: 'patients', + column: 'last_name', + skyflowID: 'skyflow-id-2', + elementId: 'element-id-2', + ...stylesOptions + } + ] + } + ] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:multi-row:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: multiRowElement, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'multi-row' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle elements with custom spacing', () => { + const elementWithSpacing = { + ...element, + spacing: '10px', + rows: [{ + spacing: '5px', + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:spaced-elem`, + name: `reveal-composable:spaced-elem`, + table: 'patients', + column: 'card_number', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + } + ] + }] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:spacing-test:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: elementWithSpacing, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'spacing-test' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle elements with alignItems and justifyContent', () => { + const elementWithAlignment = { + ...element, + alignItems: 'center', + justifyContent: 'space-between', + rows: [{ + alignItems: 'flex-start', + justifyContent: 'flex-end', + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:aligned-elem`, + name: `reveal-composable:aligned-elem`, + table: 'patients', + column: 'card_number', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + } + ] + }] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:alignment-test:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: elementWithAlignment, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'alignment-test' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should emit MOUNTED event on initialization', () => { + const containerId = '123'; + const postMessageSpy = jest.fn(); + const addEventListenerSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:123:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId: containerId }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: addEventListenerSpy, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_CLIENT.MOUNTED + containerId, + }), + 'http://localhost.com' + ); + }); + + test('should handle RENDER_FILE type reveal calls', () => { + const onSpy = jest.spyOn(bus, 'on'); + windowSpy = jest.spyOn(global, 'window', 'get'); + const id = `${COMPOSABLE_REVEAL}:file-render:ERROR:` + const containerId = 'file-render'; + const addEventListenerSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId: containerId }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: addEventListenerSpy, + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + window.dispatchEvent(new MessageEvent('message', { + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: ['element-id-1'], + type: REVEAL_TYPES.RENDER_FILE, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + })); + + expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); + }); + + test('should handle reveal data with multiple element IDs', () => { + const multiElementRecord = { + ...element, + rows: [{ + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:elem-1`, + name: `reveal-composable:elem-1`, + table: 'patients', + column: 'card_number', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + token: 'token-1', + ...stylesOptions + }, + { + elementType: 'REVEAL', + elementName: `reveal-composable:elem-2`, + name: `reveal-composable:elem-2`, + table: 'patients', + column: 'cvv', + skyflowID: 'skyflow-id-2', + elementId: 'element-id-2', + token: 'token-2', + ...stylesOptions + } + ] + }] + }; + + const containerId = 'multi-elem'; + const addEventListenerSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: multiElementRecord, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId: containerId }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: addEventListenerSpy, + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + window.dispatchEvent(new MessageEvent('message', { + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [{frameId: 'reveal-composable:elem-1'}, {frameId: 'reveal-composable:elem-2'}], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + })); + + expect(addEventListenerSpy).toHaveBeenCalled(); + }); + + test('should handle empty rows array', () => { + const emptyRowsElement = { + ...element, + rows: [] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:empty-rows:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: emptyRowsElement, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'empty-rows' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle missing styles in element', () => { + const noStylesElement = { + elementName: "element:group:test", + rows: [{ + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:no-styles`, + name: `reveal-composable:no-styles`, + table: 'patients', + column: 'card_number', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + } + ] + }] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:no-styles:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: noStylesElement, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'no-styles' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle global styles in errorTextStyles', () => { + const globalStylesElement = { + ...element, + errorTextStyles: { + base: { + color: "#f44336", + }, + global: { + '@import': 'https://fonts.googleapis.com/css2?family=Roboto&display=swap' + } + } + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:global-styles:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: globalStylesElement, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'global-styles' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should listen to HEIGHT event and respond with height data', () => { + const onSpy = jest.spyOn(bus, 'on'); + const containerId = 'height-test'; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' }, containerId: containerId }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Verify HEIGHT listener is registered + expect(onSpy).toHaveBeenCalledWith( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`, + expect.any(Function) + ); + }); + + test('should handle reveal call with empty elementIds', () => { + const containerId = 'empty-ids'; + const addEventListenerSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, containerId: containerId }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: addEventListenerSpy, + dispatchEvent: jest.fn(), + postMessage: jest.fn(), + })); + + RevealComposableFrameElementInit.startFrameElement(); + + window.dispatchEvent(new MessageEvent('message', { + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + })); + + expect(addEventListenerSpy).toHaveBeenCalled(); + }); + + test('should handle rows with multiple elements', () => { + const multiElementRowElement = { + ...element, + rows: [{ + elements: [ + { + elementType: 'REVEAL', + elementName: `reveal-composable:elem-1`, + name: `reveal-composable:elem-1`, + table: 'patients', + column: 'first_name', + skyflowID: 'skyflow-id-1', + elementId: 'element-id-1', + ...stylesOptions + }, + { + elementType: 'REVEAL', + elementName: `reveal-composable:elem-2`, + name: `reveal-composable:elem-2`, + table: 'patients', + column: 'last_name', + skyflowID: 'skyflow-id-2', + elementId: 'element-id-2', + ...stylesOptions + }, + { + elementType: 'REVEAL', + elementName: `reveal-composable:elem-3`, + name: `reveal-composable:elem-3`, + table: 'patients', + column: 'email', + skyflowID: 'skyflow-id-3', + elementId: 'element-id-3', + ...stylesOptions + } + ] + }] + }; + + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:multi-elem-row:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: multiElementRowElement, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'multi-elem-row' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle missing containerId in URL params', () => { + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:missing-container:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' } }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('should handle missing context in URL params', () => { + windowSpy.mockImplementation(() => ({ + name: `${COMPOSABLE_REVEAL}:missing-context:ERROR:`, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ record: element, metaData: { clientDomain: 'http://localhost.com' }, containerId: 'missing-context' }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + expect(() => RevealComposableFrameElementInit.startFrameElement()).not.toThrow(); + }); + + test('reveal success call', async () => { + const containerId = 'unrelated-msg-test'; + const id = `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`; + const postMessageSpy = jest.fn().mockImplementation(() => { + }); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, + containerId: containerId, + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Clear initial calls + postMessageSpy.mockClear(); + + // Send unrelated message + if (messageHandler) { + messageHandler({ + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [{frameId:'reveal-composable:123'}], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + }); + // Should not call postMessage for unrelated events + expect(postMessageSpy).not.toHaveBeenCalled(); + } + }); + + test('should handle reveal success response with records', async () => { + const containerId = 'reveal-success-test'; + const id = `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`; + const postMessageSpy = jest.fn(); + let messageHandler; + + // Mock successful response + mockFetchRecordsByTokenIdComposable.mockResolvedValue({ + records: [{ + token: 'skyflow-id-1', + value: '4111111111111111', + valueType: 'STRING', + frameId: 'reveal-composable:123', + }] + }); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, + containerId: containerId, + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Clear initial calls + postMessageSpy.mockClear(); + + // Send reveal message + if (messageHandler) { + messageHandler({ + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [{frameId:'reveal-composable:123'}], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + authToken: 'test-token', + } + } + }); + + // Wait for async operations to complete + await new Promise(resolve => setTimeout(resolve, 100)); + + // Should call postMessage with REVEAL_RESPONSE_READY + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + containerId, + }), + 'http://localhost.com' + ); + } + }); + + test('should handle reveal error response', async () => { + const containerId = 'reveal-error-test'; + const id = `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`; + const postMessageSpy= jest.fn().mockImplementation((data) => { + if (data.type === ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + containerId) { + expect(data).toMatchObject({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + containerId, + data: { + errors: [{ + error: { + code: '404', + description: 'Token not found', + }, + } + ], + success: [{ + token: '', + valueType: '', + }] + }, + }); + } + }); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element2, + clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, + containerId: containerId, + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Clear initial calls + postMessageSpy.mockClear(); + // Mock error response + mockFetchRecordsByTokenIdComposable.mockRejectedValue({ + errors: [{ + token: 'skyflow-id-1', + error: { + code: '404', + description: 'Token not found', + }, + frameId: 'reveal-composable:124', + }], + records: [{ + token: 'skyflow-id-1', + value: '4111111111111111', + valueType: 'STRING', + frameId: 'reveal-composable:123', + }] + }); + // Send reveal message + if (messageHandler) { + messageHandler({ + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [{frameId:'reveal-composable:123'}, {frameId:'reveal-composable:124'}], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + authToken: 'test-token', + } + } + }); + + // Wait for async operations to complete + await new Promise(resolve => setTimeout(resolve, 100)); + await new Promise(reject => setTimeout(reject, 100)); + // Should call postMessage with REVEAL_RESPONSE_READY even for errors + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.REVEAL_RESPONSE_READY + containerId, + }), + 'http://localhost.com' + ); + } + }); + + test('reveal call - ignore unrelated messages', async () => { + const containerId = 'unrelated-msg-test'; + const id = `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`; + const postMessageSpy = jest.fn().mockImplementation(() => { + }); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, + containerId: containerId, + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + // Clear initial calls + postMessageSpy.mockClear(); + + // Send unrelated message + if (messageHandler) { + messageHandler({ + data:{ + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + context:{ + vaultId: 'vault-id-1', + }, + data:{ + elementIds: [{frameId:'reveal-composable:123'}], + type: REVEAL_TYPES.REVEAL, + name: ELEMENT_EVENTS_TO_IFRAME.COMPOSABLE_REVEAL + containerId, + }, + clientConfig:{ + clientDomain: 'http://localhost.com', + uuid: 'uuid-1', + } + } + }); + // Should not call postMessage for unrelated events + expect(postMessageSpy).not.toHaveBeenCalled(); + } + }); + + test('should handle window message with missing data fields gracefully', () => { + const containerId = 'missing-data-test'; + const id = `${COMPOSABLE_REVEAL}:${containerId}:ERROR:`; + const postMessageSpy = jest.fn(); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + clientJSON: { metaData: { clientDomain: 'http://localhost.com' }}, + containerId: containerId + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + RevealComposableFrameElementInit.startFrameElement(); + + postMessageSpy.mockClear(); + + // Send message with missing data + if (messageHandler) { + expect(() => { + messageHandler({ + data: null + }); + }).not.toThrow(); + + expect(() => { + messageHandler({}); + }).not.toThrow(); + } + }); +}); diff --git a/tests/core/internal/frame-element-init.test.js b/tests/core/internal/frame-element-init.test.js index e68934cb..7fac34fa 100644 --- a/tests/core/internal/frame-element-init.test.js +++ b/tests/core/internal/frame-element-init.test.js @@ -1,7 +1,59 @@ import FrameElementInit from '../../../src/core/internal/frame-element-init'; -import { ELEMENT_EVENTS_TO_IFRAME, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT } from '../../../src/core/constants'; +import { ELEMENT_EVENTS_TO_IFRAME, FRAME_ELEMENT, ELEMENT_EVENTS_TO_CLIENT, ElementType } 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'; + +// Helper to flush pending microtasks (Promise.allSettled resolution) deterministically +const flushPromises = async (cycles = 3) => { + for (let i = 0; i < cycles; i += 1) { + // eslint-disable-next-line no-await-in-loop + await Promise.resolve(); + } +}; + +jest.mock('../../../src/utils/helpers', () => { + const actual = jest.requireActual('../../../src/utils/helpers'); + return { + ...actual, + fileValidation: (...args) => {return true}, + vaildateFileName: () => true, + generateUploadFileName: (name) => `generated_${name}`, + }; +}); +const mockFile1 = new File(['file content 1'], 'test-file-1.txt', { type: 'text/plain' }); +// Create a mock FileList +const mockFileList = mockFile1; +// { +// 0: mockFile1, +// length: 1, +// item: function(index) { return this[index] || null; }, +// [Symbol.iterator]: function* () { +// for (let i = 0; i < this.length; i++) { +// yield this[i]; +// } +// } +// }; +// jest.mock('../../../src/core/internal/iframe-form', () => { +// const actual = jest.requireActual('../../../src/core/internal/iframe-form'); + +// return { +// __esModule: true, +// default: class MockIFrameFormElement extends actual.default { +// constructor(...args) { +// super(...args); +// // Override state after construction +// this.state = { +// value: mockFileList, // This would still have scope issues +// name: 'file_upload', +// // ... rest of state +// }; +// } +// } +// }; +// }); const stylesOptions = { inputStyles: { @@ -54,6 +106,13 @@ const element = { table: 'patients', column: 'card_number', ...stylesOptions + }, + { + elementType: ElementType.MULTI_FILE_INPUT, + elementName: `element:MULTI_FILE_INPUT:123`, + table: 'patients', + column: 'file_uploads', + ...stylesOptions } ] }], @@ -245,4 +304,500 @@ describe('FrameElementInit Additional Test Cases', () => { expect(() => FrameElementInit.startFrameElement('')).toThrow(TypeError); }); -}); + + test('should call window.parent.postMessage on HEIGHT_CALLBACK event', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`; + const postMessageSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: jest.fn(), + })); + + FrameElementInit.startFrameElement(); + + // Verify that postMessage was called with HEIGHT_CALLBACK + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + id, + data: expect.objectContaining({ + name: id, + height: expect.any(Number), + }), + }), + 'http://localhost.com' + ); + }); + + test('should add window message event listener', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`; + const addEventListenerSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: addEventListenerSpy, + })); + + FrameElementInit.startFrameElement(); + + // Verify that addEventListener was called for 'message' event + expect(addEventListenerSpy).toHaveBeenCalledWith('message', expect.any(Function)); + }); + + // test('should handle HEIGHT message event and post HEIGHT_CALLBACK', () => { + // const id = `${FRAME_ELEMENT}:CARD_NUMBER:height-test:ERROR:`; + // const postMessageSpy = jest.fn(); + // let messageHandler; + // let windowMock; + // const addEventListenerSpy = jest.fn((event, handler) => { + // if (event === 'message') { + // messageHandler = handler; + // } + // }); + + // windowSpy.mockImplementation(() => { + // windowMock = { + // name: id, + // location: { + // href: `http://localhost/?${btoa(JSON.stringify({ + // record: element, + // metaData: { clientDomain: 'http://localhost.com' } + // }))}`, + // }, + // parent: { + // postMessage: postMessageSpy, + // }, + // addEventListener: addEventListenerSpy, + // }; + // return windowMock; + // }); + + // FrameElementInit.startFrameElement(); + + // // Clear initial postMessage calls + // postMessageSpy.mockClear(); + + // // Simulate HEIGHT message event + // if (messageHandler) { + // // Ensure window mock is still available when handler executes + // windowSpy.mockImplementation(() => windowMock); + + // messageHandler({ + // data: { + // name: ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, + // } + // }); + + // // Verify postMessage was called in response to HEIGHT event + // expect(postMessageSpy).toHaveBeenCalledWith( + // expect.objectContaining({ + // type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + id, + // data: expect.objectContaining({ + // name: id, + // height: expect.any(Number), + // }), + // }), + // 'http://localhost.com' + // ); + // } + // }); + + test('should register HEIGHT bus event listener', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:123:ERROR:`; + const onSpy = jest.spyOn(bus, 'on'); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + FrameElementInit.startFrameElement(); + + // Verify HEIGHT listener is registered + expect(onSpy).toHaveBeenCalledWith( + ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id, + expect.any(Function) + ); + }); + + test('should ignore unrelated window messages', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:unrelated-test:ERROR:`; + const postMessageSpy = jest.fn(); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + FrameElementInit.startFrameElement(); + postMessageSpy.mockClear(); + + // Send unrelated message + if (messageHandler) { + messageHandler({ + data: { + name: 'UNRELATED_EVENT', + type: 'SOME_OTHER_TYPE', + } + }); + + // Should not call postMessage for unrelated events + expect(postMessageSpy).not.toHaveBeenCalled(); + } + }); + + test('should handle window message with missing data gracefully', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:missing-data:ERROR:`; + const postMessageSpy = jest.fn(); + let messageHandler; + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + })); + + FrameElementInit.startFrameElement(); + postMessageSpy.mockClear(); + + // Send message with missing data + if (messageHandler) { + expect(() => { + messageHandler({ data: null }); + }).not.toThrow(); + + expect(() => { + messageHandler({}); + }).not.toThrow(); + + // Should not call postMessage for invalid events + expect(postMessageSpy).not.toHaveBeenCalled(); + } + }); + + test('should respond to HEIGHT bus event with callback', () => { + const id = `${FRAME_ELEMENT}:CARD_NUMBER:bus-height:ERROR:`; + const callbackSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: element, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: jest.fn(), + }, + addEventListener: jest.fn(), + })); + + FrameElementInit.startFrameElement(); + + // Get the HEIGHT event listener callback + const heightCallback = on.mock.calls.find(call => + call[0] === ELEMENT_EVENTS_TO_CLIENT.HEIGHT + id + )?.[1]; + + // Trigger the HEIGHT event with callback + if (heightCallback) { + heightCallback({}, callbackSpy); + + // Verify callback was called with height data + expect(callbackSpy).toHaveBeenCalledWith( + expect.objectContaining({ + name: id, + height: expect.any(Number), + }) + ); + } + }); + + test('should post HEIGHT_CALLBACK on BLUR event for composable container', () => { + const composableElement = { + ...element, + rows: [{ + elements: [{ + elementType: 'CARD_NUMBER', + elementName: `element:CARD_NUMBER:composable-blur`, + table: 'patients', + column: 'card_number', + ...stylesOptions + }] + }] + }; + + const id = `${FRAME_ELEMENT}:group:composable-blur:ERROR:`; + const postMessageSpy = jest.fn(); + + windowSpy.mockImplementation(() => ({ + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: composableElement, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: postMessageSpy, + }, + addEventListener: jest.fn(), + })); + + FrameElementInit.startFrameElement(); + + // The HEIGHT_CALLBACK should be posted on initialization + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: ELEMENT_EVENTS_TO_IFRAME.HEIGHT_CALLBACK + id, + }), + 'http://localhost.com' + ); + }); + test('should handle multi file upload message event error case', async () => { + const composableElement = { + ...element, + rows: [{ + elements: [{ + elementType: 'CARD_NUMBER', + elementName: `element:CARD_NUMBER:123`, + table: 'patients', + column: 'card_number', + ...stylesOptions + }, + { + elementType: ElementType.MULTI_FILE_INPUT, + elementName: `element:MULTI_FILE_INPUT:123`, + table: 'patients', + column: 'file_uploads', + ...stylesOptions + }] + }] + }; + + const id = `${FRAME_ELEMENT}:group:123:ERROR:`; + const postMessageSpy = jest.fn(); + let messageHandler; + let windowMock; + + windowSpy.mockImplementation(() => { + windowMock = { + name: id, + location: { + href: `http://localhost/?${btoa(JSON.stringify({ + record: composableElement, + metaData: { clientDomain: 'http://localhost.com' } + }))}`, + }, + parent: { + postMessage: postMessageSpy, + addEventListener: jest.fn(), + }, + addEventListener: (event, handler) => { + if (event === 'message') { + messageHandler = handler; + } + }, + dispatchEvent: jest.fn(), + }; + return windowMock; + }); + + FrameElementInit.startFrameElement(); + + // Clear initialization postMessage calls + postMessageSpy.mockClear(); + + // Ensure window mock is available when handler executes + windowSpy.mockImplementation(() => windowMock); + + // Simulate multi file upload message event + if (messageHandler) { + messageHandler(new MessageEvent('message', { + data: { + name: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:element:MULTI_FILE_INPUT:123`, + clientConfig: { + vaultId: 'vault123', + vaultURL: 'https://vaulturl.com', + authToken: 'token123', + uuid: 'uuid123', + }, + options: { + // Additional metadata for file upload + } + } + })); + + // Wait for async operations + await new Promise(resolve => setTimeout(resolve, 100)); + + // Verify window.parent.postMessage was called with MULTIPLE_UPLOAD_FILES_RESPONSE + expect(postMessageSpy).toHaveBeenCalledWith( + expect.objectContaining({ + type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:element:MULTI_FILE_INPUT:123`, + data: {"error": "No files selected"}, // Response data (success or error) + }), + 'http://localhost.com' + ); + } + }); + // test('should handle multi file upload message event with success', async () => { + // const composableElement = { + // ...element, + // rows: [{ + // elements: [{ + // elementType: ElementType.MULTI_FILE_INPUT, + // elementName: 'element:MULTI_FILE_INPUT:123', + // table: 'patients', + // column: 'file_uploads', + // required: false, + // label: 'Upload Files', + // ...stylesOptions, + // }], + // }], + // }; + + // const id = `${FRAME_ELEMENT}:group:123:ERROR:`; + // const postMessageSpy = jest.fn(); + // let messageHandler; + // let windowMock; + + // windowSpy.mockImplementation(() => { + // windowMock = { + // name: id, + // location: { + // href: `http://localhost/?${btoa(JSON.stringify({ + // record: composableElement, + // metaData: { clientDomain: 'http://localhost.com' }, + // }))}`, + // }, + // parent: { + // postMessage: postMessageSpy, + // addEventListener: jest.fn(), + // }, + // addEventListener: (event, handler) => { + // if (event === 'message') messageHandler = handler; + // }, + // dispatchEvent: jest.fn(), + // }; + // return windowMock; + // }); + + // // Mock successful network requests for each file + // const mockRequest = jest.fn().mockResolvedValue({ skyflow_id: 'id1', file: 'test-file-1.txt' }); + // Client.prototype.request = mockRequest; + // Client.prototype.config = { vaultURL: 'https://vaulturl.com', vaultID: 'vault123' }; + + // FrameElementInit.startFrameElement(); + // postMessageSpy.mockClear(); + + // // Find MULTI_FILE_INPUT form element and inject FileList state + // const multiFileElement = FrameElementInit.frameEle.iframeFormList.find( + // (f) => f.iFrameName === 'element:MULTI_FILE_INPUT:123' + // ); + // expect(multiFileElement).toBeTruthy(); + + // if (multiFileElement) { + // Object.defineProperty(multiFileElement, 'state', { + // value: { + // value: mockFileList, // emulate user having selected files + // name: 'file_uploads', + // isRequired: false, + // isValid: true, + // isEmpty: false, + // isComplete: true, + // }, + // configurable: true, + // writable: true, + // }); + // } + + // windowSpy.mockImplementation(() => windowMock); // keep same window for handler + + // if (!messageHandler) throw new Error('messageHandler missing'); + + // messageHandler({ + // data: { + // name: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES}:element:MULTI_FILE_INPUT:123`, + // clientConfig: { + // vaultId: 'vault123', + // vaultURL: 'https://vaulturl.com', + // authToken: 'token123', + // uuid: 'uuid123', + // }, + // options: {}, + // }, + // }); + + // await flushPromises(5); + + // expect(postMessageSpy).toHaveBeenCalledWith( + // expect.objectContaining({ + // type: `${ELEMENT_EVENTS_TO_IFRAME.MULTIPLE_UPLOAD_FILES_RESPONSE}:element:MULTI_FILE_INPUT:123`, + // data: expect.objectContaining({ + // fileUploadResponse: expect.arrayContaining([ + // expect.objectContaining({ skyflow_id: 'id1' }), + // ]), + // }), + // }), + // 'http://localhost.com' + // ); + + // // Ensure request called once per file + // expect(mockRequest).toHaveBeenCalledTimes(mockFileList.length); + // }); + }); diff --git a/tests/core/internal/iframe-form/iframe-form.test.js b/tests/core/internal/iframe-form/iframe-form.test.js index 7f452ea9..3c58e88b 100644 --- a/tests/core/internal/iframe-form/iframe-form.test.js +++ b/tests/core/internal/iframe-form/iframe-form.test.js @@ -2205,58 +2205,6 @@ describe('test file Upload method', () => { }); }); - // test.only('initialize iFrame and upload with file input', () => { - // // const form = new IFrameForm("controllerId", "", "ERROR"); - // // form.setClient(fileClientObj) - // // form.setClientMetadata(metaData) - // // form.setContext(context) - - // // const frameReadyEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FRAME_READY + 'controllerId'); - // // const frameReadyCb = frameReadyEvent[0][1]; - - // // expect(() => { frameReadyCb({}) }).toThrow(SkyflowError) - - // // frameReadyCb({ name: COLLECT_FRAME_CONTROLLER }) - - // // expect(() => { frameReadyCb({ name: "element:type:aW52YWxpZA==" }) }).toThrow(SkyflowError) - // const skyflowInit = jest.fn(); - // windowSpy = jest.spyOn(global, 'window', 'get'); - // windowSpy.mockImplementation(() => ({ - // name: `${FRAME_ELEMENT}:FILE_INPUT:123:ERROR:`, - // location: { - // href: `http://localhost/?${btoa(JSON.stringify({record: element, metaData: {clientDomain: 'http://localhost.com'}}))}`, - // } - // })) - - // // frameReadyCb({ name: file_element }) - - // const createFormElement = skyflowInit.mock.calls[0][0] - // const fileElement = createFormElement(file_element) - - // const fileUploadEvent = on.mock.calls.filter((data) => data[0] === ELEMENT_EVENTS_TO_IFRAME.FILE_UPLOAD + 'controllerId'); - // const fileUploadCb = fileUploadEvent[0][1]; - // const cb2 = jest.fn(); - // fileUploadCb(fileData, cb2) - - // setTimeout(() => { - // expect(cb2.mock.calls[0][0].error).toBeDefined() - // }, 3000) - - // setTimeout(() => { - // expect(cb2.mock.calls[0][0].error).toBeDefined() - // }, 3000) - - // fileElement.setValue({ - // lastModified: '', - // lastModifiedDate: '', - // name: "sample.jpg", - // size: 48848, - // type: "image/jpeg", - // webkitRelativePath: "" - // }) - // const cb3 = jest.fn() - // fileUploadCb(fileData, cb3) - // }); test('validate for file input - valid blockZeroSizeFile boolean', () => { const elementType = ELEMENTS.FILE_INPUT.name; var options = { blockEmptyFiles: true}; diff --git a/tests/skyflow.test.js b/tests/skyflow.test.js index 92bf6b61..e19db09a 100644 --- a/tests/skyflow.test.js +++ b/tests/skyflow.test.js @@ -12,6 +12,7 @@ import ComposableContainer from '../src/core/external/collect/compose-collect-co import SkyflowContainer from '../src/core/external/skyflow-container'; import Client from '../src/client' import logs from '../src/utils/logs'; +import { ComposableRevealContainer } from '../src/index-node'; jest.mock('../src/utils/jwt-utils', () => ({ __esModule: true, @@ -90,6 +91,9 @@ describe('Create container', () => { const composableContainer = skyflow.container(ContainerType.COMPOSABLE,{layout:[1]}); expect(composableContainer).toBeInstanceOf(ComposableContainer); + const revealComposableContainer = skyflow.container(ContainerType.COMPOSE_REVEAL,{layout:[1]}); + expect(revealComposableContainer).toBeInstanceOf(ComposableRevealContainer); + try { const revealContainer = skyflow.container('test'); } catch (err) { From 641a405e29f6728cc3af044a79e90111b8b5674d Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Mon, 17 Nov 2025 17:04:28 +0530 Subject: [PATCH 49/50] SK-2360 test commit --- src/core/internal/frame-element-init.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/internal/frame-element-init.ts b/src/core/internal/frame-element-init.ts index 20ab5659..c5691e4f 100644 --- a/src/core/internal/frame-element-init.ts +++ b/src/core/internal/frame-element-init.ts @@ -497,6 +497,7 @@ export default class FrameElementInit { const formData = new FormData(); formData.append('columnName', state.name); if (tableName) formData.append('tableName', tableName); + if (preserveFileName) { formData.append('file', file); } else { From b418697de6cb71ac8755d55116f7da1d7ea231ac Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Mon, 17 Nov 2025 11:36:52 +0000 Subject: [PATCH 50/50] [AUTOMATED] Release - 2.5.0-dev.641a405 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index aaf05319..aefa6708 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "skyflow-js", "preferGlobal": true, "analyze": false, - "version": "2.4.4-dev.af74922", + "version": "2.5.0-dev.641a405", "author": "Skyflow", "description": "Skyflow JavaScript SDK", "homepage": "https://github.com/skyflowapi/skyflow-js",