@exodus/react-native-fs
Advanced tools
Comparing version 2.14.1 to 2.14.2
204
FS.common.js
@@ -12,4 +12,2 @@ /** | ||
var NativeAppEventEmitter = require('react-native').NativeAppEventEmitter; // iOS | ||
var DeviceEventEmitter = require('react-native').DeviceEventEmitter; // Android | ||
var base64 = require('base-64'); | ||
@@ -22,9 +20,2 @@ var utf8 = require('utf8'); | ||
var jobId = 0; | ||
var getJobId = () => { | ||
jobId += 1; | ||
return jobId; | ||
}; | ||
var normalizeFilePath = (path: string) => (path.startsWith('file://') ? path.slice(7) : path); | ||
@@ -63,76 +54,2 @@ | ||
type Headers = { [name: string]: string }; | ||
type Fields = { [name: string]: string }; | ||
type DownloadFileOptions = { | ||
fromUrl: string; // URL to download file from | ||
toFile: string; // Local filesystem path to save the file to | ||
headers?: Headers; // An object of headers to be passed to the server | ||
background?: boolean; // Continue the download in the background after the app terminates (iOS only) | ||
discretionary?: boolean; // Allow the OS to control the timing and speed of the download to improve perceived performance (iOS only) | ||
cacheable?: boolean; // Whether the download can be stored in the shared NSURLCache (iOS only) | ||
progressDivider?: number; | ||
begin?: (res: DownloadBeginCallbackResult) => void; | ||
progress?: (res: DownloadProgressCallbackResult) => void; | ||
resumable?: () => void; // only supported on iOS yet | ||
connectionTimeout?: number; // only supported on Android yet | ||
readTimeout?: number; // supported on Android and iOS | ||
}; | ||
type DownloadBeginCallbackResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number; // The HTTP status code | ||
contentLength: number; // The total size in bytes of the download resource | ||
headers: Headers; // The HTTP response headers from the server | ||
}; | ||
type DownloadProgressCallbackResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
contentLength: number; // The total size in bytes of the download resource | ||
bytesWritten: number; // The number of bytes written to the file so far | ||
}; | ||
type DownloadResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number; // The HTTP status code | ||
bytesWritten: number; // The number of bytes written to the file | ||
}; | ||
type UploadFileOptions = { | ||
toUrl: string; // URL to upload file to | ||
binaryStreamOnly?: boolean; // Allow for binary data stream for file to be uploaded without extra headers, Default is 'false' | ||
files: UploadFileItem[]; // An array of objects with the file information to be uploaded. | ||
headers?: Headers; // An object of headers to be passed to the server | ||
fields?: Fields; // An object of fields to be passed to the server | ||
method?: string; // Default is 'POST', supports 'POST' and 'PUT' | ||
beginCallback?: (res: UploadBeginCallbackResult) => void; // deprecated | ||
progressCallback?: (res: UploadProgressCallbackResult) => void; // deprecated | ||
begin?: (res: UploadBeginCallbackResult) => void; | ||
progress?: (res: UploadProgressCallbackResult) => void; | ||
}; | ||
type UploadFileItem = { | ||
name: string; // Name of the file, if not defined then filename is used | ||
filename: string; // Name of file | ||
filepath: string; // Path to file | ||
filetype: string; // The mimetype of the file to be uploaded, if not defined it will get mimetype from `filepath` extension | ||
}; | ||
type UploadBeginCallbackResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
}; | ||
type UploadProgressCallbackResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
totalBytesExpectedToSend: number; // The total number of bytes that will be sent to the server | ||
totalBytesSent: number; // The number of bytes sent to the server | ||
}; | ||
type UploadResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
statusCode: number; // The HTTP status code | ||
headers: Headers; // The HTTP response headers from the server | ||
body: string; // The HTTP response body | ||
}; | ||
type FSInfoResult = { | ||
@@ -231,22 +148,2 @@ totalSpace: number; // The total amount of storage space on the device (in bytes). | ||
stopDownload(jobId: number): void { | ||
RNFSManager.stopDownload(jobId); | ||
}, | ||
resumeDownload(jobId: number): void { | ||
RNFSManager.resumeDownload(jobId); | ||
}, | ||
isResumable(jobId: number): Promise<bool> { | ||
return RNFSManager.isResumable(jobId); | ||
}, | ||
stopUpload(jobId: number): void { | ||
RNFSManager.stopUpload(jobId); | ||
}, | ||
completeHandlerIOS(jobId: number): void { | ||
return RNFSManager.completeHandlerIOS(jobId); | ||
}, | ||
readDir(dirpath: string): Promise<ReadDirItem[]> { | ||
@@ -487,103 +384,2 @@ return readDirGeneric(dirpath, RNFSManager.readDir); | ||
downloadFile(options: DownloadFileOptions): { jobId: number, promise: Promise<DownloadResult> } { | ||
if (typeof options !== 'object') throw new Error('downloadFile: Invalid value for argument `options`'); | ||
if (typeof options.fromUrl !== 'string') throw new Error('downloadFile: Invalid value for property `fromUrl`'); | ||
if (typeof options.toFile !== 'string') throw new Error('downloadFile: Invalid value for property `toFile`'); | ||
if (options.headers && typeof options.headers !== 'object') throw new Error('downloadFile: Invalid value for property `headers`'); | ||
if (options.background && typeof options.background !== 'boolean') throw new Error('downloadFile: Invalid value for property `background`'); | ||
if (options.progressDivider && typeof options.progressDivider !== 'number') throw new Error('downloadFile: Invalid value for property `progressDivider`'); | ||
if (options.readTimeout && typeof options.readTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `readTimeout`'); | ||
if (options.connectionTimeout && typeof options.connectionTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `connectionTimeout`'); | ||
var jobId = getJobId(); | ||
var subscriptions = []; | ||
if (options.begin) { | ||
subscriptions.push(NativeAppEventEmitter.addListener('DownloadBegin-' + jobId, options.begin)); | ||
} | ||
if (options.progress) { | ||
subscriptions.push(NativeAppEventEmitter.addListener('DownloadProgress-' + jobId, options.progress)); | ||
} | ||
if (options.resumable) { | ||
subscriptions.push(NativeAppEventEmitter.addListener('DownloadResumable-' + jobId, options.resumable)); | ||
} | ||
var bridgeOptions = { | ||
jobId: jobId, | ||
fromUrl: options.fromUrl, | ||
toFile: normalizeFilePath(options.toFile), | ||
headers: options.headers || {}, | ||
background: !!options.background, | ||
progressDivider: options.progressDivider || 0, | ||
readTimeout: options.readTimeout || 15000, | ||
connectionTimeout: options.connectionTimeout || 5000 | ||
}; | ||
return { | ||
jobId, | ||
promise: RNFSManager.downloadFile(bridgeOptions).then(res => { | ||
subscriptions.forEach(sub => sub.remove()); | ||
return res; | ||
}) | ||
.catch(e => { | ||
return Promise.reject(e); | ||
}) | ||
}; | ||
}, | ||
uploadFiles(options: UploadFileOptions): { jobId: number, promise: Promise<UploadResult> } { | ||
if (!RNFSManager.uploadFiles) { | ||
return { | ||
jobId: -1, | ||
promise: Promise.reject(new Error('`uploadFiles` is unsupported on this platform')) | ||
}; | ||
} | ||
var jobId = getJobId(); | ||
var subscriptions = []; | ||
if (typeof options !== 'object') throw new Error('uploadFiles: Invalid value for argument `options`'); | ||
if (typeof options.toUrl !== 'string') throw new Error('uploadFiles: Invalid value for property `toUrl`'); | ||
if (!Array.isArray(options.files)) throw new Error('uploadFiles: Invalid value for property `files`'); | ||
if (options.headers && typeof options.headers !== 'object') throw new Error('uploadFiles: Invalid value for property `headers`'); | ||
if (options.fields && typeof options.fields !== 'object') throw new Error('uploadFiles: Invalid value for property `fields`'); | ||
if (options.method && typeof options.method !== 'string') throw new Error('uploadFiles: Invalid value for property `method`'); | ||
if (options.begin) { | ||
subscriptions.push(NativeAppEventEmitter.addListener('UploadBegin-' + jobId, options.begin)); | ||
} | ||
if (options.beginCallback && options.beginCallback instanceof Function) { | ||
// Deprecated | ||
subscriptions.push(NativeAppEventEmitter.addListener('UploadBegin-' + jobId, options.beginCallback)); | ||
} | ||
if (options.progress) { | ||
subscriptions.push(NativeAppEventEmitter.addListener('UploadProgress-' + jobId, options.progress)); | ||
} | ||
if (options.progressCallback && options.progressCallback instanceof Function) { | ||
// Deprecated | ||
subscriptions.push(NativeAppEventEmitter.addListener('UploadProgress-' + jobId, options.progressCallback)); | ||
} | ||
var bridgeOptions = { | ||
jobId: jobId, | ||
toUrl: options.toUrl, | ||
files: options.files, | ||
binaryStreamOnly: options.binaryStreamOnly || false, | ||
headers: options.headers || {}, | ||
fields: options.fields || {}, | ||
method: options.method || 'POST' | ||
}; | ||
return { | ||
jobId, | ||
promise: RNFSManager.uploadFiles(bridgeOptions).then(res => { | ||
subscriptions.forEach(sub => sub.remove()); | ||
return res; | ||
}) | ||
}; | ||
}, | ||
touch(filepath: string, mtime?: Date, ctime?: Date): Promise<void> { | ||
@@ -590,0 +386,0 @@ if (ctime && !(ctime instanceof Date)) throw new Error('touch: Invalid value for argument `ctime`'); |
@@ -32,76 +32,2 @@ type MkdirOptions = { | ||
type Headers = { [name: string]: string } | ||
type Fields = { [name: string]: string } | ||
type DownloadFileOptions = { | ||
fromUrl: string // URL to download file from | ||
toFile: string // Local filesystem path to save the file to | ||
headers?: Headers // An object of headers to be passed to the server | ||
background?: boolean // Continue the download in the background after the app terminates (iOS only) | ||
discretionary?: boolean // Allow the OS to control the timing and speed of the download to improve perceived performance (iOS only) | ||
cacheable?: boolean // Whether the download can be stored in the shared NSURLCache (iOS only) | ||
progressDivider?: number | ||
begin?: (res: DownloadBeginCallbackResult) => void | ||
progress?: (res: DownloadProgressCallbackResult) => void | ||
resumable?: () => void // only supported on iOS yet | ||
connectionTimeout?: number // only supported on Android yet | ||
readTimeout?: number // supported on Android and iOS | ||
} | ||
type DownloadBeginCallbackResult = { | ||
jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number // The HTTP status code | ||
contentLength: number // The total size in bytes of the download resource | ||
headers: Headers // The HTTP response headers from the server | ||
} | ||
type DownloadProgressCallbackResult = { | ||
jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
contentLength: number // The total size in bytes of the download resource | ||
bytesWritten: number // The number of bytes written to the file so far | ||
} | ||
type DownloadResult = { | ||
jobId: number // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number // The HTTP status code | ||
bytesWritten: number // The number of bytes written to the file | ||
} | ||
type UploadFileOptions = { | ||
toUrl: string // URL to upload file to | ||
binaryStreamOnly?: boolean // Allow for binary data stream for file to be uploaded without extra headers, Default is 'false' | ||
files: UploadFileItem[] // An array of objects with the file information to be uploaded. | ||
headers?: Headers // An object of headers to be passed to the server | ||
fields?: Fields // An object of fields to be passed to the server | ||
method?: string // Default is 'POST', supports 'POST' and 'PUT' | ||
beginCallback?: (res: UploadBeginCallbackResult) => void // deprecated | ||
progressCallback?: (res: UploadProgressCallbackResult) => void // deprecated | ||
begin?: (res: UploadBeginCallbackResult) => void | ||
progress?: (res: UploadProgressCallbackResult) => void | ||
} | ||
type UploadFileItem = { | ||
name: string // Name of the file, if not defined then filename is used | ||
filename: string // Name of file | ||
filepath: string // Path to file | ||
filetype: string // The mimetype of the file to be uploaded, if not defined it will get mimetype from `filepath` extension | ||
} | ||
type UploadBeginCallbackResult = { | ||
jobId: number // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
} | ||
type UploadProgressCallbackResult = { | ||
jobId: number // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
totalBytesExpectedToSend: number // The total number of bytes that will be sent to the server | ||
totalBytesSent: number // The number of bytes sent to the server | ||
} | ||
type UploadResult = { | ||
jobId: number // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
statusCode: number // The HTTP status code | ||
headers: Headers // The HTTP response headers from the server | ||
body: string // The HTTP response body | ||
} | ||
type FSInfoResult = { | ||
@@ -129,13 +55,2 @@ totalSpace: number // The total amount of storage space on the device (in bytes). | ||
export function exists(filepath: string): Promise<boolean> | ||
export function stopDownload(jobId: number): void | ||
export function resumeDownload(jobId: number): void | ||
export function isResumable(jobId: number): Promise<boolean> | ||
export function stopUpload(jobId: number): void | ||
export function completeHandlerIOS(jobId: number): void | ||
export function readDir(dirpath: string): Promise<ReadDirItem[]> | ||
@@ -270,10 +185,2 @@ | ||
export function downloadFile( | ||
options: DownloadFileOptions | ||
): { jobId: number; promise: Promise<DownloadResult> } | ||
export function uploadFiles( | ||
options: UploadFileOptions | ||
): { jobId: number; promise: Promise<UploadResult> } | ||
export function touch( | ||
@@ -280,0 +187,0 @@ filepath: string, |
{ | ||
"name": "@exodus/react-native-fs", | ||
"version": "2.14.1", | ||
"version": "2.14.2", | ||
"description": "Native filesystem access for react-native", | ||
@@ -5,0 +5,0 @@ "main": "FS.common.js", |
237
README.md
@@ -233,63 +233,2 @@ # react-native-fs | ||
### File upload (Android and IOS only) | ||
```javascript | ||
// require the module | ||
var RNFS = require('react-native-fs'); | ||
var uploadUrl = 'http://requestb.in/XXXXXXX'; // For testing purposes, go to http://requestb.in/ and create your own link | ||
// create an array of objects of the files you want to upload | ||
var files = [ | ||
{ | ||
name: 'test1', | ||
filename: 'test1.w4a', | ||
filepath: RNFS.DocumentDirectoryPath + '/test1.w4a', | ||
filetype: 'audio/x-m4a' | ||
}, { | ||
name: 'test2', | ||
filename: 'test2.w4a', | ||
filepath: RNFS.DocumentDirectoryPath + '/test2.w4a', | ||
filetype: 'audio/x-m4a' | ||
} | ||
]; | ||
var uploadBegin = (response) => { | ||
var jobId = response.jobId; | ||
console.log('UPLOAD HAS BEGUN! JobId: ' + jobId); | ||
}; | ||
var uploadProgress = (response) => { | ||
var percentage = Math.floor((response.totalBytesSent/response.totalBytesExpectedToSend) * 100); | ||
console.log('UPLOAD IS ' + percentage + '% DONE!'); | ||
}; | ||
// upload files | ||
RNFS.uploadFiles({ | ||
toUrl: uploadUrl, | ||
files: files, | ||
method: 'POST', | ||
headers: { | ||
'Accept': 'application/json', | ||
}, | ||
fields: { | ||
'hello': 'world', | ||
}, | ||
begin: uploadBegin, | ||
progress: uploadProgress | ||
}).promise.then((response) => { | ||
if (response.statusCode == 200) { | ||
console.log('FILES UPLOADED!'); // response.statusCode, response.headers, response.body | ||
} else { | ||
console.log('SERVER ERROR'); | ||
} | ||
}) | ||
.catch((err) => { | ||
if(err.description === "cancelled") { | ||
// cancelled by user | ||
} | ||
console.log(err); | ||
}); | ||
``` | ||
## API | ||
@@ -499,149 +438,2 @@ | ||
### `downloadFile(options: DownloadFileOptions): { jobId: number, promise: Promise<DownloadResult> }` | ||
``` | ||
type DownloadFileOptions = { | ||
fromUrl: string; // URL to download file from | ||
toFile: string; // Local filesystem path to save the file to | ||
headers?: Headers; // An object of headers to be passed to the server | ||
background?: boolean; // Continue the download in the background after the app terminates (iOS only) | ||
discretionary?: boolean; // Allow the OS to control the timing and speed of the download to improve perceived performance (iOS only) | ||
cacheable?: boolean; // Whether the download can be stored in the shared NSURLCache (iOS only, defaults to true) | ||
progressDivider?: number; | ||
begin?: (res: DownloadBeginCallbackResult) => void; | ||
progress?: (res: DownloadProgressCallbackResult) => void; | ||
resumable?: () => void; // only supported on iOS yet | ||
connectionTimeout?: number // only supported on Android yet | ||
readTimeout?: number // supported on Android and iOS | ||
}; | ||
``` | ||
``` | ||
type DownloadResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number; // The HTTP status code | ||
bytesWritten: number; // The number of bytes written to the file | ||
}; | ||
``` | ||
Download file from `options.fromUrl` to `options.toFile`. Will overwrite any previously existing file. | ||
If `options.begin` is provided, it will be invoked once upon download starting when headers have been received and passed a single argument with the following properties: | ||
``` | ||
type DownloadBeginCallbackResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
statusCode: number; // The HTTP status code | ||
contentLength: number; // The total size in bytes of the download resource | ||
headers: Headers; // The HTTP response headers from the server | ||
}; | ||
``` | ||
If `options.progress` is provided, it will be invoked continuously and passed a single argument with the following properties: | ||
``` | ||
type DownloadProgressCallbackResult = { | ||
jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. | ||
contentLength: number; // The total size in bytes of the download resource | ||
bytesWritten: number; // The number of bytes written to the file so far | ||
}; | ||
``` | ||
If `options.progressDivider` is provided, it will return progress events that divided by `progressDivider`. | ||
For example, if `progressDivider` = 10, you will receive only ten callbacks for this values of progress: 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 | ||
Use it for performance issues. | ||
If `progressDivider` = 0, you will receive all `progressCallback` calls, default value is 0. | ||
(IOS only): `options.background` (`Boolean`) - Whether to continue downloads when the app is not focused (default: `false`) | ||
This option is currently only available for iOS, see the [Background Downloads Tutorial (iOS)](#background-downloads-tutorial-ios) section. | ||
(IOS only): If `options.resumable` is provided, it will be invoked when the download has stopped and and can be resumed using `resumeDownload()`. | ||
### `stopDownload(jobId: number): void` | ||
Abort the current download job with this ID. The partial file will remain on the filesystem. | ||
### (iOS only) `resumeDownload(jobId: number): void` | ||
Resume the current download job with this ID. | ||
### (iOS only) `isResumable(jobId: number): Promise<bool>` | ||
Check if the the download job with this ID is resumable with `resumeDownload()`. | ||
Example: | ||
``` | ||
if (await RNFS.isResumable(jobId) { | ||
RNFS.resumeDownload(jobId) | ||
} | ||
``` | ||
### (iOS only) `completeHandlerIOS(jobId: number): void` | ||
For use when using background downloads, tell iOS you are done handling a completed download. | ||
Read more about background downloads in the [Background Downloads Tutorial (iOS)](#background-downloads-tutorial-ios) section. | ||
### `uploadFiles(options: UploadFileOptions): { jobId: number, promise: Promise<UploadResult> }` | ||
`options` (`Object`) - An object containing named parameters | ||
``` | ||
type UploadFileOptions = { | ||
toUrl: string; // URL to upload file to | ||
binaryStreamOnly?: boolean// Allow for binary data stream for file to be uploaded without extra headers, Default is 'false' | ||
files: UploadFileItem[]; // An array of objects with the file information to be uploaded. | ||
headers?: Headers; // An object of headers to be passed to the server | ||
fields?: Fields; // An object of fields to be passed to the server | ||
method?: string; // Default is 'POST', supports 'POST' and 'PUT' | ||
begin?: (res: UploadBeginCallbackResult) => void; | ||
progress?: (res: UploadProgressCallbackResult) => void; | ||
}; | ||
``` | ||
``` | ||
type UploadResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
statusCode: number; // The HTTP status code | ||
headers: Headers; // The HTTP response headers from the server | ||
body: string; // The HTTP response body | ||
}; | ||
``` | ||
Each file should have the following structure: | ||
``` | ||
type UploadFileItem = { | ||
name: string; // Name of the file, if not defined then filename is used | ||
filename: string; // Name of file | ||
filepath: string; // Path to file | ||
filetype: string; // The mimetype of the file to be uploaded, if not defined it will get mimetype from `filepath` extension | ||
}; | ||
``` | ||
If `options.begin` is provided, it will be invoked once upon upload has begun: | ||
``` | ||
type UploadBeginCallbackResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
}; | ||
``` | ||
If `options.progress` is provided, it will be invoked continuously and passed a single object with the following properties: | ||
``` | ||
type UploadProgressCallbackResult = { | ||
jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. | ||
totalBytesExpectedToSend: number; // The total number of bytes that will be sent to the server | ||
totalBytesSent: number; // The number of bytes sent to the server | ||
}; | ||
``` | ||
Percentage can be computed easily by dividing `totalBytesSent` by `totalBytesExpectedToSend`. | ||
### (iOS only) `stopUpload(jobId: number): Promise<void>` | ||
Abort the current upload job with this ID. | ||
### `getFSInfo(): Promise<FSInfoResult>` | ||
@@ -677,31 +469,2 @@ | ||
## Background Downloads Tutorial (iOS) | ||
Background downloads in iOS require a bit of a setup. | ||
First, in your `AppDelegate.m` file add the following: | ||
``` | ||
#import <RNFSManager.h> | ||
... | ||
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler | ||
{ | ||
[RNFSManager setCompletionHandlerForIdentifier:identifier completionHandler:completionHandler]; | ||
} | ||
``` | ||
The `handleEventsForBackgroundURLSession` method is called when a background download is done and your app is not in the foreground. | ||
We need to pass the `completionHandler` to RNFS along with its `identifier`. | ||
The JavaScript will continue to work as usual when the download is done but now you must call `RNFS.completeHandlerIOS(jobId)` when you're done handling the download (show a notification etc.) | ||
**BE AWARE!** iOS will give about 30 sec. to run your code after `handleEventsForBackgroundURLSession` is called and until `completionHandler` | ||
is triggered so don't do anything that might take a long time (like unzipping), you will be able to do it after the user re-launces the app, | ||
otherwide iOS will terminate your app. | ||
## Test / Demo app | ||
@@ -708,0 +471,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
111267
17
521
472