Skip to content

Commit ce60c41

Browse files
authored
Merge pull request #56 from dev-saad07/sdk-improvements
Sdk improvements
2 parents 05f6002 + ad1ad38 commit ce60c41

File tree

9 files changed

+410
-75
lines changed

9 files changed

+410
-75
lines changed

README.md

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ Sample usage
287287
// Upload function internally uses the ImageKit.io javascript SDK
288288
function upload(data) {
289289
var file = document.getElementById("file1");
290-
imagekit.upload({
290+
291+
// Using Callback Function
292+
var xhr = imagekit.upload({
291293
file: file.files[0],
292294
fileName: "abc1.jpg",
293295
tags: ["tag1"],
@@ -305,10 +307,78 @@ Sample usage
305307
transformation: [{ height: 300, width: 400}]
306308
}));
307309
})
310+
311+
xhr.upload.onprogress = (event) => {
312+
console.log(`Uploaded ${event.loaded} of ${event.total} bytes`);
313+
}
314+
315+
// Using Promises
316+
imagekit.upload({
317+
file: file.files[0],
318+
fileName: "abc1.jpg",
319+
tags: ["tag1"],
320+
extensions: [
321+
{
322+
name: "aws-auto-tagging",
323+
minConfidence: 80,
324+
maxTags: 10
325+
}
326+
]
327+
}).then(result => {
328+
console.log(imagekit.url({
329+
src: result.url,
330+
transformation: [{ height: 300, width: 400}]
331+
}));
332+
}).then(error => {
333+
console.log(error);
334+
})
308335
}
309336
</script>
310337
```
311338

312339
If the upload succeeds, `err` will be `null`, and the `result` will be the same as what is received from ImageKit's servers.
313340
If the upload fails, `err` will be the same as what is received from ImageKit's servers, and the `result` will be null.
341+
Upload using callback functions returns the upload XHR, it can be used to monitor the progress of the upload.
342+
343+
## Access request-id, other response headers and HTTP status code
344+
You can access `$ResponseMetadata` on success or error object to access the HTTP status code and response headers.
345+
346+
```javascript
347+
// Success
348+
var response = await imagekit.upload({
349+
file: file.files[0],
350+
fileName: "abc1.jpg",
351+
tags: ["tag1"],
352+
extensions: [
353+
{
354+
name: "aws-auto-tagging",
355+
minConfidence: 80,
356+
maxTags: 10
357+
}
358+
]
359+
});
360+
console.log(response.$ResponseMetadata.statusCode); // 200
361+
362+
// { 'content-length': "300", 'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'}
363+
console.log(response.$ResponseMetadata.headers);
364+
365+
// Error
366+
try {
367+
await imagekit.upload({
368+
file: file.files[0],
369+
fileName: "abc1.jpg",
370+
tags: ["tag1"],
371+
extensions: [
372+
{
373+
name: "aws-auto-tagging",
374+
minConfidence: 80,
375+
maxTags: 10
376+
}
377+
]
378+
});
379+
} catch (ex) {
380+
console.log(response.$ResponseMetadata.statusCode); // 400
314381

382+
// {'content-type': 'application/json', 'x-request-id': 'ee560df4-d44f-455e-a48e-29dfda49aec5'}
383+
console.log(response.$ResponseMetadata.headers);
384+
}

src/constants/supportedTransforms.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ const supportedTransforms: { [key: string]: string } = {
281281
* @link https://docs.imagekit.io/features/image-transformations/resize-crop-and-other-transformations#original-image-orig
282282
*/
283283
original: "orig",
284+
285+
/**
286+
* @link https://docs.imagekit.io/features/image-transformations/conditional-transformations
287+
*/
288+
raw: "raw",
284289
}
285290

286291

src/index.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { version } from "../package.json";
22
import errorMessages from "./constants/errorMessages";
33
import { ImageKitOptions, UploadOptions, UploadResponse, UrlOptions } from "./interfaces";
4+
import IKResponse from "./interfaces/IKResponse";
45
import { upload } from "./upload/index";
56
import { url } from "./url/index";
67
import transformationUtils from "./utils/transformation";
@@ -13,6 +14,30 @@ function privateKeyPassed(options: ImageKitOptions) {
1314
return typeof (options as any).privateKey != "undefined";
1415
}
1516

