cloudinary-lite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/index.js +160 -0
  2. package/package.json +24 -0
  3. package/readme.md +122 -0
package/index.js ADDED
@@ -0,0 +1,160 @@
1
+ /**
2
+ * cloudinary-lite
3
+ * Zero-dependency, lightweight helper library for Cloudinary.
4
+ * Supported in both browser and server environments.
5
+ */
6
+
7
+ /**
8
+ * Uploads a file directly to Cloudinary from the browser using an unsigned preset.
9
+ *
10
+ * @param {Object} params
11
+ * @param {File|Blob} params.file - The file object to upload.
12
+ * @param {string} params.cloudName - Your Cloudinary Cloud Name.
13
+ * @param {string} params.uploadPreset - The name of the unsigned upload preset.
14
+ * @param {string} [params.folder] - Optional folder path to save the asset.
15
+ * @returns {Promise<string>} - The secure URL of the uploaded asset.
16
+ */
17
+ export async function uploadUnsigned({ file, cloudName, uploadPreset, folder = 'others' }) {
18
+ if (!file || !cloudName || !uploadPreset) {
19
+ throw new Error('Missing required arguments: file, cloudName, and uploadPreset are required.');
20
+ }
21
+
22
+ const url = `https://api.cloudinary.com/v1_1/${cloudName}/upload`;
23
+ const formData = new FormData();
24
+ formData.append('file', file);
25
+ formData.append('upload_preset', uploadPreset);
26
+ formData.append('folder', folder);
27
+
28
+ const response = await fetch(url, {
29
+ method: 'POST',
30
+ body: formData
31
+ });
32
+
33
+ if (!response.ok) {
34
+ const errorData = await response.json();
35
+ throw new Error(errorData.error?.message || 'Cloudinary upload failed');
36
+ }
37
+
38
+ const data = await response.json();
39
+ if (data && data.secure_url) {
40
+ return data.secure_url;
41
+ } else {
42
+ throw new Error('Failed to get secure URL from Cloudinary upload response');
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Deletes an asset from Cloudinary.
48
+ * Secure operation - MUST only be called in server environments (Node.js).
49
+ *
50
+ * @param {Object} params
51
+ * @param {string} params.publicId - The Cloudinary public ID of the resource.
52
+ * @param {string} [params.resourceType] - The resource type: 'image', 'raw', or 'video'.
53
+ * @param {string} params.cloudName - Your Cloudinary Cloud Name.
54
+ * @param {string} params.apiKey - Your Cloudinary API Key.
55
+ * @param {string} params.apiSecret - Your Cloudinary API Secret.
56
+ * @returns {Promise<boolean>} - True if deletion succeeded, false otherwise.
57
+ */
58
+ export async function deleteSigned({ publicId, resourceType = 'image', cloudName, apiKey, apiSecret }) {
59
+ if (!publicId || !cloudName || !apiKey || !apiSecret) {
60
+ throw new Error('Missing required arguments for deletion: publicId, cloudName, apiKey, and apiSecret are required.');
61
+ }
62
+
63
+ // Use dynamic import of Node's native 'crypto' module so frontend bundlers
64
+ // do not compile Node-specific APIs if this function is not imported.
65
+ const crypto = await import('crypto');
66
+
67
+ const timestamp = Math.round(new Date().getTime() / 1000);
68
+
69
+ // Generate SHA-1 signature
70
+ const signatureData = `public_id=${publicId}&timestamp=${timestamp}${apiSecret}`;
71
+ const signature = crypto.createHash('sha1').update(signatureData).digest('hex');
72
+
73
+ const url = `https://api.cloudinary.com/v1_1/${cloudName}/${resourceType}/destroy`;
74
+
75
+ const response = await fetch(url, {
76
+ method: 'POST',
77
+ headers: {
78
+ 'Content-Type': 'application/json'
79
+ },
80
+ body: JSON.stringify({
81
+ public_id: publicId,
82
+ timestamp,
83
+ api_key: apiKey,
84
+ signature
85
+ })
86
+ });
87
+
88
+ if (!response.ok) {
89
+ const errorData = await response.json();
90
+ throw new Error(errorData.error?.message || 'Failed to destroy resource');
91
+ }
92
+
93
+ const result = await response.json();
94
+ return result.result === 'ok';
95
+ }
96
+
97
+ /**
98
+ * Helper to parse a Cloudinary URL and extract its public ID and resource type.
99
+ *
100
+ * @param {string} url - The complete Cloudinary URL.
101
+ * @returns {Object|null} - An object containing { public_id, resource_type } or null.
102
+ */
103
+ export function parseCloudinaryUrl(url) {
104
+ if (!url || typeof url !== 'string' || !url.includes('cloudinary.com')) {
105
+ return null;
106
+ }
107
+
108
+ try {
109
+ const parsed = new URL(url);
110
+ const path = parsed.pathname;
111
+
112
+ // Split pathname: /de3t60fd7/image/upload/v1722513904/alumni/profile-photos/abc123xyz.jpg
113
+ const parts = path.split('/').filter(Boolean);
114
+ if (parts.length < 4) {
115
+ return null;
116
+ }
117
+
118
+ const resourceType = parts[1]; // e.g. "image" or "raw"
119
+
120
+ // Find version index (starts with v followed by numbers)
121
+ let versionIndex = -1;
122
+ for (let i = 3; i < parts.length; i++) {
123
+ const part = parts[i];
124
+ if (part.startsWith('v') && !isNaN(part.slice(1)) && /^\d+$/.test(part.slice(1))) {
125
+ versionIndex = i;
126
+ break;
127
+ }
128
+ }
129
+
130
+ // Fallback if version not found (take index after upload)
131
+ if (versionIndex === -1) {
132
+ const uploadIndex = parts.indexOf('upload');
133
+ if (uploadIndex !== -1) {
134
+ versionIndex = uploadIndex + 1;
135
+ }
136
+ }
137
+
138
+ if (versionIndex === -1) {
139
+ return null;
140
+ }
141
+
142
+ const publicIdParts = parts.slice(versionIndex + 1);
143
+ const publicIdWithExt = publicIdParts.join('/');
144
+
145
+ let publicId = publicIdWithExt;
146
+ if (resourceType !== 'raw') {
147
+ const dotIndex = publicIdWithExt.lastIndexOf('.');
148
+ if (dotIndex !== -1) {
149
+ publicId = publicIdWithExt.slice(0, dotIndex);
150
+ }
151
+ }
152
+
153
+ return {
154
+ public_id: publicId,
155
+ resource_type: resourceType
156
+ };
157
+ } catch (_) {
158
+ return null;
159
+ }
160
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "cloudinary-lite",
3
+ "version": "1.0.0",
4
+ "description": "Zero-dependency, lightweight helper for Cloudinary uploads (unsigned client-side) and secure deletes (signed server-side).",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "exports": {
8
+ ".": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 0"
12
+ },
13
+ "keywords": [
14
+ "cloudinary",
15
+ "lite",
16
+ "upload",
17
+ "delete",
18
+ "unsigned",
19
+ "client",
20
+ "server"
21
+ ],
22
+ "author": "Saiful Islam",
23
+ "license": "MIT"
24
+ }
package/readme.md ADDED
@@ -0,0 +1,122 @@
1
+ # Cloudinary-Lite
2
+
3
+ A zero-dependency, ultra-lightweight utility library to handle **unsigned client-side uploads** and **signed server-side deletions** with Cloudinary. Works seamlessly in all modern browser environments (React, Angular, Vue, Svelte, Vanilla JS) and Node.js server runtimes.
4
+
5
+ ---
6
+
7
+ ## Installation
8
+
9
+ Install the package via NPM:
10
+
11
+ ```bash
12
+ npm install cloudinary-lite
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Credential Configuration
18
+
19
+ ### 1. Client-Side Upload (Public Credentials)
20
+ For public client-side uploading, only the public **Cloud Name** and **Upload Preset** are required. You should **never** expose your API Secret on the client side.
21
+
22
+ Define these in your frontend `.env` file (e.g. Vite format):
23
+ ```env
24
+ VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
25
+ VITE_CLOUDINARY_UPLOAD_PRESET=your_unsigned_preset
26
+ ```
27
+
28
+ ### 2. Server-Side Deletion (Secret Credentials)
29
+ For server-side deletions, you need your private **API Key** and **API Secret** to sign the deletion requests securely.
30
+
31
+ Define these in your server environment variables or `.env` file:
32
+ ```env
33
+ CLOUDINARY_CLOUD_NAME=your_cloud_name
34
+ CLOUDINARY_API_KEY=your_api_key
35
+ CLOUDINARY_API_SECRET=your_api_secret
36
+ ```
37
+
38
+ ---
39
+
40
+ ## API Reference
41
+
42
+ ### 1. Unsigned Upload (Client-Side)
43
+
44
+ Upload files directly from the browser to Cloudinary.
45
+
46
+ ```javascript
47
+ import { uploadUnsigned } from 'cloudinary-lite';
48
+
49
+ async function handleFileChange(event) {
50
+ const file = event.target.files[0];
51
+ if (!file) return;
52
+
53
+ try {
54
+ const secureUrl = await uploadUnsigned({
55
+ file,
56
+ cloudName: import.meta.env.VITE_CLOUDINARY_CLOUD_NAME,
57
+ uploadPreset: import.meta.env.VITE_CLOUDINARY_UPLOAD_PRESET,
58
+ folder: 'alumni/photos' // Optional folder path
59
+ });
60
+
61
+ console.log('Uploaded successfully! URL:', secureUrl);
62
+ } catch (error) {
63
+ console.error('Upload failed:', error.message);
64
+ }
65
+ }
66
+ ```
67
+
68
+ ---
69
+
70
+ ### 2. Signed Deletion (Server-Side / Node.js)
71
+
72
+ Delete assets securely from a backend server. This function dynamically imports Node's native `crypto` module to securely sign requests.
73
+
74
+ ```javascript
75
+ import { deleteSigned } from 'cloudinary-lite';
76
+
77
+ async function deleteUserAsset(publicId, resourceType) {
78
+ try {
79
+ const success = await deleteSigned({
80
+ publicId,
81
+ resourceType, // 'image', 'raw', or 'video'
82
+ cloudName: process.env.CLOUDINARY_CLOUD_NAME,
83
+ apiKey: process.env.CLOUDINARY_API_KEY,
84
+ apiSecret: process.env.CLOUDINARY_API_SECRET
85
+ });
86
+
87
+ if (success) {
88
+ console.log('Asset deleted successfully');
89
+ } else {
90
+ console.warn('Asset deletion did not return success state');
91
+ }
92
+ } catch (error) {
93
+ console.error('Failed to delete asset:', error.message);
94
+ }
95
+ }
96
+ ```
97
+
98
+ ---
99
+
100
+ ### 3. Parse Cloudinary URLs (Universal Helper)
101
+
102
+ Extract the `public_id` and the `resource_type` from a complete secure URL. Useful when you only store the file URL in the database and need to resolve it for deletion.
103
+
104
+ ```javascript
105
+ import { parseCloudinaryUrl } from 'cloudinary-lite';
106
+
107
+ const url = 'https://res.cloudinary.com/de3t60fd7/image/upload/v1722513904/alumni/profile-photos/abc123xyz.jpg';
108
+ const parsed = parseCloudinaryUrl(url);
109
+
110
+ console.log(parsed);
111
+ // Output:
112
+ // {
113
+ // public_id: "alumni/profile-photos/abc123xyz",
114
+ // resource_type: "image"
115
+ // }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## License
121
+
122
+ MIT