From c5b84915cdc4c27727af1ac668e1a8d1567eab99 Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Mon, 19 Oct 2026 21:05:15 +0530 Subject: [PATCH 1/2] SK-3231 add return file metadata support in js sdk --- CHANGELOG.md | 8 ++ core/helpers/index.ts | 3 + core/internal/iframe-form/index.ts | 3 + core/internal/index.ts | 3 + core/libs/element-options.ts | 7 ++ core/types/index.ts | 21 ++++ packages/skyflow-js/README.md | 106 ++++++++++++++++-- packages/skyflow-js/src/api-utils/collect.ts | 13 ++- packages/skyflow-js/src/api-utils/reveal.ts | 14 ++- packages/skyflow-js/src/index-node.ts | 1 + .../src/internal/frame-element-init.ts | 6 +- .../src/internal/reveal/reveal-frame.ts | 5 +- .../skyflow-frame/skyflow-frame-controller.ts | 18 ++- packages/skyflow-js/src/utils/common/index.ts | 1 + .../api-utils/collect-upload-response.test.js | 24 ++++ .../tests/api-utils/reveal-render-url.test.js | 50 +++++++++ .../frame-element-init.additional.test.js | 69 +++++++++++- ...w-frame-controller-upload-tokenize.test.js | 12 +- ...w-frame-controller-upload-tokenize.test.ts | 4 +- .../tests/libs/element-options.test.js | 27 ++++- .../skyflow-js/tests/utils/helpers.test.js | 15 +++ 21 files changed, 374 insertions(+), 36 deletions(-) create mode 100644 packages/skyflow-js/tests/api-utils/collect-upload-response.test.js create mode 100644 packages/skyflow-js/tests/api-utils/reveal-render-url.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 2867a96f..b7fa361a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to this project will be documented in this file. +## [Unreleased] +### Added +- `returnFileMetadata` option on `FILE_INPUT` and `MULTI_FILE_INPUT` elements (default `false`): when `true`, each entry of the `fileUploadResponse` from `uploadFiles()` and `uploadMultipleFiles()` includes `fileMetadata` (`fileName`, `fileSize`, `fileType`) +- `returnFileMetadata` option on file render elements, passed in the `create()` options like `enableCopy` (default `true`, preserving existing behaviour): set `false` to omit `fileMetadata` from the render response; applies to reveal and composable reveal containers +- `FileUploadMetadata` type export +### Fixed +- `uploadFiles()` on a collect container returned each `fileUploadResponse` entry as a JSON string (visible as escaped quotes); entries are now plain objects, matching the composable container and the documented response. Callers that worked around this with their own `JSON.parse` must remove it. + ## [2.8.0] - 2026-9-22 ### Added - Render zip files in composable reveal file elements (list-detail layout with per-file preview), opt-in via `renderFile({ zipRender: true })` diff --git a/core/helpers/index.ts b/core/helpers/index.ts index a0308385..6e42620f 100644 --- a/core/helpers/index.ts +++ b/core/helpers/index.ts @@ -365,6 +365,9 @@ export const formatRevealElementOptions = (options:IRevealElementOptions) => { if (Object.prototype.hasOwnProperty.call(revealOptions, 'enableCopy') && !validateBooleanOptions(revealOptions.enableCopy)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['enableCopy'], true); } + if (Object.prototype.hasOwnProperty.call(revealOptions, 'returnFileMetadata') && !validateBooleanOptions(revealOptions.returnFileMetadata)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['returnFileMetadata'], true); + } if (Object.prototype.hasOwnProperty.call(revealOptions, 'format') || Object.prototype.hasOwnProperty.call(revealOptions, 'translation')) { const revealElementMask:any[] = []; diff --git a/core/internal/iframe-form/index.ts b/core/internal/iframe-form/index.ts index 181a77ec..e7cf1154 100644 --- a/core/internal/iframe-form/index.ts +++ b/core/internal/iframe-form/index.ts @@ -108,6 +108,9 @@ export default class IFrameFormElement extends EventEmitter { preserveFileName: boolean = true; + // Ask the vault for fileMetadata on upload. Off by default (API default too). + returnFileMetadata: boolean = false; + allowedFileType: any; blockEmptyFiles: boolean = false; diff --git a/core/internal/index.ts b/core/internal/index.ts index c54c4604..c6b25ced 100644 --- a/core/internal/index.ts +++ b/core/internal/index.ts @@ -114,6 +114,9 @@ export default class FrameElement { if (Object.prototype.hasOwnProperty.call(options, 'preserveFileName')) { this.iFrameFormElement.preserveFileName = options?.preserveFileName; } + if (Object.prototype.hasOwnProperty.call(options, 'returnFileMetadata')) { + this.iFrameFormElement.returnFileMetadata = options?.returnFileMetadata; + } if (Object.prototype.hasOwnProperty.call(options, 'allowedFileType')) { this.iFrameFormElement.allowedFileType = options?.allowedFileType; } diff --git a/core/libs/element-options.ts b/core/libs/element-options.ts index 6d9f6095..a3c403bd 100644 --- a/core/libs/element-options.ts +++ b/core/libs/element-options.ts @@ -386,6 +386,10 @@ export const formatOptions = ( if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'preserveFileName')) { formattedOptions = { ...formattedOptions, preserveFileName: true }; } + // Opt-in: the vault only returns file metadata for an upload when asked. + if (!Object.prototype.hasOwnProperty.call(formattedOptions, 'returnFileMetadata')) { + formattedOptions = { ...formattedOptions, returnFileMetadata: false }; + } break; } @@ -404,6 +408,9 @@ 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 (Object.prototype.hasOwnProperty.call(formattedOptions, 'returnFileMetadata') && !validateBooleanOptions(formattedOptions.returnFileMetadata)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS, ['returnFileMetadata'], true); + } if (elementType === ELEMENTS.FILE_INPUT.name || elementType === ELEMENTS.MULTI_FILE_INPUT.name) { if (options.allowedFileType) { if (!Array.isArray(options.allowedFileType)) { diff --git a/core/types/index.ts b/core/types/index.ts index f6443166..a61ffaad 100644 --- a/core/types/index.ts +++ b/core/types/index.ts @@ -520,6 +520,21 @@ export interface EventConfig{ vaultID: string, } +// Metadata of an uploaded file, returned by the vault for every upload because the +// SDK always sends `returnFileMetadata: true`. +export interface IFileUploadMetadata { + fileName: string, + fileSize: number, + fileType: string, +} + +// One entry of `fileUploadResponse`: the record id and, when requested with +// `returnFileMetadata: true`, the uploaded file's `fileMetadata`. +export interface IFileUploadRecord { + skyflow_id: string, + fileMetadata?: IFileUploadMetadata, +} + export interface UploadFilesResponse { fileUploadResponse?: Record, errorResponse?: Record, @@ -619,6 +634,10 @@ export interface IRevealElementOptions { enableCopy?: boolean; format?: string; translation?:Record + // File render elements only. Ask the vault for `fileMetadata` with the render + // response. Default true: the API default is false, but the SDK has always requested + // it, surfaces it as `success.fileMetadata`, and uses it as a fallback for zip detection. + returnFileMetadata?: boolean; } // Internal element/frame types — variant-neutral. Relocated from @@ -684,6 +703,8 @@ export interface CVVMap { // without importing a package symbol; the file branch never runs for flowDB. export interface IFileCollectElementOptions { preserveFileName?: boolean, + // Include `fileMetadata` in each upload response entry. Default false. + returnFileMetadata?: boolean, allowedFileType?: string[], blockEmptyFiles?: boolean, maxFileSize?: number, diff --git a/packages/skyflow-js/README.md b/packages/skyflow-js/README.md index c9677674..11d588bd 100644 --- a/packages/skyflow-js/README.md +++ b/packages/skyflow-js/README.md @@ -1654,11 +1654,18 @@ container.uploadFiles(); { fileUploadResponse: [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } ``` + +`fileMetadata` is included only when the element was created with `returnFileMetadata: true` (the samples above assume it). It describes the stored file: `fileName` is the name saved in the vault (the original name when `preserveFileName` is true, otherwise the generated one), `fileSize` is in bytes, and `fileType` is the MIME type. ### File upload with options: Along with fileElementInput, you can define other options in the Options object as described below: @@ -1754,7 +1761,12 @@ collectContainer.uploadFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -1840,7 +1852,12 @@ collectContainer.uploadFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -2540,7 +2557,12 @@ container.uploadFiles(); { fileUploadResponse: [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -2642,7 +2664,12 @@ collectContainer.uploadFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -2731,7 +2758,12 @@ collectContainer.uploadFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -2842,7 +2874,12 @@ element.uploadMultipleFiles(); { fileUploadResponse: [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -2854,7 +2891,8 @@ Along with fileElementInput, you can define other options in the Options object const options = { allowedFileType: String[], // Optional. Restricts uploads to the listed file extensions (e.g. [".pdf", ".png"]). blockEmptyFiles: Boolean, // Optional. When true, rejects files with 0 bytes. Default: false. - preserveFileName: Boolean, // Optional. When true, keeps the original filename on upload. Default: false. + preserveFileName: Boolean, // Optional. When true, keeps the original filename on upload. Default: true. + returnFileMetadata: Boolean, // Optional. When true, each upload response entry includes fileMetadata. Default: false. maxFileSize: Number, // Optional. Maximum size in bytes for each individual file. Default: 32000000 (32 MB). maxFileCount: Number, // Optional. Maximum number of files that can be selected at once. Must be a positive integer. Default: 4. } @@ -2862,7 +2900,8 @@ const options = { - `allowedFileType`: An array of strings indicating which file extensions are accepted for upload. - `blockEmptyFiles`: When `true`, files with a size of 0 bytes are rejected. -- `preserveFileName`: When `true`, the original filename is preserved on upload. +- `preserveFileName`: When `true`, the original filename is preserved on upload. Defaults to `true`. +- `returnFileMetadata`: When `true`, the vault returns `fileMetadata` (`fileName`, `fileSize`, `fileType`) for each uploaded file and it is included in the `fileUploadResponse` entry. Defaults to `false`, matching the vault API default. Supported on `FILE_INPUT` and `MULTI_FILE_INPUT` elements in both collect and composable containers. - `maxFileSize`: Maximum allowed size **per file**, in bytes. If any file exceeds this limit, a validation error is shown with the filename. Defaults to `32000000` (32 MB). Only applies to `MULTI_FILE_INPUT` elements. - `maxFileCount`: Maximum number of files that can be selected for a single upload. Must be a positive integer. Defaults to `4`. Only applies to `MULTI_FILE_INPUT` elements. @@ -2955,7 +2994,12 @@ fileElement.uploadMultipleFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -3043,10 +3087,20 @@ fileElement.uploadMultipleFiles(); { "fileUploadResponse": [ { - "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882" + "skyflow_id": "431eaa6c-5c15-4513-aa15-29f50babe882", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } }, { - "skyflow_id": "546eaa6c-5c15-4513-aa15-29f50babe809" + "skyflow_id": "546eaa6c-5c15-4513-aa15-29f50babe809", + "fileMetadata": { + "fileName": "kyc.jpeg", + "fileSize": 1024, + "fileType": "application/jpeg" + } } ] } @@ -3658,6 +3712,20 @@ const fileElement = { ``` The inputStyles and errorTextStyles parameters accept a styles object as described in the [previous section](https://github.com/skyflowapi/skyflow-js#step-2-create-a-collect-element) for collecting data. But for render file elements, inputStyles accepts only base variant, global style objects. +#### File render options +`create()` accepts an options object as its second argument. For file render elements it supports: + +```javascript +const options = { + returnFileMetadata: true, // Optional. When true, the render response includes fileMetadata (fileName, fileSizeKB, fileType). Default: true. +}; + +const fileElement = container.create(fileElementInput, options); +``` + +The vault API defaults `returnFileMetadata` to `false`; the SDK defaults it to `true` to keep the existing render response. Set it to `false` to omit `fileMetadata` from the response, in which case zip detection relies on the file name alone. + + An example of a inputStyles object: ```javascript @@ -4279,6 +4347,20 @@ const fileElement = { ``` The inputStyles and errorTextStyles parameters accept a styles object as described in the [previous section](https://github.com/skyflowapi/skyflow-js#step-2-create-a-collect-element) for collecting data. But for render file elements, inputStyles accepts only base variant, global style objects. +#### File render options +`create()` accepts an options object as its second argument. For file render elements it supports: + +```javascript +const options = { + returnFileMetadata: true, // Optional. When true, the render response includes fileMetadata (fileName, fileSizeKB, fileType). Default: true. +}; + +const fileElement = container.create(fileElementInput, options); +``` + +The vault API defaults `returnFileMetadata` to `false`; the SDK defaults it to `true` to keep the existing render response. Set it to `false` to omit `fileMetadata` from the response, in which case zip detection relies on the file name alone. + + An example of a inputStyles object: ```javascript diff --git a/packages/skyflow-js/src/api-utils/collect.ts b/packages/skyflow-js/src/api-utils/collect.ts index aaefe761..1a9c1deb 100644 --- a/packages/skyflow-js/src/api-utils/collect.ts +++ b/packages/skyflow-js/src/api-utils/collect.ts @@ -5,6 +5,7 @@ import omit from 'lodash/omit'; import { getAccessToken } from '@core/utils/bus-events'; import Client from '@core/client'; import { + IFileUploadRecord, IInsertRecordInput, IInsertRecord, MessageType, LogLevel, InsertResponse, @@ -156,9 +157,17 @@ export const constructFinalUpdateRecordResponse = ( }; }; -export const constructUploadResponse = (response) => { +// Shapes the vault's file upload response for the client. `fileMetadata` is returned +// by the vault when the request carries `returnFileMetadata: true`, which every upload +// path sets; it is passed through untouched ({ fileName, fileSize, fileType }). +export const constructUploadResponse = (response): IFileUploadRecord => { const data = typeof response === 'string' ? JSON.parse(response) : response; - return JSON.stringify({ skyflow_id: data.skyflowID }) as any; + // Returns a plain object: a stringified entry would reach the caller with escaped + // quotes inside `fileUploadResponse` and force a second JSON.parse. + return { + skyflow_id: data.skyflowID, + ...(data.fileMetadata ? { fileMetadata: data.fileMetadata } : {}), + }; }; // keyify / checkDuplicateColumns / constructElementsInsertReq moved to diff --git a/packages/skyflow-js/src/api-utils/reveal.ts b/packages/skyflow-js/src/api-utils/reveal.ts index 5f264b93..ab5dd239 100644 --- a/packages/skyflow-js/src/api-utils/reveal.ts +++ b/packages/skyflow-js/src/api-utils/reveal.ts @@ -131,16 +131,22 @@ const getTokenRecordsFromVault = ( }); }; +// `returnFileMetadata` defaults to true: the vault API defaults it to false, but the +// SDK has always requested it, surfaces it as `success.fileMetadata`, and uses the +// file type from it as a fallback for zip detection. Callers turn it off through the +// `returnFileMetadata` element option passed to `create()`. export const getFileURLForRender = ( skyflowIdRecord: IRevealRecord, client: Client, authToken: string, + returnFileMetadata: boolean = true, ): Promise => { let paramList: string = ''; paramList += `${skyflowIdRecord.skyflowID}?`; - paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}&returnFileMetadata=true`; + paramList += `fields=${skyflowIdRecord.column}&${FILE_DOWNLOAD_URL_PARAM}`; + if (returnFileMetadata) paramList += '&returnFileMetadata=true'; const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}/${paramList}`; return client.request({ @@ -156,12 +162,13 @@ export const getFileURLForRender = ( export const getFileURLFromVaultBySkyflowID = ( skyflowIdRecord: IRevealRecord, client: Client, + returnFileMetadata: boolean = true, ): Promise => new Promise((rootResolve, rootReject) => { try { const clientId = client.toJSON().metaData.uuid || ''; getAccessToken(clientId).then((authToken) => { getFileURLForRender( - skyflowIdRecord, client, authToken as string, + skyflowIdRecord, client, authToken as string, returnFileMetadata, ).then((resolvedResult: IRenderResponseType) => { rootResolve(resolvedResult); }).catch((err: any) => { @@ -182,10 +189,11 @@ export const getFileURLFromVaultBySkyflowIDComposable = ( skyflowIdRecord: IRevealRecord, client: Client, authToken: string, + returnFileMetadata: boolean = true, ): Promise => new Promise((rootResolve, rootReject) => { try { getFileURLForRender( - skyflowIdRecord, client, authToken as string, + skyflowIdRecord, client, authToken as string, returnFileMetadata, ).then((resolvedResult: IRenderResponseType) => { rootResolve(resolvedResult); }).catch((err: any) => { diff --git a/packages/skyflow-js/src/index-node.ts b/packages/skyflow-js/src/index-node.ts index 10a8b84c..fcd059f6 100644 --- a/packages/skyflow-js/src/index-node.ts +++ b/packages/skyflow-js/src/index-node.ts @@ -45,6 +45,7 @@ export type { ICollectOptions as CollectOptions, CollectResponse, UploadFilesResponse, + IFileUploadMetadata as FileUploadMetadata, CardMetadata, InputStyles, LabelStyles, diff --git a/packages/skyflow-js/src/internal/frame-element-init.ts b/packages/skyflow-js/src/internal/frame-element-init.ts index d936a312..a7aa4aa3 100644 --- a/packages/skyflow-js/src/internal/frame-element-init.ts +++ b/packages/skyflow-js/src/internal/frame-element-init.ts @@ -235,7 +235,7 @@ export default class FrameElementInit extends CoreFrameElementInit { const fileUploadObject: any = {}; const { - state, tableName, skyflowID, onFocusChange, preserveFileName, + state, tableName, skyflowID, onFocusChange, preserveFileName, returnFileMetadata, } = fileElement; if (state.isRequired) { @@ -261,6 +261,7 @@ export default class FrameElementInit extends CoreFrameElementInit { const value: Blob = Object.values(fileUploadObject)[0] as Blob; formData.append('columnName', column); + if (returnFileMetadata) formData.append('returnFileMetadata', 'true'); formData.append('tableName', tableName); if (preserveFileName) { @@ -327,7 +328,7 @@ export default class FrameElementInit extends CoreFrameElementInit { if (!this.#client) throw new SkyflowError(SKYFLOW_ERROR_CODE.CLIENT_CONNECTION, [], true); const { - state, tableName, onFocusChange, preserveFileName, + state, tableName, onFocusChange, preserveFileName, returnFileMetadata, } = fileElement; if (state.isRequired) { onFocusChange(false); @@ -350,6 +351,7 @@ export default class FrameElementInit extends CoreFrameElementInit { const uploadFile = (file: File, skyflowID?: string) => { const formData = new FormData(); formData.append('columnName', state.name); + if (returnFileMetadata) formData.append('returnFileMetadata', 'true'); if (tableName) formData.append('tableName', tableName); if (preserveFileName) { formData.append('file', file); diff --git a/packages/skyflow-js/src/internal/reveal/reveal-frame.ts b/packages/skyflow-js/src/internal/reveal/reveal-frame.ts index b7360046..8b55b5a9 100644 --- a/packages/skyflow-js/src/internal/reveal/reveal-frame.ts +++ b/packages/skyflow-js/src/internal/reveal/reveal-frame.ts @@ -233,7 +233,10 @@ class RevealFrame extends CoreRevealFrame { this.resetZipLayout(); return new Promise((resolve, reject) => { try { - getFileURLFromVaultBySkyflowIDComposable(data, this.#client, clientConfig.authToken) + // `returnFileMetadata` is an element option (create-time); default true. + getFileURLFromVaultBySkyflowIDComposable( + data, this.#client, clientConfig.authToken, this.record?.returnFileMetadata ?? true, + ) .then((resolvedResult) => { let url = ''; if (resolvedResult.fields && data.column) { diff --git a/packages/skyflow-js/src/internal/skyflow-frame/skyflow-frame-controller.ts b/packages/skyflow-js/src/internal/skyflow-frame/skyflow-frame-controller.ts index ca473e1c..9e549861 100644 --- a/packages/skyflow-js/src/internal/skyflow-frame/skyflow-frame-controller.ts +++ b/packages/skyflow-js/src/internal/skyflow-frame/skyflow-frame-controller.ts @@ -46,6 +46,7 @@ import { } from '../../api-utils/reveal'; import { printLog, parameterizedString } from '../../utils/logs-helper'; import { + IFileUploadRecord, IRevealRecord, IGetRecord, MessageType, @@ -316,7 +317,12 @@ class SkyflowFrameController renderFile(data: IRevealRecord, iframeName: string): Promise { return new Promise((resolve, reject) => { try { - getFileURLFromVaultBySkyflowID(data, this.client) + // `returnFileMetadata` is an element option carried on the record; default true. + getFileURLFromVaultBySkyflowID( + data, + this.client, + (data as IRevealRecord & { returnFileMetadata?: boolean }).returnFileMetadata ?? true, + ) .then((resolvedResult) => { let url = ''; if (resolvedResult.fields && data.column) { @@ -525,7 +531,10 @@ class SkyflowFrameController if (Object.prototype.hasOwnProperty.call(result.value, 'error')) { errorResponse.push(result.value); } else { - fileUploadResponse.push(result.value); + const entry = typeof result.value === 'string' + ? JSON.parse(result.value) + : result.value; + fileUploadResponse.push(entry); } } } else if (result.status === 'rejected') { @@ -544,7 +553,7 @@ class SkyflowFrameController const fileUploadObject: any = {}; const { - state, tableName, skyflowID, onFocusChange, preserveFileName, + state, tableName, skyflowID, onFocusChange, preserveFileName, returnFileMetadata, } = fileElement; if (state.isRequired) { @@ -570,6 +579,7 @@ class SkyflowFrameController const value: Blob = Object.values(fileUploadObject)[0] as Blob; formData.append('columnName', column); + if (returnFileMetadata) formData.append('returnFileMetadata', 'true'); formData.append('tableName', tableName ?? ''); if (preserveFileName) { @@ -591,7 +601,7 @@ class SkyflowFrameController const client = this.client; const sendRequest = (): - Promise => new Promise((rootResolve, rootReject) => { + Promise => new Promise((rootResolve, rootReject) => { const clientId = client.toJSON()?.metaData?.uuid || ''; getAccessToken(clientId).then((authToken) => { client diff --git a/packages/skyflow-js/src/utils/common/index.ts b/packages/skyflow-js/src/utils/common/index.ts index 6d9c9d4b..5fe7259e 100644 --- a/packages/skyflow-js/src/utils/common/index.ts +++ b/packages/skyflow-js/src/utils/common/index.ts @@ -53,6 +53,7 @@ export interface CollectElementUpdateOptions extends ICollectElementUpdateOption // (flowDB has no file API). See Decision 2.3. export interface CollectElementOptions extends ICollectElementOptionsBase { preserveFileName?: boolean, + returnFileMetadata?: boolean, allowedFileType?: string[], blockEmptyFiles?: boolean, maxFileSize?: number, diff --git a/packages/skyflow-js/tests/api-utils/collect-upload-response.test.js b/packages/skyflow-js/tests/api-utils/collect-upload-response.test.js new file mode 100644 index 00000000..5d6d6f3d --- /dev/null +++ b/packages/skyflow-js/tests/api-utils/collect-upload-response.test.js @@ -0,0 +1,24 @@ +import { constructUploadResponse } from "../../src/api-utils/collect"; + +describe("constructUploadResponse", () => { + const metadata = { fileName: "kyc.jpeg", fileSize: 1024, fileType: "application/jpeg" }; + + test("maps skyflowID to skyflow_id and passes fileMetadata through untouched", () => { + const out = (constructUploadResponse({ + skyflowID: "4423ccdf-75eb-4e2e-abfc-acb43b1440cd", + fileMetadata: metadata, + })); + expect(out).toEqual({ skyflow_id: "4423ccdf-75eb-4e2e-abfc-acb43b1440cd", fileMetadata: metadata }); + }); + + test("accepts the raw JSON string the client returns", () => { + const out = (constructUploadResponse(JSON.stringify({ skyflowID: "id-1", fileMetadata: metadata }))); + expect(out.fileMetadata).toEqual(metadata); + }); + + test("omits fileMetadata when the vault does not return it", () => { + const out = (constructUploadResponse({ skyflowID: "id-2" })); + expect(out).toEqual({ skyflow_id: "id-2" }); + expect(out).not.toHaveProperty("fileMetadata"); + }); +}); diff --git a/packages/skyflow-js/tests/api-utils/reveal-render-url.test.js b/packages/skyflow-js/tests/api-utils/reveal-render-url.test.js new file mode 100644 index 00000000..24c7fb73 --- /dev/null +++ b/packages/skyflow-js/tests/api-utils/reveal-render-url.test.js @@ -0,0 +1,50 @@ +import { getFileURLForRender } from "../../src/api-utils/reveal"; + +describe("getFileURLForRender: returnFileMetadata query parameter", () => { + const record = { skyflowID: "id-1", column: "file", table: "files" }; + const makeClient = () => ({ + config: { vaultURL: "https://vault.test", vaultID: "vault1" }, + request: jest.fn(() => Promise.resolve({})), + }); + + test("is sent by default (SDK default true, API default false)", async () => { + const client = makeClient(); + await getFileURLForRender(record, client, "token"); + const { url } = client.request.mock.calls[0][0]; + // match path + query only: the base URL may be rewritten for local dev proxies + expect(url).toMatch(/\/v1\/vaults\/vault1\/files\/id-1\?fields=file&downloadURL=true&contentDisposition=INLINE&returnFileMetadata=true$/); + }); + + test("is omitted when the caller passes false", async () => { + const client = makeClient(); + await getFileURLForRender(record, client, "token", false); + const { url } = client.request.mock.calls[0][0]; + expect(url).toMatch(/\/v1\/vaults\/vault1\/files\/id-1\?fields=file&downloadURL=true&contentDisposition=INLINE$/); + expect(url).not.toContain("returnFileMetadata"); + }); +}); + +import * as busEvents from "@core/utils/bus-events"; +import { getFileURLFromVaultBySkyflowID } from "../../src/api-utils/reveal"; + +describe("getFileURLFromVaultBySkyflowID (collect reveal path): element option forwarded", () => { + const record = { skyflowID: "id-1", column: "file", table: "files" }; + const makeClient = () => ({ + config: { vaultURL: "https://vault.test", vaultID: "vault1" }, + request: jest.fn(() => Promise.resolve({ fields: { file: "https://signed" } })), + toJSON: () => ({ metaData: { uuid: "client-1" } }), + }); + beforeEach(() => { busEvents.getAccessToken = jest.fn(() => Promise.resolve("token")); }); + + test("requests metadata by default", async () => { + const client = makeClient(); + await getFileURLFromVaultBySkyflowID(record, client); + expect(client.request.mock.calls[0][0].url).toContain("&returnFileMetadata=true"); + }); + + test("omits the parameter when the element option is false", async () => { + const client = makeClient(); + await getFileURLFromVaultBySkyflowID(record, client, false); + expect(client.request.mock.calls[0][0].url).not.toContain("returnFileMetadata"); + }); +}); diff --git a/packages/skyflow-js/tests/core/internal/frame-element-init.additional.test.js b/packages/skyflow-js/tests/core/internal/frame-element-init.additional.test.js index 47e5c6e2..2032abb7 100644 --- a/packages/skyflow-js/tests/core/internal/frame-element-init.additional.test.js +++ b/packages/skyflow-js/tests/core/internal/frame-element-init.additional.test.js @@ -81,6 +81,7 @@ const makeFileElement = ({ name = 'upload', tableName = 'files_table', preserveFileName = true, + returnFileMetadata = false, maxFileCount = 4, maxFileSize = 32_000_000, }) => { @@ -94,6 +95,7 @@ const makeFileElement = ({ tableName, onFocusChange: jest.fn(), preserveFileName, + returnFileMetadata, maxFileCount, maxFileSize, fieldType: multiple ? ELEMENTS.MULTI_FILE_INPUT.name : ELEMENTS.FILE_INPUT.name, @@ -538,7 +540,7 @@ describe('FrameElementInit extended unit tests', () => { test('multipleUploadFiles uploads a file with an invalid original name when preserveFileName is false', async () => { const instance = new FrameElementInit(); const files = [makeFile('my file.pdf')]; - const fileElement = makeFileElement({ multiple: true, files, preserveFileName: false }); + const fileElement = makeFileElement({ multiple: true, files, preserveFileName: false, returnFileMetadata: true }); instance.iframeFormList = [fileElement]; helpers.fileValidation = jest.fn(() => true); helpers.vaildateFileName = jest.fn(() => false); // original name is invalid @@ -549,6 +551,7 @@ describe('FrameElementInit extended unit tests', () => { await expect(instance['multipleUploadFiles'](fileElement, config, undefined)) .resolves.toEqual({ fileUploadResponse: [{ skyflow_id: 'abc' }] }); expect(mockClientRequest).toHaveBeenCalledTimes(1); + expect(mockClientRequest.mock.calls[0][0].body.get('returnFileMetadata')).toBe('true'); const uploaded = mockClientRequest.mock.calls[0][0].body.get('file'); expect(uploaded.name).toBe('generated-uuid.pdf'); expect(helpers.vaildateFileName).not.toHaveBeenCalled(); @@ -586,19 +589,20 @@ describe('FrameElementInit extended unit tests', () => { }); // ================= uploadFiles tests ================= - const makeSingleFileElement = ({ file, isRequired = false, preserveFileName = true, skyflowID } = {}) => ({ + const makeSingleFileElement = ({ file, isRequired = false, preserveFileName = true, returnFileMetadata = false, skyflowID } = {}) => ({ state: { value: file, name: 'uploadColumn', isRequired }, tableName: 'files_table', skyflowID, onFocusChange: jest.fn(), preserveFileName, + returnFileMetadata, fieldType: ELEMENTS.FILE_INPUT.name, }); test('uploadFiles success with preserveFileName true and skyflowID included', async () => { const instance = new FrameElementInit(); const file = makeFile('original.txt', 5); - const element = makeSingleFileElement({ file, isRequired: true, preserveFileName: true, skyflowID: 'sky123' }); + const element = makeSingleFileElement({ file, isRequired: true, preserveFileName: true, returnFileMetadata: true, skyflowID: 'sky123' }); helpers.fileValidation = jest.fn(() => true); helpers.vaildateFileName = jest.fn(() => true); mockClientRequest.mockResolvedValue({ upload: 'ok' }); @@ -611,6 +615,7 @@ describe('FrameElementInit extended unit tests', () => { const callArgs = mockClientRequest.mock.calls[0][0]; const body = callArgs.body; expect(body.get('columnName')).toBe('uploadColumn'); + expect(body.get('returnFileMetadata')).toBe('true'); expect(body.get('tableName')).toBe('files_table'); expect(body.get('skyflowID')).toBe('sky123'); const uploadedFile = body.get('file'); @@ -648,6 +653,64 @@ describe('FrameElementInit extended unit tests', () => { genSpy.mockRestore(); }); + test('returnFileMetadata is off by default: no field is sent on single or multi uploads', async () => { + const instance = new FrameElementInit(); + helpers.fileValidation = jest.fn(() => true); + helpers.vaildateFileName = jest.fn(() => true); + mockClientRequest.mockResolvedValue({ skyflowID: 'x' }); + const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'tokenXYZ' }; + + const single = makeSingleFileElement({ file: makeFile('a.txt') }); + await instance.uploadFiles(single, config); + expect(mockClientRequest.mock.calls[0][0].body.get('returnFileMetadata')).toBeNull(); + + const files = [makeFile('b.txt')]; + const multi = makeFileElement({ multiple: true, files }); + multi.state.value = makeFileList(...files); + await instance['multipleUploadFiles'](multi, config, undefined); + expect(mockClientRequest.mock.calls[1][0].body.get('returnFileMetadata')).toBeNull(); + }); + + test('uploadFiles (single) passes fileMetadata from the vault response through to the caller', async () => { + const instance = new FrameElementInit(); + const file = makeFile('kyc.jpeg', 1024); + const element = makeSingleFileElement({ file, preserveFileName: true, returnFileMetadata: true }); + helpers.fileValidation = jest.fn(() => true); + helpers.vaildateFileName = jest.fn(() => true); + const vaultResponse = { + skyflowID: '4423ccdf-75eb-4e2e-abfc-acb43b1440cd', + fileMetadata: { fileName: 'kyc.jpeg', fileSize: 1024, fileType: 'application/jpeg' }, + }; + mockClientRequest.mockResolvedValue(JSON.stringify(vaultResponse)); + const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'tokenXYZ' }; + instance.iframeFormList = [element]; + await expect(instance['parallelUploadFiles']({}, config)) + .resolves.toEqual({ fileUploadResponse: [vaultResponse] }); + expect(mockClientRequest.mock.calls[0][0].body.get('returnFileMetadata')).toBe('true'); + }); + + test('multipleUploadFiles passes fileMetadata for every uploaded file', async () => { + const instance = new FrameElementInit(); + const files = [makeFile('a.pdf', 10), makeFile('b.pdf', 20)]; + const fileElement = makeFileElement({ multiple: true, files, returnFileMetadata: true }); + // the multi path only fans out over a real FileList; a plain array is treated as one file + fileElement.state.value = makeFileList(...files); + instance.iframeFormList = [fileElement]; + helpers.fileValidation = jest.fn(() => true); + helpers.vaildateFileName = jest.fn(() => true); + const meta = (name, size) => ({ fileName: name, fileSize: size, fileType: 'application/pdf' }); + mockClientRequest + .mockResolvedValueOnce({ skyflowID: 'id-a', fileMetadata: meta('a.pdf', 10) }) + .mockResolvedValueOnce({ skyflowID: 'id-b', fileMetadata: meta('b.pdf', 20) }); + const config = { vaultURL: 'https://vault.url', vaultID: 'vault123', authToken: 'token123' }; + const res = await instance['multipleUploadFiles'](fileElement, config, undefined); + expect(res.fileUploadResponse).toHaveLength(2); + expect(res.fileUploadResponse.map((r) => r.fileMetadata)).toEqual([meta('a.pdf', 10), meta('b.pdf', 20)]); + mockClientRequest.mock.calls.forEach((call) => { + expect(call[0].body.get('returnFileMetadata')).toBe('true'); + }); + }); + test('uploadFiles rejects on invalid file name when preserveFileName true', async () => { const instance = new FrameElementInit(); const file = makeFile('bad.txt', 5); diff --git a/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.js b/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.js index a97641dd..fd1acf14 100644 --- a/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.js +++ b/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.js @@ -357,7 +357,7 @@ describe('Uploading files to the vault', () => { // Check successful upload expect(result.error.fileUploadResponse).toHaveLength(1); - const parsedResponse = JSON.parse(result.error.fileUploadResponse[0]); + const parsedResponse = result.error.fileUploadResponse[0]; expect(parsedResponse.skyflow_id).toBe('success-file-id'); // Check failed upload @@ -495,6 +495,7 @@ describe('Uploading files to the vault', () => { test('should upload file with preserveFileName false and skyflowID set', (done) => { testValue.iFrameFormElement.preserveFileName = false; testValue.iFrameFormElement.skyflowID = 'test-skyflow-id'; + testValue.iFrameFormElement.returnFileMetadata = true; windowSpy.mockImplementation(()=>({ frames:{ 'element:FILE_INPUT:ID:CONTAINER-ID:ERROR:':{document:{ @@ -502,7 +503,8 @@ describe('Uploading files to the vault', () => { }} } })); - const clientReq = jest.fn(() => Promise.resolve({ skyflowID: 'file-upload-skyflow-id' })); + const fileMetadata = { fileName: 'kyc.jpeg', fileSize: 1024, fileType: 'application/jpeg' }; + const clientReq = jest.fn(() => Promise.resolve({ skyflowID: 'file-upload-skyflow-id', fileMetadata })); jest.spyOn(clientModule, 'fromJSON').mockImplementation(() => ({ ...clientData.client, request: clientReq, @@ -530,10 +532,14 @@ describe('Uploading files to the vault', () => { expect(cb2).toHaveBeenCalled(); expect(cb2.mock.calls[0][0].fileUploadResponse).toBeDefined(); expect(cb2.mock.calls[0][0].fileUploadResponse.length).toBe(1); - const parsedResponse = JSON.parse(cb2.mock.calls[0][0].fileUploadResponse[0]); + const parsedResponse = cb2.mock.calls[0][0].fileUploadResponse[0]; expect(parsedResponse.skyflow_id).toBe('file-upload-skyflow-id'); + // fileMetadata from the vault is passed through to the client response + expect(parsedResponse.fileMetadata).toEqual(fileMetadata); const requestArgs = clientReq.mock.calls[0][0]; expect(requestArgs.body.get('skyflowID')).toBe('test-skyflow-id'); + // every upload asks the vault for the metadata + expect(requestArgs.body.get('returnFileMetadata')).toBe('true'); done(); }, 1000); }); diff --git a/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.ts b/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.ts index 876484f1..e6b788f3 100644 --- a/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.ts +++ b/packages/skyflow-js/tests/core/internal/skyflow-frame/skyflow-frame-controller-upload-tokenize.test.ts @@ -363,7 +363,7 @@ describe("Uploading files to the vault", () => { // Check successful upload expect(result.error.fileUploadResponse).toHaveLength(1); - const parsedResponse = JSON.parse(result.error.fileUploadResponse[0]); + const parsedResponse = result.error.fileUploadResponse[0]; expect(parsedResponse.skyflow_id).toBe('success-file-id'); // Check failed upload @@ -476,7 +476,7 @@ describe("Uploading files to the vault", () => { expect(cb2).toHaveBeenCalled(); expect(cb2.mock.calls[0][0].fileUploadResponse).toBeDefined(); expect(cb2.mock.calls[0][0].fileUploadResponse.length).toBe(1); - const parsedResponse = JSON.parse(cb2.mock.calls[0][0].fileUploadResponse[0]); + const parsedResponse = cb2.mock.calls[0][0].fileUploadResponse[0]; expect(parsedResponse.skyflow_id).toBe("file-upload-skyflow-id"); expect(clientReq).toHaveBeenCalled(); const requestArgs = (clientReq.mock.calls as any[][])[0][0]; diff --git a/packages/skyflow-js/tests/libs/element-options.test.js b/packages/skyflow-js/tests/libs/element-options.test.js index b6497b96..1a6e222c 100644 --- a/packages/skyflow-js/tests/libs/element-options.test.js +++ b/packages/skyflow-js/tests/libs/element-options.test.js @@ -100,22 +100,41 @@ describe('test formatOptions function with format and translation', () => { test('should return preserveFileName true when not provied in options',()=>{ const formattedOptions = formatOptions(FileElementType.FILE_INPUT,{required:true},LogLevel.ERROR); - expect(formattedOptions).toEqual({required:true,preserveFileName:true}); + expect(formattedOptions).toEqual({required:true,preserveFileName:true,returnFileMetadata:false}); }); test('should return preserveFileName true for MULTI_FILE_INPUT when not provided in options',()=>{ const formattedOptions = formatOptions(FileElementType.MULTI_FILE_INPUT,{required:true},LogLevel.ERROR); - expect(formattedOptions).toEqual({required:true,preserveFileName:true}); + expect(formattedOptions).toEqual({required:true,preserveFileName:true,returnFileMetadata:false}); }); test('should keep preserveFileName false for MULTI_FILE_INPUT when provided as false',()=>{ const formattedOptions = formatOptions(FileElementType.MULTI_FILE_INPUT,{required:true,preserveFileName:false},LogLevel.ERROR); - expect(formattedOptions).toEqual({required:true,preserveFileName:false}); + expect(formattedOptions).toEqual({required:true,preserveFileName:false,returnFileMetadata:false}); + }); + + test('should default returnFileMetadata to false for FILE_INPUT and MULTI_FILE_INPUT',()=>{ + expect(formatOptions(FileElementType.FILE_INPUT,{required:true},LogLevel.ERROR)).toEqual({required:true,preserveFileName:true,returnFileMetadata:false}); + expect(formatOptions(FileElementType.MULTI_FILE_INPUT,{required:true},LogLevel.ERROR)).toEqual({required:true,preserveFileName:true,returnFileMetadata:false}); + }); + + test('should keep returnFileMetadata true when provided',()=>{ + expect(formatOptions(FileElementType.FILE_INPUT,{returnFileMetadata:true},LogLevel.ERROR)).toEqual({required:false,preserveFileName:true,returnFileMetadata:true}); + }); + + test('should throw error for returnFileMetadata provided as not of boolean type',(done)=>{ + try{ + formatOptions(FileElementType.FILE_INPUT,{returnFileMetadata:'yes'},LogLevel.ERROR); + done('should throw error'); + }catch(err){ + expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS.description, 'returnFileMetadata')); + done(); + } }); test('should return preserveFileName false when not provied as false in options',()=>{ const formattedOptions = formatOptions(FileElementType.FILE_INPUT,{required:true,preserveFileName:false},LogLevel.ERROR); - expect(formattedOptions).toEqual({required:true,preserveFileName:false}); + expect(formattedOptions).toEqual({required:true,preserveFileName:false,returnFileMetadata:false}); }); test('should throw errror for preserveFileName provied as not of boolean type',(done)=>{ diff --git a/packages/skyflow-js/tests/utils/helpers.test.js b/packages/skyflow-js/tests/utils/helpers.test.js index b980fd69..cd238006 100644 --- a/packages/skyflow-js/tests/utils/helpers.test.js +++ b/packages/skyflow-js/tests/utils/helpers.test.js @@ -492,6 +492,21 @@ describe('test formatRevealElementOptions function', () => { } }); + test('should pass returnFileMetadata through when it is a boolean', () => { + expect(formatRevealElementOptions({ returnFileMetadata: false })).toEqual({ returnFileMetadata: false }); + expect(formatRevealElementOptions({ returnFileMetadata: true })).toEqual({ returnFileMetadata: true }); + }); + + test('should throw error if passed returnFileMetadata option is invalid type', (done) => { + try { + formatRevealElementOptions({ returnFileMetadata: 'yes' }); + done('should throw error'); + } catch (err) { + expect(err?.error?.description).toEqual(parameterizedString(SKYFLOW_ERROR_CODE.INVALID_BOOLEAN_OPTIONS.description, 'returnFileMetadata')); + done(); + } + }); + test('should return mask in options if both format and translation are provided',()=>{ expect(formatRevealElementOptions({format:'YY-YY',translation:{Y:'[A-Z]'}})).toEqual({mask:['YY-YY',null,{Y:'[A-Z]'}]}); }); From 8d513cb2afd702622b8f301780f3cac6f2fab4ad Mon Sep 17 00:00:00 2001 From: skyflow-bharti Date: Wed, 21 Oct 2026 15:17:40 +0530 Subject: [PATCH 2/2] SK-3231 add return file metadata support in js sdk --- packages/skyflow-js/README.md | 6 ------ .../using-script-tag/composable-multi-file-upload.html | 1 + .../samples/using-script-tag/skyflow-file-upload.html | 1 + 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/skyflow-js/README.md b/packages/skyflow-js/README.md index 11d588bd..a48da980 100644 --- a/packages/skyflow-js/README.md +++ b/packages/skyflow-js/README.md @@ -3723,9 +3723,6 @@ const options = { const fileElement = container.create(fileElementInput, options); ``` -The vault API defaults `returnFileMetadata` to `false`; the SDK defaults it to `true` to keep the existing render response. Set it to `false` to omit `fileMetadata` from the response, in which case zip detection relies on the file name alone. - - An example of a inputStyles object: ```javascript @@ -4358,9 +4355,6 @@ const options = { const fileElement = container.create(fileElementInput, options); ``` -The vault API defaults `returnFileMetadata` to `false`; the SDK defaults it to `true` to keep the existing render response. Set it to `false` to omit `fileMetadata` from the response, in which case zip detection relies on the file name alone. - - An example of a inputStyles object: ```javascript diff --git a/packages/skyflow-js/samples/using-script-tag/composable-multi-file-upload.html b/packages/skyflow-js/samples/using-script-tag/composable-multi-file-upload.html index ec2fb583..a8860961 100644 --- a/packages/skyflow-js/samples/using-script-tag/composable-multi-file-upload.html +++ b/packages/skyflow-js/samples/using-script-tag/composable-multi-file-upload.html @@ -149,6 +149,7 @@

Collect Composable Elements

blockEmptyFiles: true, // default is false, if set to true, it will block empty files maxFileSize: 1000000, // default is 32000000 (32MB), if set, it will block files greater than the specified size maxFileCount: 3, // default is 5, if set, it will block if the number of files exceeds the specified number + returnFileMetadata: false } const fileElement = collectContainer.create({ diff --git a/packages/skyflow-js/samples/using-script-tag/skyflow-file-upload.html b/packages/skyflow-js/samples/using-script-tag/skyflow-file-upload.html index 98c91904..2041a60e 100644 --- a/packages/skyflow-js/samples/using-script-tag/skyflow-file-upload.html +++ b/packages/skyflow-js/samples/using-script-tag/skyflow-file-upload.html @@ -127,6 +127,7 @@

Collect Elements

const options = { allowedFileType: ["", ""], blockEmptyFiles: true, // default is false, if set to true, it will block empty files + returnFileMetadata: false, } const fileElement = collectContainer.create({