Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# skyflow-js
Skyflows JavaScript SDK can be used to securely collect, tokenize, and reveal sensitive data in the browser without exposing your front-end infrastructure to sensitive data.
Skyflow's JavaScript SDK can be used to securely collect, tokenize, and reveal sensitive data in the browser without exposing your front-end infrastructure to sensitive data.

---

Expand Down Expand Up @@ -2791,7 +2791,8 @@ Note:
### File upload limitations:

- Only non-executable file are allowed to be uploaded.
- Files must have a maximum size of 32 MB
- Files have a default maximum size of 32 MB per file. This limit is configurable using the `maxFileSize` option.
- Up to 4 files can be uploaded at a time by default. This limit is configurable using the `maxFileCount` option.
- File columns can't enable tokenization, redaction, or arrays.
- Re-uploading a file overwrites previously uploaded data.
- Partial uploads or resuming a previous upload isn't supported.
Expand Down Expand Up @@ -2851,10 +2852,19 @@ element.uploadMultipleFiles();
Along with fileElementInput, you can define other options in the Options object as described below:
```js
const options = {
allowedFileType: String[], // Optional, indicates the allowed file types for upload
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.
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.
}
```
`allowedFileType`: An array of string value that indicates the allowedFileTypes to be uploaded.

- `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.
- `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.

#### File upload with options example

Expand Down Expand Up @@ -2889,8 +2899,10 @@ const cardNumberElement = collectContainer.create({
label: 'Card Number',
type: Skyflow.ElementType.CARD_NUMBER,
});
const options = {
allowedFileType: [".pdf",".png"];
const options = {
allowedFileType: [".pdf", ".png"],
maxFileSize: 5000000, // 5 MB per file
maxFileCount: 3, // up to 3 files at once
};
const fileElement = collectContainer.create({
table: 'newTable',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "skyflow-js",
"preferGlobal": true,
"analyze": false,
"version": "2.7.7",
"version": "2.7.7-dev.a6816dc",
"author": "Skyflow",
"description": "Skyflow JavaScript SDK",
"homepage": "https://github.com/skyflowapi/skyflow-js",
Expand Down
2 changes: 2 additions & 0 deletions samples/using-script-tag/composable-multi-file-upload.html
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ <h3>Collect Composable Elements</h3>
const options = {
allowedFileType: ["<allowedFileType1>", "<allowedFileType2>"],
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
}

const fileElement = collectContainer.create({
Expand Down
19 changes: 15 additions & 4 deletions src/core/internal/frame-element-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,12 @@ export default class FrameElementInit {
}

const files = state.value instanceof FileList ? Array.from(state.value) : [state.value];
this.validateFiles(files, state, fileElement);
try {
this.validateFiles(files, state, fileElement);
} catch (err: any) {
rootReject({ errorResponse: [{ error: err?.error || err?.errors?.[0] || err }] });
return;
}

const uploadFile = (file: File, skyflowID?: string) => {
const formData = new FormData();
Expand Down Expand Up @@ -624,14 +629,19 @@ export default class FrameElementInit {
});

private validateFiles = (files: File[], state: any, fileElement: IFrameFormElement) => {
if (files.length > fileElement.maxFileCount) {
throw new SkyflowError(
SKYFLOW_ERROR_CODE.FILE_COUNT_EXCEEDED,
[String(fileElement.maxFileCount)],
true,
);
}
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);
Expand Down Expand Up @@ -700,8 +710,9 @@ export default class FrameElementInit {
type: error?.error?.type,
},
});
} else {
rootReject(error);
}
rootReject(error);
});
});