17+
const promisify = function <T = void>(thisContext: ImageKit, fn: Function) {
18+
return function (...args: any[]): Promise<T> | void {
19+
if (args.length === fn.length && typeof args[args.length - 1] !== "undefined") {
20+
if (typeof args[args.length - 1] !== "function") {
21+
throw new Error("Callback must be a function.");
22+
}
23+
fn.call(thisContext, ...args);
24+
} else {
25+
return new Promise<T>((resolve, reject) => {
26+
const callback = function (err: Error, ...results: any[]) {
27+
if (err) {
28+
return reject(err);
29+
} else {
30+
resolve(results.length > 1 ? results : results[0]);
31+
}
32+
};
33+
args.pop()
34+
args.push(callback);
35+
fn.call(thisContext, ...args);
36+
});
37+
}
38+
};
39+
};
40+
1641
class ImageKit {
1742
options: ImageKitOptions = {
1843
sdkVersion: `javascript-${version}`,
@@ -58,16 +83,26 @@ class ImageKit {
5883
*
5984
* @param uploadOptions
6085
*/
61-
upload(
62-
uploadOptions: UploadOptions,
63-
callback?: (err: Error | null, response: UploadResponse | null) => void,
64-
options?: Partial<ImageKitOptions>,
65-
): void {
66-
var mergedOptions = {
86+
upload(uploadOptions: UploadOptions, options?: Partial<ImageKitOptions>): Promise<IKResponse<UploadResponse>>
87+
upload(uploadOptions: UploadOptions, callback: (err: Error | null, response: IKResponse<UploadResponse> | null) => void, options?: Partial<ImageKitOptions>): XMLHttpRequest;
88+
upload(uploadOptions: UploadOptions, callbackOrOptions?: ((err: Error | null, response: IKResponse<UploadResponse> | null) => void) | Partial<ImageKitOptions>, options?: Partial<ImageKitOptions>): XMLHttpRequest | Promise<IKResponse<UploadResponse>> {
89+
let callback;
90+
if (typeof callbackOrOptions === 'function') {
91+
callback = callbackOrOptions;
92+
} else {
93+
options = callbackOrOptions || {};
94+
}
95+
var mergedOptions = {
6796
...this.options,
6897
...options,
6998
};
70-
return upload(uploadOptions, mergedOptions, callback);
99+
const xhr = new XMLHttpRequest();
100+
const promise = promisify<IKResponse<UploadResponse>>(this, upload)(xhr, uploadOptions, mergedOptions, callback);
101+
if (typeof promise === "object" && typeof promise.then === "function") {
102+
return promise
103+
} else {
104+
return xhr;
105+
}
71106
}
72107
}
73108

src/interfaces/IKResponse.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
interface ResponseMetadata {
2+
statusCode: number;
3+
headers: Record<string, string | number | boolean>;
4+
}
5+
6+
type IKResponse<T> = T extends Error
7+
? T & { $ResponseMetadata?: ResponseMetadata }
8+
: T & { $ResponseMetadata: ResponseMetadata };
9+
10+
export default IKResponse;

src/interfaces/UploadOptions.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,27 @@ export interface UploadOptions {
7575
* Final status of pending extensions will be sent to this URL.
7676
*/
7777
webhookUrl?: string
78+
/*
79+
* 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.
80+
*/
81+
overwriteFile?: boolean
82+
/*
83+
* 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.
84+
*/
85+
overwriteAITags?: boolean
86+
/*
87+
* 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.
88+
* In case the request body has tags, setting overwriteTags to false has no effect and request's tags are set on the asset.
89+
*/
90+
overwriteTags?: boolean
91+
/*
92+
* 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.
93+
* In case the request body has customMetadata, setting overwriteCustomMetadata to false has no effect and request's customMetadata is set on the asset.
94+
*/
95+
overwriteCustomMetadata?: boolean
96+
/*
97+
* Stringified JSON key-value data to be associated with the asset. Checkout overwriteCustomMetadata parameter to understand default behaviour.
98+
* Before setting any custom metadata on an asset you have to create the field using custom metadata fields API.
99+
*/
100+
customMetadata?: string | Record<string, string | number | boolean | Array<string | number | boolean>>
78101
}

src/upload/index.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { request } from "../utils/request";
44
import { ImageKitOptions, UploadOptions, UploadResponse } from "../interfaces";
55

66
export const upload = (
7+
xhr: XMLHttpRequest,
78
uploadOptions: UploadOptions,
89
options: ImageKitOptions,
910
callback?: (err: Error | null, response: UploadResponse | null) => void,
@@ -39,23 +40,28 @@ export const upload = (
3940
}
4041

4142
var formData = new FormData();
42-
let i: keyof typeof uploadOptions;
43-
for (i in uploadOptions) {
44-
const param = uploadOptions[i];
45-
if (typeof param !== "undefined") {
46-
if (typeof param === "string" || typeof param === "boolean") {
47-
formData.append(i, String(param));
48-
}
49-
else if(Array.isArray(param)) {
50-
formData.append(i, JSON.stringify(param));
43+
let key: keyof typeof uploadOptions;
44+
for (key in uploadOptions) {
45+
if (key) {
46+
if (key == "file" && typeof uploadOptions.file != "string") {
47+
formData.append('file', uploadOptions.file, String(uploadOptions.fileName));
48+
} else if (key == "tags" && Array.isArray(uploadOptions.tags)) {
49+
formData.append('tags', uploadOptions.tags.join(","));
50+
} else if (key == "responseFields" && Array.isArray(uploadOptions.responseFields)) {
51+
formData.append('responseFields', uploadOptions.responseFields.join(","));
52+
} else if (key == "extensions" && Array.isArray(uploadOptions.extensions)) {
53+
formData.append('extensions', JSON.stringify(uploadOptions.extensions));
54+
} else if (key === "customMetadata" && typeof uploadOptions.customMetadata === "object" &&
55+
!Array.isArray(uploadOptions.customMetadata) && uploadOptions.customMetadata !== null) {
56+
formData.append('customMetadata', JSON.stringify(uploadOptions.customMetadata));
5157
}
5258
else {
53-
formData.append(i, param);
59+
formData.append(key, String(uploadOptions[key]));
5460
}
5561
}
5662
}
5763

5864
formData.append("publicKey", options.publicKey);
5965

60-
request(formData, { ...options, authenticationEndpoint: options.authenticationEndpoint }, callback);
66+
request(xhr, formData, { ...options, authenticationEndpoint: options.authenticationEndpoint }, callback);
6167
};

src/utils/request.ts

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,44 @@
11
import respond from "../utils/respond";
22
import errorMessages from "../constants/errorMessages"
33
import { ImageKitOptions, UploadResponse } from "../interfaces";
4+
import IKResponse from "../interfaces/IKResponse";
45

56
interface SignatureResponse {
67
signature: string
78
expire: number
89
token: string
910
}
1011

11-
export const request = (formData: FormData, options: ImageKitOptions & { authenticationEndpoint: string }, callback?: (err: Error | null, response: UploadResponse | null) => void) => {
12+
function getResponseHeaderMap(xhr: XMLHttpRequest) {
13+
const headers: Record<string, string | number | boolean> = {};
14+
const responseHeaders = xhr.getAllResponseHeaders();
15+
if (Object.keys(responseHeaders).length) {
16+
responseHeaders
17+
.trim()
18+
.split(/[\r\n]+/)
19+
.map(value => value.split(/: /))
20+
.forEach(keyValue => {
21+
headers[keyValue[0].trim()] = keyValue[1].trim();
22+
});
23+
}
24+
return headers;
25+
}
26+
27+
const addResponseHeadersAndBody = (body: any, xhr: XMLHttpRequest):IKResponse<UploadResponse> => {
28+
let response = { ...body };
29+
const responseMetadata = {
30+
statusCode: xhr.status,
31+
headers: getResponseHeaderMap(xhr)
32+
}
33+
Object.defineProperty(response, "$ResponseMetadata", {
34+
value: responseMetadata,
35+
enumerable: false,
36+
writable: false
37+
});
38+
return response as IKResponse<UploadResponse>;
39+
}
40+
41+
export const request = (uploadFileXHR: XMLHttpRequest,formData: FormData, options: ImageKitOptions & { authenticationEndpoint: string }, callback?: (err: Error | null, response: UploadResponse | null) => void) => {
1242
generateSignatureToken(options, (err, signaturObj) => {
1343
if (err) {
1444
return respond(true, err, callback)
@@ -17,25 +47,30 @@ export const request = (formData: FormData, options: ImageKitOptions & { authent
1747
formData.append("expire", String(signaturObj?.expire || 0));
1848
formData.append("token", signaturObj?.token || "");
1949

20-
uploadFile(formData, (err, responseSucessText) => {
50+
uploadFile(uploadFileXHR, formData, (err, responseSucessText) => {
2151
if (err) {
2252
return respond(true, err, callback)
2353
}
2454
return respond(false, responseSucessText!, callback)
2555
});
2656
}
2757
});
58+
return uploadFileXHR;
2859
}
2960

3061
export const generateSignatureToken = (options: ImageKitOptions & { authenticationEndpoint: string }, callback: (err: Error | null, response: SignatureResponse | null) => void) => {
3162
var xhr = new XMLHttpRequest();
3263
xhr.timeout = 60000;
3364
xhr.open('GET', options.authenticationEndpoint);
3465
xhr.ontimeout = function (e) {
35-
respond(true, errorMessages.AUTH_ENDPOINT_TIMEOUT, callback);
66+
var body = errorMessages.AUTH_ENDPOINT_TIMEOUT;
67+
var result = addResponseHeadersAndBody(body, xhr);
68+
respond(true, result, callback);
3669
};
3770
xhr.onerror = function() {
38-
respond(true, errorMessages.AUTH_ENDPOINT_NETWORK_ERROR, callback);
71+
var body = errorMessages.AUTH_ENDPOINT_NETWORK_ERROR;
72+
var result = addResponseHeadersAndBody(body, xhr);
73+
respond(true, result, callback);
3974
}
4075
xhr.onload = function () {
4176
if (xhr.status === 200) {
@@ -46,14 +81,16 @@ export const generateSignatureToken = (options: ImageKitOptions & { authenticati
4681
expire: body.expire,
4782
token: body.token
4883
}
49-
respond(false, obj, callback)
84+
var result = addResponseHeadersAndBody(obj, xhr);
85+
respond(false, result, callback);
5086
} catch (ex) {
5187
respond(true, ex, callback)
5288
}
5389
} else {
5490
try {
5591
var error = JSON.parse(xhr.responseText);
56-
respond(true, error, callback);
92+
var result = addResponseHeadersAndBody(error, xhr);
93+
respond(true, result, callback);
5794
} catch (ex) {
5895
respond(true, ex, callback);
5996
}
@@ -63,27 +100,30 @@ export const generateSignatureToken = (options: ImageKitOptions & { authenticati
63100
return;
64101
}
65102

66-
export const uploadFile = (formData: FormData, callback: (err: Error | null, response: UploadResponse | null) => void) => {
67-
var uploadFileXHR = new XMLHttpRequest();
103+
export const uploadFile = (uploadFileXHR:XMLHttpRequest, formData: FormData, callback: (err: Error | IKResponse<UploadResponse> | null, response: UploadResponse | null) => void) => {
68104
uploadFileXHR.open('POST', 'https://upload.imagekit.io/api/v1/files/upload');
69105
uploadFileXHR.onerror = function() {
70-
respond(true, errorMessages.UPLOAD_ENDPOINT_NETWORK_ERROR, callback);
106+
var body = errorMessages.UPLOAD_ENDPOINT_NETWORK_ERROR;
107+
var result = addResponseHeadersAndBody(body, uploadFileXHR);
108+
respond(true, result, callback);
71109
return;
72110
}
73111
uploadFileXHR.onload = function () {
74112
if (uploadFileXHR.status === 200) {
75-
var uploadResponse = JSON.parse(uploadFileXHR.responseText);
113+
var body = JSON.parse(uploadFileXHR.responseText);
114+
var uploadResponse = addResponseHeadersAndBody(body, uploadFileXHR);
76115
callback(null, uploadResponse);
77116
}
78117
else if (uploadFileXHR.status !== 200) {
79118
try {
80-
callback(JSON.parse(uploadFileXHR.responseText), null);
119+
var body = JSON.parse(uploadFileXHR.responseText);
120+
var uploadResponse = addResponseHeadersAndBody(body, uploadFileXHR);
121+
callback(uploadResponse, null);
81122
} catch (ex : any) {
82123
callback(ex, null);
83124
}
84125
}
85126
};
86127
uploadFileXHR.send(formData);
87-
return
88128
}
89129

0 commit comments

Comments
 (0)