diff --git a/README.md b/README.md index 2a2bd92..74412a7 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Twitter Follow](https://img.shields.io/twitter/follow/imagekitio?label=Follow&style=social)](https://twitter.com/ImagekitIo) -ImageKit Javascript SDK allows you to use real-time [image resizing](https://docs.imagekit.io/features/image-transformations), [optimization](https://docs.imagekit.io/features/image-optimization), and [file uploading](https://docs.imagekit.io/api-reference/upload-file-api/client-side-file-upload) in the client-side. +Javascript SDK for [ImageKit](https://imagekit.io/) provides URL generation for image & video resizing and provides an interface for file upload. This SDK is lightweight and you can also use this as an ES module. -This SDK is lightweight and has no dependency. You can also use this as an ES module. +ImageKit is complete media storage, optimization, and transformation solution that comes with an [image and video CDN](https://imagekit.io/features/imagekit-infrastructure). It can be integrated with your existing infrastructure - storage like AWS S3, web servers, your CDN, and custom domain names, allowing you to deliver optimized images in minutes with minimal code changes. ## Installation @@ -85,6 +85,7 @@ Create a file `.env` using `sample.env` in the directory `samples/sample-app` an Now start the sample application by running: ``` +// Run it from project root yarn startSampleApp ``` @@ -95,7 +96,7 @@ You can use this SDK for URL generation and client-side file uploads. **1. Using image path and image hostname or endpoint** -This method allows you to create a URL using the `path` where the image exists and the URL endpoint (`urlEndpoint`) you want to use to access the image. You can refer to the documentation [here](https://docs.imagekit.io/integration/url-endpoints) to read more about URL endpoints in ImageKit and the section about [image origins](https://docs.imagekit.io/integration/configure-origin) to understand about paths with different kinds of origins. +This method allows you to create an URL to access a file using the relative file path and the ImageKit URL endpoint (`urlEndpoint`). The file can be an image, video, or any other static file supported by ImageKit. ``` var imageURL = imagekit.url({ @@ -116,7 +117,7 @@ https://ik.imagekit.io/your_imagekit_id/endpoint/tr:h-300,w-400/default-image.jp **2. Using full image URL** -This method allows you to add transformation parameters to an existing, complete URL that is already mapped to ImageKit using the `src` parameter. This method should be used if you have the complete URL mapped to ImageKit stored in your database. +This method allows you to add transformation parameters to an absolute URL. For example, if you have configured a custom CNAME and have absolute asset URLs in your database or CMS, you will often need this. ``` @@ -141,11 +142,11 @@ The `.url()` method accepts the following parameters | Option | Description | | :----------------| :----------------------------- | | urlEndpoint | Optional. The base URL to be appended before the path of the image. If not specified, the URL Endpoint specified at the time of SDK initialization is used. For example, https://ik.imagekit.io/your_imagekit_id/endpoint/ | -| path | Conditional. This is the path at which the image exists. For example, `/path/to/image.jpg`. Either the `path` or `src` parameter need to be specified for URL generation. | -| src | Conditional. This is the complete URL of an image already mapped to ImageKit. For example, `https://ik.imagekit.io/your_imagekit_id/endpoint/path/to/image.jpg`. Either the `path` or `src` parameter need to be specified for URL generation. | -| transformation | Optional. An array of objects specifying the transformation to be applied in the URL. The transformation name and the value should be specified as a key-value pair in the object. Different steps of a [chained transformation](https://docs.imagekit.io/features/image-transformations/chained-transformations) can be specified as different objects of the array. The complete list of supported transformations in the SDK and some examples of using them are given later. If you use a transformation name that is not specified in the SDK, it gets applied as it is in the URL. | -| transformationPostion | Optional. The default value is `path` that places the transformation string as a path parameter in the URL. It can also be specified as `query` which adds the transformation string as the query parameter `tr` in the URL. If you use `src` parameter to create the URL, then the transformation string is always added as a query parameter. | -| queryParameters | Optional. These are the other query parameters that you want to add to the final URL. These can be any query parameters and not necessarily related to ImageKit. Especially useful if you want to add some versioning parameter to your URLs. | +| path | Conditional. This is the path at which the image exists. For example, `/path/to/image.jpg`. Either the `path` or `src` parameter needs to be specified for URL generation. | +| src | Conditional. This is the complete URL of an image already mapped to ImageKit. For example, `https://ik.imagekit.io/your_imagekit_id/endpoint/path/to/image.jpg`. Either the `path` or `src` parameter needs to be specified for URL generation. | +| transformation | Optional. An array of objects specifying the transformation to be applied in the URL. The transformation name and the value should be specified as a key-value pair in the object. Different steps of a [chained transformation](https://docs.imagekit.io/features/image-transformations/chained-transformations) can be specified as different objects of the array. The complete list of supported transformations in the SDK and some examples of using them are given later. If you use a transformation name that is not specified in the SDK, it gets applied as it is in the URL. | +| transformationPostion | Optional. The default value is `path`, which places the transformation string as a path parameter in the URL. It can also be specified as `query`, which adds the transformation string as the query parameter `tr` in the URL. If you use the `src` parameter to create the URL, then the transformation string is always added as a query parameter. | +| queryParameters | Optional. These are the other query parameters that you want to add to the final URL. These can be any query parameters and are not necessarily related to ImageKit. Especially useful if you want to add some versioning parameters to your URLs. | #### Examples of generating URLs @@ -189,7 +190,7 @@ https://ik.imagekit.io/your_imagekit_id/endpoint/default-image.jpg?tr=f-jpg%2Cpr #### List of supported transformations -The complete list of transformations supported and their usage in ImageKit can be found [here](https://docs.imagekit.io/features/image-transformations). The SDK gives a name to each transformation parameter, making the code simpler and readable. If a transformation is supported in ImageKit, but a name for it cannot be found in the table below, then use the transformation code from ImageKit docs as the name when using in the `url` function. +See the complete list of transformations supported in ImageKit [here](https://docs.imagekit.io/features/image-transformations). The SDK gives a name to each transformation parameter e.g. `height` for `h` and `width` for `w` parameter. It makes your code more readable. If the property does not match any of the following supported options, it is added as it is. If you want to generate transformations in your application and add them to the URL as it is, use the `raw` parameter. @@ -256,9 +257,9 @@ If you want to generate transformations in your application and add them to the ### File Upload -The SDK provides a simple interface using the `.upload()` method to upload files to the ImageKit Media Library. It accepts all the parameters supported by the [ImageKit Upload API](https://docs.imagekit.io/api-reference/upload-file-api/client-side-file-upload). +The SDK provides a simple interface using the `.upload()` method to upload files to the ImageKit Media Library. -The `upload()` method requires `file` and the `fileName` parameter. +The `upload()` method requires mandatory `file` and the `fileName` parameter. In addition, it accepts all the parameters supported by the [ImageKit Upload API](https://docs.imagekit.io/api-reference/upload-file-api/client-side-file-upload). Also, make sure that you have specified `authenticationEndpoint` during SDK initialization. The SDK makes an HTTP GET request to this endpoint and expects a JSON response with three fields, i.e. `signature`, `token`, and `expire`. @@ -266,8 +267,9 @@ Also, make sure that you have specified `authenticationEndpoint` during SDK init You can pass other parameters supported by the ImageKit upload API using the same parameter name as specified in the upload API documentation. For example, to specify tags for a file at the time of upload, use the `tags` parameter as specified in the [documentation here](https://docs.imagekit.io/api-reference/upload-file-api/client-side-file-upload). -Sample usage -``` + +#### Sample usage +```html
@@ -287,6 +289,8 @@ Sample usage // Upload function internally uses the ImageKit.io javascript SDK function upload(data) { var file = document.getElementById("file1"); + + // Using Callback Function imagekit.upload({ file: file.files[0], fileName: "abc1.jpg", @@ -299,11 +303,25 @@ Sample usage } ] }, function(err, result) { - console.log(arguments); - console.log(imagekit.url({ - src: result.url, - transformation: [{ height: 300, width: 400}] - })); + console.log(result); + }) + + // Using Promises + imagekit.upload({ + file: file.files[0], + fileName: "abc1.jpg", + tags: ["tag1"], + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] + }).then(result => { + console.log(result); + }).then(error => { + console.log(error); }) } @@ -312,3 +330,81 @@ Sample usage If the upload succeeds, `err` will be `null`, and the `result` will be the same as what is received from ImageKit's servers. If the upload fails, `err` will be the same as what is received from ImageKit's servers, and the `result` will be null. +## Tracking upload progress using custom XMLHttpRequest +You can use a custom XMLHttpRequest object as the following to bind `progress` or any other events for a customized implementation. + +```js +var fileSize = file.files[0].size; +var customXHR = new XMLHttpRequest(); +customXHR.upload.addEventListener('progress', function (e) { + if (e.loaded <= fileSize) { + var percent = Math.round(e.loaded / fileSize * 100); + console.log(`Uploaded ${percent}%`); + } + + if(e.loaded == e.total){ + console.log("Upload done"); + } +}); + +imagekit.upload({ + xhr: customXHR, + file: file.files[0], + fileName: "abc1.jpg", + tags: ["tag1"], + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] +}).then(result => { + console.log(result); +}).then(error => { + console.log(error); +}) +``` + +## Access request-id, other response headers, and HTTP status code +You can access `$ResponseMetadata` on success or error object to access the HTTP status code and response headers. + +```js +// Success +var response = await imagekit.upload({ + file: file.files[0], + fileName: "abc1.jpg", + tags: ["tag1"], + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] +}); +console.log(response.$ResponseMetadata.statusCode); // 200 + +// { 'content-length': "300", 'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'} +console.log(response.$ResponseMetadata.headers); + +// Error +try { + await imagekit.upload({ + file: file.files[0], + fileName: "abc1.jpg", + tags: ["tag1"], + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] + }); +} catch (ex) { + console.log(response.$ResponseMetadata.statusCode); // 400 + + // {'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'} + console.log(response.$ResponseMetadata.headers); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a336ff0..8f507aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4954,10 +4954,9 @@ } }, "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" }, "regenerator-transform": { "version": "0.14.5", diff --git a/package.json b/package.json index 8168d32..7c71b06 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "imagekit-javascript", - "version": "1.4.3", + "version": "1.5.0", "description": "Javascript SDK for using ImageKit.io in the browser", "main": "dist/imagekit.cjs.js", "module": "dist/imagekit.esm.js", @@ -68,5 +68,8 @@ "bugs": { "url": "https://github.com/imagekit-developer/imagekit-javascript/issues" }, - "homepage": "https://github.com/imagekit-developer/imagekit-javascript#readme" + "homepage": "https://github.com/imagekit-developer/imagekit-javascript#readme", + "dependencies": { + "regenerator-runtime": "^0.13.9" + } } diff --git a/samples/sample-app/views/index.pug b/samples/sample-app/views/index.pug index 2e56e84..b27d3e9 100644 --- a/samples/sample-app/views/index.pug +++ b/samples/sample-app/views/index.pug @@ -17,7 +17,7 @@ html h4 Sample transformation with height: 300, width: 400: p img(src="") - + script(type='text/javascript' src="https://unpkg.com/imagekit-javascript/dist/imagekit.min.js") script. try { @@ -26,25 +26,42 @@ html urlEndpoint: "!{urlEndpoint}", authenticationEndpoint: "!{authenticationEndpoint}" }); - + window.imagekit = imagekit; + function upload(e) { e.preventDefault(); var file = document.getElementById("file1"); + var fileSize = file.files[0].size; var statusEl = document.getElementById("status"); - statusEl.innerHTML = "Uploading..." + statusEl.innerHTML = "Uploading..."; + + // Use this if you want to track upload progress + var customXHR = new XMLHttpRequest(); + customXHR.upload.addEventListener('progress', function (e) { + if (e.loaded <= fileSize) { + var percent = Math.round(e.loaded / fileSize * 100); + console.log(`Uploaded ${percent}%`); + } + + if(e.loaded == e.total){ + console.log("Upload done"); + } + }); + imagekit.upload({ + xhr: customXHR, // Use this if you want to track upload progress file : file.files[0], fileName : file.files[0].name || "test_image.jpg", tags : ["test_tag_1"], //- extensions: [ - //- { - //- name: "aws-auto-tagging", - //- minConfidence: 80, - //- maxTags: 10 - //- } - //- ], + //- { + //- name: "aws-auto-tagging", + //- minConfidence: 80, + //- maxTags: 10 + //- } + //- ], }, function(err, result) { if (err) { statusEl.innerHTML = "Error uploading image. "+ err.message; @@ -60,15 +77,13 @@ html var orig_img = document.querySelector("#orig_image > p > img"); var trans_img = document.querySelector("#trans_image > p > img"); - + orig_img.setAttribute("src", srcUrl); trans_img.setAttribute("src", transformedURL); var el = document.getElementById('images') el.setAttribute("style", ""); } - - }); } } catch(ex) { diff --git a/src/constants/errorMessages.ts b/src/constants/errorMessages.ts index 96983dc..0787193 100644 --- a/src/constants/errorMessages.ts +++ b/src/constants/errorMessages.ts @@ -6,9 +6,10 @@ export default { MISSING_UPLOAD_FILE_PARAMETER: { message: "Missing file parameter for upload", help: "" }, MISSING_UPLOAD_FILENAME_PARAMETER: { message: "Missing fileName parameter for upload", help: "" }, MISSING_AUTHENTICATION_ENDPOINT: { message: "Missing authentication endpoint for upload", help: "" }, - MISSING_PUBLIC_KEY : { message: "Missing public key for upload", help: "" }, + MISSING_PUBLIC_KEY: { message: "Missing public key for upload", help: "" }, AUTH_ENDPOINT_TIMEOUT: { message: "The authenticationEndpoint you provided timed out in 60 seconds", help: "" }, AUTH_ENDPOINT_NETWORK_ERROR: { message: "Request to authenticationEndpoint failed due to network error", help: "" }, + AUTH_INVALID_RESPONSE: { message: "Invalid response from authenticationEndpoint. The SDK expects a JSON response with three fields i.e. signature, token and expire.", help: "" }, UPLOAD_ENDPOINT_NETWORK_ERROR: { message: "Request to ImageKit upload endpoint failed due to network error", help: "", diff --git a/src/constants/supportedTransforms.ts b/src/constants/supportedTransforms.ts index df77633..a459ab2 100644 --- a/src/constants/supportedTransforms.ts +++ b/src/constants/supportedTransforms.ts @@ -281,6 +281,11 @@ const supportedTransforms: { [key: string]: string } = { * @link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#original-image-orig */ original: "orig", + + /** + * @link https://docs.imagekit.io/features/image-transformations/conditional-transformations + */ + raw: "raw", } diff --git a/src/index.ts b/src/index.ts index 5930dc5..c6d0bc3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,9 @@ import { version } from "../package.json"; import errorMessages from "./constants/errorMessages"; import { ImageKitOptions, UploadOptions, UploadResponse, UrlOptions } from "./interfaces"; +import IKResponse from "./interfaces/IKResponse"; import { upload } from "./upload/index"; +import respond from "./utils/respond"; import { url } from "./url/index"; import transformationUtils from "./utils/transformation"; @@ -9,9 +11,29 @@ function mandatoryParametersAvailable(options: ImageKitOptions) { return options.urlEndpoint; } -function privateKeyPassed(options: ImageKitOptions) { - return typeof (options as any).privateKey != "undefined"; -} +const promisify = function (thisContext: ImageKit, fn: Function) { + return function (...args: any[]): Promise | void { + if (args.length === fn.length && typeof args[args.length - 1] !== "undefined") { + if (typeof args[args.length - 1] !== "function") { + throw new Error("Callback must be a function."); + } + fn.call(thisContext, ...args); + } else { + return new Promise((resolve, reject) => { + const callback = function (err: Error, ...results: any[]) { + if (err) { + return reject(err); + } else { + resolve(results.length > 1 ? results : results[0]); + } + }; + args.pop() + args.push(callback); + fn.call(thisContext, ...args); + }); + } + }; +}; class ImageKit { options: ImageKitOptions = { @@ -26,9 +48,6 @@ class ImageKit { if (!mandatoryParametersAvailable(this.options)) { throw errorMessages.MANDATORY_INITIALIZATION_MISSING; } - if (privateKeyPassed(this.options)) { - throw errorMessages.PRIVATE_KEY_CLIENT_SIDE; - } if (!transformationUtils.validParameters(this.options)) { throw errorMessages.INVALID_TRANSFORMATION_POSITION; @@ -58,16 +77,26 @@ class ImageKit { * * @param uploadOptions */ - upload( - uploadOptions: UploadOptions, - callback?: (err: Error | null, response: UploadResponse | null) => void, - options?: Partial, - ): void { + upload(uploadOptions: UploadOptions, options?: Partial): Promise> + upload(uploadOptions: UploadOptions, callback: (err: Error | null, response: IKResponse | null) => void, options?: Partial): void; + upload(uploadOptions: UploadOptions, callbackOrOptions?: ((err: Error | null, response: IKResponse | null) => void) | Partial, options?: Partial): void | Promise> { + let callback; + if (typeof callbackOrOptions === 'function') { + callback = callbackOrOptions; + } else { + options = callbackOrOptions || {}; + } + if (!uploadOptions || typeof uploadOptions !== "object") { + return respond(true, errorMessages.INVALID_UPLOAD_OPTIONS, callback); + } var mergedOptions = { ...this.options, ...options, }; - return upload(uploadOptions, mergedOptions, callback); + const { xhr: userProvidedXHR } = uploadOptions || {}; + delete uploadOptions.xhr; + const xhr = userProvidedXHR || new XMLHttpRequest(); + return promisify>(this, upload)(xhr, uploadOptions, mergedOptions, callback); } } diff --git a/src/interfaces/IKResponse.ts b/src/interfaces/IKResponse.ts new file mode 100644 index 0000000..a53ca4f --- /dev/null +++ b/src/interfaces/IKResponse.ts @@ -0,0 +1,10 @@ +interface ResponseMetadata { + statusCode: number; + headers: Record; +} + +type IKResponse = T extends Error + ? T & { $ResponseMetadata?: ResponseMetadata } + : T & { $ResponseMetadata: ResponseMetadata }; + +export default IKResponse; diff --git a/src/interfaces/UploadOptions.ts b/src/interfaces/UploadOptions.ts index 5a9f7af..24f18ec 100644 --- a/src/interfaces/UploadOptions.ts +++ b/src/interfaces/UploadOptions.ts @@ -36,7 +36,7 @@ export interface UploadOptions { * - % is not allowed. * - If this field is not specified and the file is overwritten then the tags will be removed. */ - tags?: string; + tags?: string | string[]; /** * The folder path (e.g. /images/folder/) in which the image has to be uploaded. If the folder(s) didn't exist before, a new folder(s) is created. * The folder name can contain: @@ -66,7 +66,7 @@ export interface UploadOptions { * * For example, set the value of this field to tags,customCoordinates,isPrivateFile,metadata to get value of tags, customCoordinates, isPrivateFile , and metadata in the response. */ - responseFields?: string; + responseFields?: string | string[]; /* * Object with array of extensions to be processed on the image. */ @@ -75,4 +75,32 @@ export interface UploadOptions { * Final status of pending extensions will be sent to this URL. */ webhookUrl?: string + /* + * Default is true. If overwriteFile is set to false and useUniqueFileName is also false, and a file already exists at the exact location, upload API will return an error immediately. + */ + overwriteFile?: boolean + /* + * Default is true. If set to true and a file already exists at the exact location, its AITags will be removed. Set overwriteAITags to false to preserve AITags. + */ + overwriteAITags?: boolean + /* + * Default is true. If the request does not have tags , overwriteTags is set to true and a file already exists at the exact location, existing tags will be removed. + * In case the request body has tags, setting overwriteTags to false has no effect and request's tags are set on the asset. + */ + overwriteTags?: boolean + /* + * Default is true. If the request does not have customMetadata , overwriteCustomMetadata is set to true and a file already exists at the exact location, exiting customMetadata will be removed. + * In case the request body has customMetadata, setting overwriteCustomMetadata to false has no effect and request's customMetadata is set on the asset. + */ + overwriteCustomMetadata?: boolean + /* + * Stringified JSON key-value data to be associated with the asset. Checkout overwriteCustomMetadata parameter to understand default behaviour. + * Before setting any custom metadata on an asset you have to create the field using custom metadata fields API. + */ + customMetadata?: string | Record> + + /** + * Optional XMLHttpRequest object that you can send for upload API request. You can listen to `progress` and other events on this object for any custom logic. + */ + xhr?: XMLHttpRequest } diff --git a/src/upload/index.ts b/src/upload/index.ts index 6c59d15..a6bbeb3 100644 --- a/src/upload/index.ts +++ b/src/upload/index.ts @@ -4,15 +4,11 @@ import { request } from "../utils/request"; import { ImageKitOptions, UploadOptions, UploadResponse } from "../interfaces"; export const upload = ( + xhr: XMLHttpRequest, uploadOptions: UploadOptions, options: ImageKitOptions, callback?: (err: Error | null, response: UploadResponse | null) => void, ) => { - if (!uploadOptions) { - respond(true, errorMessages.INVALID_UPLOAD_OPTIONS, callback); - return; - } - if (!uploadOptions.file) { respond(true, errorMessages.MISSING_UPLOAD_FILE_PARAMETER, callback); return; @@ -33,29 +29,29 @@ export const upload = ( return; } - if(uploadOptions.tags && Array.isArray(uploadOptions.tags)) - { - uploadOptions.tags = String(uploadOptions.tags); - } - var formData = new FormData(); - let i: keyof typeof uploadOptions; - for (i in uploadOptions) { - const param = uploadOptions[i]; - if (typeof param !== "undefined") { - if (typeof param === "string" || typeof param === "boolean") { - formData.append(i, String(param)); - } - else if(Array.isArray(param)) { - formData.append(i, JSON.stringify(param)); + let key: keyof typeof uploadOptions; + for (key in uploadOptions) { + if (key) { + if (key === "file" && typeof uploadOptions.file != "string") { + formData.append('file', uploadOptions.file, String(uploadOptions.fileName)); + } else if (key === "tags" && Array.isArray(uploadOptions.tags)) { + formData.append('tags', uploadOptions.tags.join(",")); + } else if (key === "responseFields" && Array.isArray(uploadOptions.responseFields)) { + formData.append('responseFields', uploadOptions.responseFields.join(",")); + } else if (key === "extensions" && Array.isArray(uploadOptions.extensions)) { + formData.append('extensions', JSON.stringify(uploadOptions.extensions)); + } else if (key === "customMetadata" && typeof uploadOptions.customMetadata === "object" && + !Array.isArray(uploadOptions.customMetadata) && uploadOptions.customMetadata !== null) { + formData.append('customMetadata', JSON.stringify(uploadOptions.customMetadata)); } else { - formData.append(i, param); + formData.append(key, String(uploadOptions[key])); } } } formData.append("publicKey", options.publicKey); - request(formData, { ...options, authenticationEndpoint: options.authenticationEndpoint }, callback); + request(xhr, formData, { ...options, authenticationEndpoint: options.authenticationEndpoint }, callback); }; diff --git a/src/utils/request.ts b/src/utils/request.ts index 08773c6..7ef5378 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -1,6 +1,8 @@ import respond from "../utils/respond"; import errorMessages from "../constants/errorMessages" import { ImageKitOptions, UploadResponse } from "../interfaces"; +import IKResponse from "../interfaces/IKResponse"; +import 'regenerator-runtime/runtime' interface SignatureResponse { signature: string @@ -8,82 +10,123 @@ interface SignatureResponse { token: string } -export const request = (formData: FormData, options: ImageKitOptions & { authenticationEndpoint: string }, callback?: (err: Error | null, response: UploadResponse | null) => void) => { - generateSignatureToken(options, (err, signaturObj) => { - if (err) { - return respond(true, err, callback) - } else { - formData.append("signature", signaturObj?.signature || ""); - formData.append("expire", String(signaturObj?.expire || 0)); - formData.append("token", signaturObj?.token || ""); - - uploadFile(formData, (err, responseSucessText) => { - if (err) { - return respond(true, err, callback) - } - return respond(false, responseSucessText!, callback) +function getResponseHeaderMap(xhr: XMLHttpRequest) { + const headers: Record = {}; + const responseHeaders = xhr.getAllResponseHeaders(); + if (Object.keys(responseHeaders).length) { + responseHeaders + .trim() + .split(/[\r\n]+/) + .map(value => value.split(/: /)) + .forEach(keyValue => { + headers[keyValue[0].trim()] = keyValue[1].trim(); }); - } - }); + } + return headers; } -export const generateSignatureToken = (options: ImageKitOptions & { authenticationEndpoint: string }, callback: (err: Error | null, response: SignatureResponse | null) => void) => { - var xhr = new XMLHttpRequest(); - xhr.timeout = 60000; - xhr.open('GET', options.authenticationEndpoint); - xhr.ontimeout = function (e) { - respond(true, errorMessages.AUTH_ENDPOINT_TIMEOUT, callback); - }; - xhr.onerror = function() { - respond(true, errorMessages.AUTH_ENDPOINT_NETWORK_ERROR, callback); +const addResponseHeadersAndBody = (body: any, xhr: XMLHttpRequest): IKResponse => { + let response = { ...body }; + const responseMetadata = { + statusCode: xhr.status, + headers: getResponseHeaderMap(xhr) } - xhr.onload = function () { - if (xhr.status === 200) { - try { - var body = JSON.parse(xhr.responseText); - var obj = { - signature: body.signature, - expire: body.expire, - token: body.token - } - respond(false, obj, callback) - } catch (ex) { - respond(true, ex, callback) - } - } else { - try { - var error = JSON.parse(xhr.responseText); - respond(true, error, callback); - } catch (ex) { - respond(true, ex, callback); - } - } - }; - xhr.send(); - return; + Object.defineProperty(response, "$ResponseMetadata", { + value: responseMetadata, + enumerable: false, + writable: false + }); + return response as IKResponse; } -export const uploadFile = (formData: FormData, callback: (err: Error | null, response: UploadResponse | null) => void) => { - var uploadFileXHR = new XMLHttpRequest(); - uploadFileXHR.open('POST', 'https://upload.imagekit.io/api/v1/files/upload'); - uploadFileXHR.onerror = function() { - respond(true, errorMessages.UPLOAD_ENDPOINT_NETWORK_ERROR, callback); - return; +export const request = async ( + uploadFileXHR: XMLHttpRequest, + formData: FormData, + options: ImageKitOptions & { authenticationEndpoint: string }, + callback?: (err: Error | null, response: UploadResponse | null) => void) => { + try { + var signaturObj = await generateSignatureToken(options.authenticationEndpoint); + } catch (ex) { + return respond(true, ex, callback); } - uploadFileXHR.onload = function () { - if (uploadFileXHR.status === 200) { - var uploadResponse = JSON.parse(uploadFileXHR.responseText); - callback(null, uploadResponse); + + formData.append("signature", signaturObj.signature); + formData.append("expire", String(signaturObj.expire)); + formData.append("token", signaturObj.token); + + try { + var result = await uploadFile(uploadFileXHR, formData); + return respond(false, result, callback); + } catch (ex) { + return respond(true, ex, callback); + } +} + +export const generateSignatureToken = ( + authenticationEndpoint: string +): Promise => { + return new Promise((resolve, reject) => { + var xhr = new XMLHttpRequest(); + xhr.timeout = 60000; + xhr.open('GET', authenticationEndpoint); + xhr.ontimeout = function (e) { + return reject(errorMessages.AUTH_ENDPOINT_TIMEOUT); + }; + xhr.onerror = function () { + return reject(errorMessages.AUTH_ENDPOINT_NETWORK_ERROR); } - else if (uploadFileXHR.status !== 200) { - try { - callback(JSON.parse(uploadFileXHR.responseText), null); - } catch (ex : any) { - callback(ex, null); + xhr.onload = function () { + if (xhr.status === 200) { + try { + var body = JSON.parse(xhr.responseText); + var obj = { + signature: body.signature, + expire: body.expire, + token: body.token + } + if (!obj.signature || !obj.expire || !obj.token) { + return reject(errorMessages.AUTH_INVALID_RESPONSE); + } + return resolve(obj); + } catch (ex) { + return reject(errorMessages.AUTH_INVALID_RESPONSE); + } + } else { + return reject(errorMessages.AUTH_INVALID_RESPONSE); } - } - }; - uploadFileXHR.send(formData); - return + }; + xhr.send(); + }); } +export const uploadFile = ( + uploadFileXHR: XMLHttpRequest, + formData: FormData +): Promise | Error> => { + return new Promise((resolve, reject) => { + uploadFileXHR.open('POST', 'https://upload.imagekit.io/api/v1/files/upload'); + uploadFileXHR.onerror = function (e) { + return reject(errorMessages.UPLOAD_ENDPOINT_NETWORK_ERROR); + } + uploadFileXHR.onload = function () { + if (uploadFileXHR.status === 200) { + try { + var body = JSON.parse(uploadFileXHR.responseText); + var uploadResponse = addResponseHeadersAndBody(body, uploadFileXHR); + return resolve(uploadResponse); + } catch (ex: any) { + return reject(ex); + } + } else { + try { + var body = JSON.parse(uploadFileXHR.responseText); + var uploadError = addResponseHeadersAndBody(body, uploadFileXHR); + return reject(uploadError) + } catch (ex: any) { + return reject(ex); + } + } + }; + uploadFileXHR.send(formData); + }); +} \ No newline at end of file diff --git a/test/upload.js b/test/upload.js index 65c794a..2ddc0b7 100644 --- a/test/upload.js +++ b/test/upload.js @@ -21,8 +21,8 @@ const uploadSuccessResponseObj = { "isPrivateFile": false, "customCoordinates": null, "fileType": "image", - "AITags":[{"name":"Face","confidence":99.95,"source":"aws-auto-tagging"}], - "extensionStatus":{"aws-auto-tagging":"success"} + "AITags": [{ "name": "Face", "confidence": 99.95, "source": "aws-auto-tagging" }], + "extensionStatus": { "aws-auto-tagging": "success" } }; function successSignature() { @@ -77,6 +77,14 @@ function errorUploadResponse(statusCode, obj) { server.respond(); } +async function sleep(ms = 0) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(); + }, ms); + }); +} + describe("File upload", function () { var imagekit = new ImageKit(initializationParams); @@ -94,8 +102,7 @@ describe("File upload", function () { server.restore(); }); - it('Invalid Options', function () { - + it('Invalid options', function () { var callback = sinon.spy(); imagekit.upload(undefined, callback); @@ -112,7 +119,7 @@ describe("File upload", function () { var callback = sinon.spy(); imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(0); + expect(server.requests.length).to.be.equal(1); expect(callback.calledOnce).to.be.true; sinon.assert.calledWith(callback, { help: "", message: "Missing fileName parameter for upload" }, null); }); @@ -125,7 +132,7 @@ describe("File upload", function () { var callback = sinon.spy(); imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(0); + expect(server.requests.length).to.be.equal(1); expect(callback.calledOnce).to.be.true; sinon.assert.calledWith(callback, { help: "", message: "Missing file parameter for upload" }, null); }); @@ -139,14 +146,14 @@ describe("File upload", function () { var callback = sinon.spy(); imagekit.upload(fileOptions, callback, { - authenticationEndpoint : "" + authenticationEndpoint: "" }); - expect(server.requests.length).to.be.equal(0); + expect(server.requests.length).to.be.equal(1); sinon.assert.calledWith(callback, { message: "Missing authentication endpoint for upload", help: "" }, null); }); - it('Missing public key', function(){ + it('Missing public key', function () { const fileOptions = { fileName: "test_file_name", file: "test_file" @@ -155,14 +162,14 @@ describe("File upload", function () { var callback = sinon.spy(); imagekit.upload(fileOptions, callback, { - publicKey : "" + publicKey: "" }); - expect(server.requests.length).to.be.equal(0); + expect(server.requests.length).to.be.equal(1); sinon.assert.calledWith(callback, { message: "Missing public key for upload", help: "" }, null); }); - it('Auth endpoint network error handling', function () { + it('Auth endpoint network error handling', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file" @@ -171,17 +178,18 @@ describe("File upload", function () { var callback = sinon.spy(); imagekit.upload(fileOptions, callback, { - authenticationEndpoint : "https://does-not-exist-sdfsdf/aut" + authenticationEndpoint: "https://does-not-exist-sdfsdf/aut" }); - expect(server.requests.length).to.be.equal(1); + expect(server.requests.length).to.be.equal(2); // Simulate network error on authentication endpoint - server.requests[0].error(); + server.requests[1].error(); + await sleep(); sinon.assert.calledWith(callback, { message: "Request to authenticationEndpoint failed due to network error", help: "" }, null); }); - it('Auth endpoint non 200 status code handling', function () { + it('Auth endpoint non 200 status code handling', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file" @@ -191,14 +199,15 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); + expect(server.requests.length).to.be.equal(2); // Simulate non 200 response on authentication endpoint nonSuccessErrorSignature(); - sinon.assert.calledWith(callback, { error: "Not allowed" }, null); + await sleep(); + sinon.assert.calledWith(callback, { message: "Invalid response from authenticationEndpoint. The SDK expects a JSON response with three fields i.e. signature, token and expire.", help: "" }, null); }); - it('Upload endpoint network error handling', function () { + it('Auth endpoint 200 status with bad body', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file" @@ -208,16 +217,68 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + // Simulate non 200 response on authentication endpoint + server.respondWith("GET", initializationParams.authenticationEndpoint, + [ + 200, + { "Content-Type": "application/json" }, + "invalid json" + ]); + server.respond(); + await sleep(); + sinon.assert.calledWith(callback, { message: "Invalid response from authenticationEndpoint. The SDK expects a JSON response with three fields i.e. signature, token and expire.", help: "" }, null); + }); + + it('Auth endpoint 200 status missing token', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file" + }; + + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + + // Simulate non 200 response on authentication endpoint + server.respondWith("GET", initializationParams.authenticationEndpoint, + [ + 200, + { "Content-Type": "application/json" }, + JSON.stringify({ + signature: "sig", + timestamp: "123" + }) + ]); + server.respond(); + await sleep(); + sinon.assert.calledWith(callback, { message: "Invalid response from authenticationEndpoint. The SDK expects a JSON response with three fields i.e. signature, token and expire.", help: "" }, null); + }); + + it('Upload endpoint network error handling', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file" + }; + + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); + // Simulate network error on upload API - server.requests[1].error(); + server.requests[0].error(); + await sleep(); sinon.assert.calledWith(callback, { message: "Request to ImageKit upload endpoint failed due to network error", help: "" }, null); }); - it('Boolean handling', function () { + it('Boolean handling', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -232,12 +293,13 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -254,7 +316,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Tag array handling', function () { + it('Tag array handling', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -267,12 +329,13 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -287,7 +350,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Missing useUniqueFileName', function () { + it('Missing useUniqueFileName', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -299,12 +362,13 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -321,7 +385,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Missing isPrivateFile', function () { + it('Missing isPrivateFile', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -332,12 +396,13 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -354,7 +419,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('With extensions parameter', function(){ + it('With extensions parameter', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -372,17 +437,17 @@ describe("File upload", function () { ], webhookUrl: "https://your-domain/?appId=some-id" }; - var jsonStringifiedExtensions = JSON.stringify(fileOptions.extensions); var callback = sinon.spy(); imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); @@ -395,14 +460,14 @@ describe("File upload", function () { expect(arg.get('useUniqueFileName')).to.be.equal('false'); expect(arg.get('isPrivateFile')).to.be.equal('true'); expect(arg.get('publicKey')).to.be.equal('test_public_key'); - expect(arg.get('extensions')).to.be.equal(jsonStringifiedExtensions); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); expect(arg.get('webhookUrl')).to.be.equal('https://your-domain/?appId=some-id') expect(callback.calledOnce).to.be.true; sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Bare minimum request', function () { + it('Bare minimum request', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file", @@ -413,19 +478,20 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("test_file"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); expect(arg.get('expire')).to.be.equal("123"); expect(arg.get('signature')).to.be.equal("test_signature"); expect(arg.get('publicKey')).to.be.equal('test_public_key'); - expect(arg.get('tags')).to.be.equal(undefined); + expect(arg.get('tags')).to.be.equal('undefined'); expect(arg.get('isPrivateFile')).to.be.equal(undefined); expect(arg.get('useUniqueFileName')).to.be.equal(undefined); expect(arg.get('customCoordinates')).to.be.equal(undefined); @@ -435,7 +501,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Bare minimum request: Blob', function () { + it('Bare minimum request: Blob', async function () { const buffer = Buffer.from("test_buffer") const fileOptions = { fileName: "test_file_name", @@ -446,12 +512,14 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; - var arg = server.requests[1].requestBody; expect(arg.get('file').size).to.be.eq(buffer.length); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -468,7 +536,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Error during upload', function () { + it('Error during upload', async function () { const fileOptions = { fileName: "test_file_name", file: "test_file" @@ -478,34 +546,90 @@ describe("File upload", function () { imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); - successSignature(); expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); var errRes = { help: "For support kindly contact us at support@imagekit.io .", message: "Your account cannot be authenticated." } errorUploadResponse(500, errRes); + await sleep(); expect(callback.calledOnce).to.be.true; sinon.assert.calledWith(callback, errRes, null); }); - it('Upload via URL', function () { + it('Error during upload non 2xx with bad body', async function () { const fileOptions = { fileName: "test_file_name", - file: "https://ik.imagekit.io/remote-url.jpg" + file: "test_file" }; var callback = sinon.spy(); imagekit.upload(fileOptions, callback); - expect(server.requests.length).to.be.equal(1); + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); + server.respondWith("POST", "https://upload.imagekit.io/api/v1/files/upload", + [ + 500, + { "Content-Type": "application/json" }, + "sdf" + ] + ); + server.respond(); + await sleep(); + expect(callback.calledOnce).to.be.true; + var error = callback.args[0][0]; + expect(error instanceof SyntaxError).to.be.true; + }); + + it('Error during upload 2xx with bad body', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file" + }; + + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); successSignature(); + await sleep(); + server.respondWith("POST", "https://upload.imagekit.io/api/v1/files/upload", + [ + 200, + { "Content-Type": "application/json" }, + "sdf" + ] + ); + server.respond(); + await sleep(); + expect(callback.calledOnce).to.be.true; + var error = callback.args[0][0]; + expect(error instanceof SyntaxError).to.be.true; + }); + + it('Upload via URL', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "https://ik.imagekit.io/remote-url.jpg" + }; + + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("https://ik.imagekit.io/remote-url.jpg"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("test_token"); @@ -522,7 +646,7 @@ describe("File upload", function () { sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); - it('Overriding public key and authentication endpoint', function () { + it('Overriding public key and authentication endpoint', async function () { var newAuthEndpoint = "http://test/auth-override"; var newPublicKey = "override_public_key"; @@ -538,22 +662,23 @@ describe("File upload", function () { publicKey: newPublicKey }); - expect(server.requests.length).to.be.equal(1); + expect(server.requests.length).to.be.equal(2); server.respondWith("GET", newAuthEndpoint, - [ - 200, - { "Content-Type": "application/json" }, - JSON.stringify({ - signature: "override_test_signature", - expire: 123123, - token: "override_test_token" - }) - ]); + [ + 200, + { "Content-Type": "application/json" }, + JSON.stringify({ + signature: "override_test_signature", + expire: 123123, + token: "override_test_token" + }) + ]); server.respond(); - expect(server.requests.length).to.be.equal(2); + await sleep(); successUploadResponse(); + await sleep(); - var arg = server.requests[1].requestBody; + var arg = server.requests[0].requestBody; expect(arg.get('file')).to.be.equal("https://ik.imagekit.io/remote-url.jpg"); expect(arg.get('fileName')).to.be.equal("test_file_name"); expect(arg.get('token')).to.be.equal("override_test_token"); @@ -565,10 +690,443 @@ describe("File upload", function () { expect(arg.get('useUniqueFileName')).to.be.equal(undefined); expect(arg.get('customCoordinates')).to.be.equal(undefined); expect(arg.get('responseFields')).to.be.equal(undefined); + expect(arg.get('extensions')).to.be.equal(undefined); + expect(arg.get('customMetadata')).to.be.equal(undefined); expect(callback.calledOnce).to.be.true; sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); }); -}); + it('With overwrite parameters', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ], + overwriteFile: false, + overwriteAITags: false, + overwriteTags: false, + overwriteCustomMetadata: false + }; + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags, customCoordinates, isPrivateFile, metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + expect(arg.get('overwriteFile')).to.be.equal('false'); + expect(arg.get('overwriteAITags')).to.be.equal('false'); + expect(arg.get('overwriteTags')).to.be.equal('false'); + expect(arg.get('overwriteCustomMetadata')).to.be.equal('false'); + + expect(callback.calledOnce).to.be.true; + sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); + }); + + it('With customMetadata', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ], + overwriteFile: false, + overwriteAITags: false, + overwriteTags: false, + overwriteCustomMetadata: false, + customMetadata: { + brand: "Nike", + color: "red" + }, + }; + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags, customCoordinates, isPrivateFile, metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + expect(arg.get('overwriteFile')).to.be.equal('false'); + expect(arg.get('overwriteAITags')).to.be.equal('false'); + expect(arg.get('overwriteTags')).to.be.equal('false'); + expect(arg.get('overwriteCustomMetadata')).to.be.equal('false'); + expect(arg.get('customMetadata')).to.be.equal(JSON.stringify(fileOptions.customMetadata)); + + expect(callback.calledOnce).to.be.true; + sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); + }); + + it('Array type fields', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: ["test_tag1", "test_tag2"], + customCoordinates: "10, 10, 100, 100", + responseFields: ["tags", "customCoordinates", "isPrivateFile", "metadata"], + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ], + overwriteFile: false, + overwriteAITags: false, + overwriteTags: false, + overwriteCustomMetadata: false, + customMetadata: { + brand: "Nike", + color: "red" + }, + }; + var callback = sinon.spy(); + + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags,customCoordinates,isPrivateFile,metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + expect(arg.get('overwriteFile')).to.be.equal('false'); + expect(arg.get('overwriteAITags')).to.be.equal('false'); + expect(arg.get('overwriteTags')).to.be.equal('false'); + expect(arg.get('overwriteCustomMetadata')).to.be.equal('false'); + expect(arg.get('customMetadata')).to.be.equal(JSON.stringify(fileOptions.customMetadata)); + + expect(callback.calledOnce).to.be.true; + sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); + }); + + it('check custom XHR object is used', async function () { + var xhr = new XMLHttpRequest(); + var fun = function () { return "hello from function" }; + xhr.onprogress = fun; + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ], + xhr + }; + var callback = sinon.spy(); + imagekit.upload(fileOptions, callback); + expect(server.requests.length).to.be.equal(2); + expect(server.requests[0]).to.be.equal(xhr); + expect(server.requests[0].onprogress.toString()).to.be.equal(fun.toString()); + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags, customCoordinates, isPrivateFile, metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + + expect(callback.calledOnce).to.be.true; + sinon.assert.calledWith(callback, null, uploadSuccessResponseObj); + }); + + it('Upload using promise - success', async function () { + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] + }; + + var uploadPromise = imagekit.upload(fileOptions); + expect(server.requests.length).to.be.equal(2); + + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags, customCoordinates, isPrivateFile, metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + var response = await uploadPromise; + expect(response).to.be.deep.equal(uploadSuccessResponseObj); + }); + + it('Upload using promise - error', async function () { + var errRes = { + help: "For support kindly contact us at support@imagekit.io .", + message: "Your account cannot be authenticated." + } + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] + }; + try { + var uploadPromise = imagekit.upload(fileOptions); + successSignature(); + await sleep(); + errorUploadResponse(500, errRes); + await sleep(); + var response = await uploadPromise; + } catch (ex) { + expect(ex).to.be.deep.equal(errRes); + } + }); + + it('Custom xhr promise', async function () { + var xhr = new XMLHttpRequest(); + var fun = function () { return "hello from function" }; + xhr.onprogress = fun; + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ], + xhr + }; + var uploadPromise = imagekit.upload(fileOptions); + + expect(server.requests.length).to.be.equal(2); + + + successSignature(); + await sleep(); + successUploadResponse(); + await sleep(); + + var arg = server.requests[0].requestBody; + expect(arg.get('file')).to.be.equal("test_file"); + expect(server.requests[0]).to.be.equal(xhr); + expect(server.requests[0].onprogress.toString()).to.be.equal(fun.toString()); + + expect(arg.get('file')).to.be.equal("test_file"); + expect(arg.get('fileName')).to.be.equal("test_file_name"); + expect(arg.get('token')).to.be.equal("test_token"); + expect(arg.get('expire')).to.be.equal("123"); + expect(arg.get('signature')).to.be.equal("test_signature"); + expect(arg.get('tags')).to.be.equal("test_tag1,test_tag2"); + expect(arg.get('customCoordinates')).to.be.equal("10, 10, 100, 100"); + expect(arg.get('responseFields')).to.be.equal("tags, customCoordinates, isPrivateFile, metadata"); + expect(arg.get('useUniqueFileName')).to.be.equal('false'); + expect(arg.get('isPrivateFile')).to.be.equal('true'); + expect(arg.get('publicKey')).to.be.equal('test_public_key'); + expect(arg.get('extensions')).to.be.equal(JSON.stringify(fileOptions.extensions)); + + var response = await uploadPromise; + expect(response).to.be.deep.equal(uploadSuccessResponseObj); + }); + + it('$ResponseMetadata assertions using promise', async function () { + var dummyResonseHeaders = { + "Content-Type": "application/json", + "x-request-id": "sdfsdfsdfdsf" + }; + const fileOptions = { + fileName: "test_file_name", + file: "test_file", + tags: "test_tag1,test_tag2", + customCoordinates: "10, 10, 100, 100", + responseFields: "tags, customCoordinates, isPrivateFile, metadata", + useUniqueFileName: false, + isPrivateFile: true, + extensions: [ + { + name: "aws-auto-tagging", + minConfidence: 80, + maxTags: 10 + } + ] + }; + + var uploadPromise = imagekit.upload(fileOptions) + expect(server.requests.length).to.be.equal(2); + + successSignature(); + await sleep(); + + server.respondWith("POST", "https://upload.imagekit.io/api/v1/files/upload", + [ + 200, + dummyResonseHeaders, + JSON.stringify(uploadSuccessResponseObj) + ] + ); + server.respond(); + await sleep(); + + var response = await uploadPromise; + expect(response.$ResponseMetadata.headers).to.be.deep.equal(dummyResonseHeaders); + expect(response.$ResponseMetadata.statusCode).to.be.deep.equal(200); + }); + + it('$ResponseMetadata assertions using callback', async function () { + var dummyResonseHeaders = { + "Content-Type": "application/json", + "x-request-id": "sdfsdfsdfdsf" + }; + const fileOptions = { + fileName: "test_file_name", + file: "test_file" + }; + var callback = sinon.spy(); + imagekit.upload(fileOptions, callback); + + expect(server.requests.length).to.be.equal(2); + + successSignature(); + await sleep(); + server.respondWith("POST", "https://upload.imagekit.io/api/v1/files/upload", + [ + 200, + dummyResonseHeaders, + JSON.stringify(uploadSuccessResponseObj) + ] + ); + server.respond(); + await sleep(); + + expect(callback.calledOnce).to.be.true; + + var callBackArguments = callback.args[0]; + expect(callBackArguments.length).to.be.eq(2); + var callbackResult = callBackArguments[1]; + + expect(callbackResult).to.be.deep.equal(uploadSuccessResponseObj); + expect(callbackResult.$ResponseMetadata.headers).to.be.deep.equal(dummyResonseHeaders); + expect(callbackResult.$ResponseMetadata.statusCode).to.be.deep.equal(200); + }); +}); diff --git a/test/url-generation.js b/test/url-generation.js index f29c608..3bf1303 100644 --- a/test/url-generation.js +++ b/test/url-generation.js @@ -337,11 +337,11 @@ describe("URL generation", function () { effectContrast: true, effectGray: true, original: true, - raw: "this_should_come_as_it_is" + raw: "h-200,w-300,l-image,i-logo.png,l-end" }] }) - expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,ox-35,oy-35,ofo-bottom,oh-20,ow-20,oi-folder@@file.jpg,oit-false,oiar-4:3,oibg-0F0F0F,oib-10_0F0F0F,oidpr-2,oiq-50,oic-force,ot-two%20words,ots-20,otf-Open%20Sans,otc-00FFFF,oa-5,ott-b,obg-00AAFF55,ote-b3ZlcmxheSBtYWRlIGVhc3k%3D,otw-50,otbg-00AAFF55,otp-40,otia-left,or-10,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,e-sharpen-10,e-usm-2-2-0.8-0.024,e-contrast-true,e-grayscale-true,orig-true,this_should_come_as_it_is/test_path.jpg?ik-sdk-version=javascript-${pkg.version}`); + expect(url).equal(`https://ik.imagekit.io/test_url_endpoint/tr:h-300,w-400,ar-4-3,q-40,c-force,cm-extract,fo-left,f-jpeg,r-50,bg-A94D34,b-5-A94D34,rt-90,bl-10,n-some_name,ox-35,oy-35,ofo-bottom,oh-20,ow-20,oi-folder@@file.jpg,oit-false,oiar-4:3,oibg-0F0F0F,oib-10_0F0F0F,oidpr-2,oiq-50,oic-force,ot-two%20words,ots-20,otf-Open%20Sans,otc-00FFFF,oa-5,ott-b,obg-00AAFF55,ote-b3ZlcmxheSBtYWRlIGVhc3k%3D,otw-50,otbg-00AAFF55,otp-40,otia-left,or-10,pr-true,lo-true,t-5,md-true,cp-true,di-folder@@file.jpg,dpr-3,e-sharpen-10,e-usm-2-2-0.8-0.024,e-contrast-true,e-grayscale-true,orig-true,h-200,w-300,l-image,i-logo.png,l-end/test_path.jpg?ik-sdk-version=javascript-${pkg.version}`); }); });