Expand Down
66 changes: 56 additions & 10 deletions src/core/internal/iframe-form/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ export default class IFrameFormElement extends EventEmitter {

blockEmptyFiles: boolean = false;

maxFileSize: number = 32_000_000;

maxFileCount: number = 4;

constructor(name: string, label: string, metaData: any, context: Context, skyflowID?: string) {
super();
const frameValues = name.split(':');
Expand Down Expand Up @@ -510,6 +514,7 @@ export default class IFrameFormElement extends EventEmitter {
validator(value: any) {
let resp = true;
let vaildateFileNames = true;
let fileSpecificError = '';

if (this.fieldType === ElementType.CARD_NUMBER && value) {
if (this.regex) {
Expand Down Expand Up @@ -537,16 +542,55 @@ export default class IFrameFormElement extends EventEmitter {
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;
const sizeMBDisplay = `${Math.round(this.maxFileSize / 1_000_000)} MB`;
const countExceeded = files.length > this.maxFileCount;
const firstOversizedFile = files.find((f) => f.size > this.maxFileSize);

if (countExceeded && firstOversizedFile) {
resp = false;
fileSpecificError = parameterizedString(
logs.errorLogs.FILE_COUNT_AND_SIZE_EXCEEDED,
String(this.maxFileCount),
sizeMBDisplay,
);
} else if (countExceeded) {
resp = false;
fileSpecificError = parameterizedString(
logs.errorLogs.FILE_COUNT_EXCEEDED,
String(this.maxFileCount),
);
} else {
const oversizedFileNames: string[] = [];
for (let i = 0; i < files.length; i += 1) {
try {
const fileValid = fileValidation(files[i], this.state.isRequired, {
allowedFileType: this.allowedFileType,
blockEmptyFiles: this.blockEmptyFiles,
maxFileSize: this.maxFileSize,
});
if (!fileValid) resp = false;
} catch (err: any) {
resp = false;
if (files[i].size > this.maxFileSize) {
oversizedFileNames.push(files[i].name);
}
}
if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name);
}
if (oversizedFileNames.length > 0) {
if (files.length === 1) {
fileSpecificError = parameterizedString(
logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE,
sizeMBDisplay,
);
} else {
fileSpecificError = parameterizedString(
logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME,
oversizedFileNames.join(', '),
sizeMBDisplay,
);
}
}
if (this.preserveFileName) vaildateFileNames = vaildateFileName(files[i].name);
}
} else {
// eslint-disable-next-line no-lonely-if
Expand All @@ -557,7 +601,9 @@ export default class IFrameFormElement extends EventEmitter {
if (!resp || !vaildateFileNames) {
this.isCustomValidationFailed = false;
if (!resp) {
if (this.label) {
if (fileSpecificError) {
this.errorText = fileSpecificError;
} else if (this.label) {
this.errorText = `${parameterizedString(
logs.errorLogs.INVALID_COLLECT_VALUE_WITH_LABEL,
this.label,
Expand Down
6 changes: 6 additions & 0 deletions src/core/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ export default class FrameElement {
if (Object.prototype.hasOwnProperty.call(options, 'blockEmptyFiles')) {
this.iFrameFormElement.blockEmptyFiles = options?.blockEmptyFiles;
}
if (Object.prototype.hasOwnProperty.call(options, 'maxFileSize')) {
this.iFrameFormElement.maxFileSize = options?.maxFileSize;
}
if (Object.prototype.hasOwnProperty.call(options, 'maxFileCount')) {
this.iFrameFormElement.maxFileCount = options?.maxFileCount;
}
}

// mount element onto dom
Expand Down
19 changes: 18 additions & 1 deletion src/libs/element-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,27 @@ export const formatOptions = (
if (Object.prototype.hasOwnProperty.call(options, 'blockEmptyFiles')) {
formattedOptions = {
...formattedOptions,
blockEmptyFiles: formattedOptions.blockEmptyFiles,
blockEmptyFiles: options.blockEmptyFiles,
};
}
}
if (elementType === ELEMENTS.MULTI_FILE_INPUT.name) {
if (Object.prototype.hasOwnProperty.call(options, 'maxFileSize')) {
if (typeof options.maxFileSize !== 'number' || options.maxFileSize <= 0) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS, ['maxFileSize'], true);
}
formattedOptions = { ...formattedOptions, maxFileSize: options.maxFileSize };
}
if (Object.prototype.hasOwnProperty.call(options, 'maxFileCount')) {
if (typeof options.maxFileCount !== 'number' || options.maxFileCount <= 0 || !Number.isInteger(options.maxFileCount)) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_POSITIVE_NUMBER_OPTIONS, ['maxFileCount'], true);
}
formattedOptions = { ...formattedOptions, maxFileCount: options.maxFileCount };
}
} else {
delete formattedOptions?.maxFileSize;
delete formattedOptions?.maxFileCount;
}

if (Object.prototype.hasOwnProperty.call(options, 'masking')) {
if (!validateBooleanOptions(options.masking)) {
Expand Down
2 changes: 2 additions & 0 deletions src/utils/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ export interface CollectElementOptions {
preserveFileName?: boolean,
allowedFileType?: string[],
blockEmptyFiles?: boolean,
maxFileSize?: number,
maxFileCount?: number,
masking?: boolean,
maskingChar?: string,
}
Expand Down
20 changes: 20 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,22 @@ const SKYFLOW_ERROR_CODE = {
code: 400,
description: logs.errorLogs.NO_FILE_SELECTED,
},
FILE_COUNT_EXCEEDED: {
code: 400,
description: logs.errorLogs.FILE_COUNT_EXCEEDED,
},
FILE_SIZE_EXCEEDED_SINGLE: {
code: 400,
description: logs.errorLogs.FILE_SIZE_EXCEEDED_SINGLE,
},
FILE_SIZE_EXCEEDED_WITH_NAME: {
code: 400,
description: logs.errorLogs.FILE_SIZE_EXCEEDED_WITH_NAME,
},
FILE_COUNT_AND_SIZE_EXCEEDED: {
code: 400,
description: logs.errorLogs.FILE_COUNT_AND_SIZE_EXCEEDED,
},
INVALID_TABLE_IN_UPSERT_OPTION: {
code: 400,
description: logs.errorLogs.INVALID_TABLE_IN_UPSERT_OPTION,
Expand Down Expand Up @@ -552,6 +568,10 @@ const SKYFLOW_ERROR_CODE = {
code: 400,
description: logs.errorLogs.INVALID_BOOLEAN_OPTIONS,
},
INVALID_POSITIVE_NUMBER_OPTIONS: {
code: 400,
description: logs.errorLogs.INVALID_POSITIVE_NUMBER_OPTIONS,
},
INVALID_MASKING_CHARACTER: {
code: 400,
description: logs.errorLogs.INVALID_MASKING_CHARACTER,
Expand Down
5 changes: 4 additions & 1 deletion src/utils/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,10 @@ export const fileValidation = (value, required: Boolean = false, fileElement) =>
}
}
}
if (value.size > 32000000) {
const sizeLimit = (Object.prototype.hasOwnProperty.call(fileElement, 'maxFileSize') && typeof fileElement.maxFileSize === 'number')
? fileElement.maxFileSize
: 32_000_000;
if (value.size > sizeLimit) {
throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_FILE_SIZE, [], true);
}
if (Object.prototype.hasOwnProperty.call(fileElement, 'blockEmptyFiles') && fileElement.blockEmptyFiles) {
Expand Down
5 changes: 5 additions & 0 deletions src/utils/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ const logs = {
INVALID_FILE_TYPE: 'Invalid File Type.',
INVALID_FILE_SIZE: 'Invalid File Size',
NO_FILE_SELECTED: 'No File Selected',
FILE_COUNT_EXCEEDED: 'You can upload up to %s1 files. Remove file(s) to add more',
FILE_SIZE_EXCEEDED_SINGLE: 'File exceeds the %s1 size limit. Choose a smaller file',
FILE_SIZE_EXCEEDED_WITH_NAME: '%s1 exceeds the %s2 size limit. Remove it or choose a smaller file',
FILE_COUNT_AND_SIZE_EXCEEDED: 'You can upload up to %s1 files, each under %s2',
INVALID_UPSERT_OPTION_TYPE:
'Validation error. Invalid \'upsert\' key in insert options. Specify a value of type array instead',
EMPTY_UPSERT_OPTIONS_ARRAY:
Expand Down Expand Up @@ -317,6 +321,7 @@ const logs = {
INVALID_COMPOSABLE_CONTAINER_OPTIONS: 'Mount failed. Invalid options object. Specify a valid options object.',
COMPOSABLE_CONTAINER_NOT_MOUNTED: 'Mount elements first. Make sure all elements are mounted before calling \'collect\' on the container.',
INVALID_BOOLEAN_OPTIONS: 'Validation error. Invalid %s1 found in collect options. Specify a value of type boolean instead.',
INVALID_POSITIVE_NUMBER_OPTIONS: 'Validation error. Invalid %s1 found in collect options. Specify a positive number instead.',
INVALID_MASKING_CHARACTER: 'Validation error. Invalid masking character. Specify a valid masking character. ',
INVALID_INPUT_OPTIONS_FORMAT: 'Mount failed. Format must be a non-empty string. Specify a valid format.',
INVALID_INPUT_OPTIONS_TRANSLATION: 'Mount failed. Translation must be a non-empty object. Specify a valid translation.',
Expand Down
Loading
Loading