@sanvika/cloudinary 0.1.3 → 0.2.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.
- package/dist/index.js +297 -29
- package/package.json +7 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/cloudinaryCore.js
|
|
2
|
-
import { v2 as cloudinary } from "cloudinary";
|
|
3
|
-
|
|
4
1
|
// src/cloudinaryErrors.js
|
|
5
2
|
var CloudinaryError = class extends Error {
|
|
6
3
|
constructor(message, operation, details = {}, originalError = null) {
|
|
@@ -111,46 +108,166 @@ var TRANSFORM_PRESETS = {
|
|
|
111
108
|
responsive: { w: "auto", dpr: "auto", q: "auto", f: "auto" }
|
|
112
109
|
};
|
|
113
110
|
|
|
111
|
+
// src/gatewayClient.js
|
|
112
|
+
function isProxyMode() {
|
|
113
|
+
return Boolean(process.env.SANVIKA_CLOUDINARY_GATEWAY_URL && process.env.SANVIKA_CLOUDINARY_TOKEN);
|
|
114
|
+
}
|
|
115
|
+
function requireProxyEnv() {
|
|
116
|
+
const base = process.env.SANVIKA_CLOUDINARY_GATEWAY_URL;
|
|
117
|
+
const token = process.env.SANVIKA_CLOUDINARY_TOKEN;
|
|
118
|
+
if (!base || !token) {
|
|
119
|
+
throw new CloudinaryError(
|
|
120
|
+
"Proxy gateway env missing. Set SANVIKA_CLOUDINARY_GATEWAY_URL and SANVIKA_CLOUDINARY_TOKEN.",
|
|
121
|
+
"gateway_env"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return { base: base.replace(/\/+$/, ""), token };
|
|
125
|
+
}
|
|
126
|
+
async function handleResponse(operation, res) {
|
|
127
|
+
let body = null;
|
|
128
|
+
const text = await res.text();
|
|
129
|
+
try {
|
|
130
|
+
body = text ? JSON.parse(text) : null;
|
|
131
|
+
} catch {
|
|
132
|
+
body = { raw: text };
|
|
133
|
+
}
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const msg = (body == null ? void 0 : body.error) || (body == null ? void 0 : body.message) || `Gateway returned ${res.status}`;
|
|
136
|
+
throw new CloudinaryError(msg, operation, { status: res.status, body });
|
|
137
|
+
}
|
|
138
|
+
return body;
|
|
139
|
+
}
|
|
140
|
+
async function gatewayPost(pathname, jsonBody) {
|
|
141
|
+
const { base, token } = requireProxyEnv();
|
|
142
|
+
const res = await fetch(`${base}${pathname}`, {
|
|
143
|
+
method: "POST",
|
|
144
|
+
headers: {
|
|
145
|
+
Authorization: `Bearer ${token}`,
|
|
146
|
+
"Content-Type": "application/json"
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify(jsonBody || {})
|
|
149
|
+
});
|
|
150
|
+
return handleResponse(`POST ${pathname}`, res);
|
|
151
|
+
}
|
|
152
|
+
async function gatewayGet(pathname, query) {
|
|
153
|
+
const { base, token } = requireProxyEnv();
|
|
154
|
+
const qs = query ? `?${new URLSearchParams(query).toString()}` : "";
|
|
155
|
+
const res = await fetch(`${base}${pathname}${qs}`, {
|
|
156
|
+
method: "GET",
|
|
157
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
158
|
+
});
|
|
159
|
+
return handleResponse(`GET ${pathname}`, res);
|
|
160
|
+
}
|
|
161
|
+
async function gatewayUpload(bufferOrBlob, options = {}) {
|
|
162
|
+
const { base, token } = requireProxyEnv();
|
|
163
|
+
const form = new FormData();
|
|
164
|
+
let blob;
|
|
165
|
+
if (bufferOrBlob instanceof Blob) {
|
|
166
|
+
blob = bufferOrBlob;
|
|
167
|
+
} else if (Buffer.isBuffer(bufferOrBlob)) {
|
|
168
|
+
blob = new Blob([bufferOrBlob]);
|
|
169
|
+
} else if (typeof bufferOrBlob === "string") {
|
|
170
|
+
return gatewayPost("/api/v1/upload?mode=source", { source: bufferOrBlob, options });
|
|
171
|
+
} else {
|
|
172
|
+
throw new CloudinaryError("Unsupported upload input", "upload", { type: typeof bufferOrBlob });
|
|
173
|
+
}
|
|
174
|
+
form.append("file", blob, options.filename || "asset");
|
|
175
|
+
form.append("options", JSON.stringify(options || {}));
|
|
176
|
+
const res = await fetch(`${base}/api/v1/upload`, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
179
|
+
body: form
|
|
180
|
+
});
|
|
181
|
+
return handleResponse("upload", res);
|
|
182
|
+
}
|
|
183
|
+
|
|
114
184
|
// src/cloudinaryCore.js
|
|
115
185
|
var _appName = null;
|
|
116
186
|
var _configured = false;
|
|
117
|
-
|
|
118
|
-
|
|
187
|
+
var _cloudinary = null;
|
|
188
|
+
async function loadLegacyCloudinary() {
|
|
189
|
+
if (_cloudinary) return _cloudinary;
|
|
190
|
+
const mod = await import("cloudinary");
|
|
191
|
+
_cloudinary = mod.v2;
|
|
192
|
+
return _cloudinary;
|
|
193
|
+
}
|
|
194
|
+
async function configureSanvikaCloudinary({ appName, cloudName, apiKey, apiSecret } = {}) {
|
|
195
|
+
const resolvedApp = appName || process.env.SANVIKA_CLOUDINARY_APP_NAME;
|
|
196
|
+
if (!resolvedApp) throw new Error("appName is required for configureSanvikaCloudinary");
|
|
197
|
+
_appName = resolvedApp;
|
|
198
|
+
if (isProxyMode()) {
|
|
199
|
+
_configured = true;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
119
202
|
const cn = cloudName || process.env.CLOUDINARY_CLOUD_NAME || process.env.CLOUDINARY_NAME;
|
|
120
203
|
const ak = apiKey || process.env.CLOUDINARY_API_KEY;
|
|
121
204
|
const as = apiSecret || process.env.CLOUDINARY_API_SECRET;
|
|
122
205
|
if (!cn || !ak || !as) {
|
|
123
|
-
throw new Error(
|
|
206
|
+
throw new Error(
|
|
207
|
+
"Cloudinary credentials missing. Either use proxy mode (SANVIKA_CLOUDINARY_GATEWAY_URL + SANVIKA_CLOUDINARY_TOKEN) or legacy env (CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, CLOUDINARY_API_SECRET)."
|
|
208
|
+
);
|
|
124
209
|
}
|
|
210
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
125
211
|
cloudinary.config({ cloud_name: cn, api_key: ak, api_secret: as, secure: true });
|
|
126
|
-
_appName = appName;
|
|
127
212
|
_configured = true;
|
|
128
213
|
}
|
|
129
|
-
function ensureConfigured() {
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
throw new Error("Call configureSanvikaCloudinary({ appName }) before using SDK operations.");
|
|
136
|
-
}
|
|
214
|
+
async function ensureConfigured() {
|
|
215
|
+
if (_configured) return;
|
|
216
|
+
if (isProxyMode()) {
|
|
217
|
+
_appName = _appName || process.env.SANVIKA_CLOUDINARY_APP_NAME || "sanvika-app";
|
|
218
|
+
_configured = true;
|
|
219
|
+
return;
|
|
137
220
|
}
|
|
221
|
+
if (process.env.SANVIKA_CLOUDINARY_APP_NAME) {
|
|
222
|
+
await configureSanvikaCloudinary({ appName: process.env.SANVIKA_CLOUDINARY_APP_NAME });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
throw new Error("Call configureSanvikaCloudinary({ appName }) before using SDK operations.");
|
|
138
226
|
}
|
|
139
|
-
function getAppName() {
|
|
140
|
-
ensureConfigured();
|
|
227
|
+
async function getAppName() {
|
|
228
|
+
await ensureConfigured();
|
|
141
229
|
return _appName;
|
|
142
230
|
}
|
|
143
231
|
async function uploadImage(fileOrBuffer, options = {}) {
|
|
144
|
-
ensureConfigured();
|
|
232
|
+
await ensureConfigured();
|
|
145
233
|
const {
|
|
146
234
|
folder,
|
|
147
235
|
resourceType = "image",
|
|
148
236
|
tags = [],
|
|
237
|
+
eager,
|
|
149
238
|
transforms,
|
|
239
|
+
eagerAsync,
|
|
240
|
+
eagerNotificationUrl,
|
|
150
241
|
publicId,
|
|
151
242
|
overwrite = false,
|
|
152
|
-
notificationUrl
|
|
243
|
+
notificationUrl,
|
|
244
|
+
chunked,
|
|
245
|
+
chunkSize = 6 * 1024 * 1024,
|
|
246
|
+
timeout,
|
|
247
|
+
maxAttempts = 3
|
|
153
248
|
} = options;
|
|
249
|
+
const eagerValue = eager || transforms;
|
|
250
|
+
const uploadOptions = {
|
|
251
|
+
folder: folder || "",
|
|
252
|
+
resourceType,
|
|
253
|
+
tags: [_appName, ...tags],
|
|
254
|
+
eager: eagerValue,
|
|
255
|
+
eagerAsync,
|
|
256
|
+
eagerNotificationUrl,
|
|
257
|
+
publicId,
|
|
258
|
+
overwrite,
|
|
259
|
+
notificationUrl,
|
|
260
|
+
chunked,
|
|
261
|
+
chunkSize,
|
|
262
|
+
timeout
|
|
263
|
+
};
|
|
264
|
+
if (isProxyMode()) {
|
|
265
|
+
return withRetry(() => gatewayUpload(fileOrBuffer, uploadOptions), {
|
|
266
|
+
operationName: "uploadImage",
|
|
267
|
+
maxAttempts
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
154
271
|
const uploadFolder = getFolderPath(_appName, folder);
|
|
155
272
|
const uploadOpts = {
|
|
156
273
|
folder: uploadFolder,
|
|
@@ -159,22 +276,37 @@ async function uploadImage(fileOrBuffer, options = {}) {
|
|
|
159
276
|
tags: [_appName, ...tags]
|
|
160
277
|
};
|
|
161
278
|
if (publicId) uploadOpts.public_id = publicId;
|
|
162
|
-
if (
|
|
279
|
+
if (eagerValue) uploadOpts.eager = eagerValue;
|
|
280
|
+
if (eagerAsync) uploadOpts.eager_async = true;
|
|
281
|
+
if (eagerNotificationUrl) uploadOpts.eager_notification_url = eagerNotificationUrl;
|
|
163
282
|
if (notificationUrl) uploadOpts.notification_url = notificationUrl;
|
|
283
|
+
if (timeout) uploadOpts.timeout = timeout;
|
|
284
|
+
const fileSize = Buffer.isBuffer(fileOrBuffer) ? fileOrBuffer.length : null;
|
|
285
|
+
const useChunked = chunked || resourceType === "video" && fileSize !== null && fileSize > 10 * 1024 * 1024;
|
|
286
|
+
if (useChunked) uploadOpts.chunk_size = chunkSize;
|
|
164
287
|
return withRetry(
|
|
165
288
|
() => {
|
|
166
289
|
if (Buffer.isBuffer(fileOrBuffer)) {
|
|
167
290
|
return new Promise((resolve, reject) => {
|
|
168
|
-
const
|
|
291
|
+
const streamFactory = useChunked ? cloudinary.uploader.upload_chunked_stream : cloudinary.uploader.upload_stream;
|
|
292
|
+
const stream = streamFactory.call(cloudinary.uploader, uploadOpts, (err, result) => {
|
|
169
293
|
if (err) return reject(err);
|
|
170
294
|
resolve(result);
|
|
171
295
|
});
|
|
172
296
|
stream.end(fileOrBuffer);
|
|
173
297
|
});
|
|
174
298
|
}
|
|
299
|
+
if (useChunked) {
|
|
300
|
+
return new Promise((resolve, reject) => {
|
|
301
|
+
cloudinary.uploader.upload_large(fileOrBuffer, uploadOpts, (err, result) => {
|
|
302
|
+
if (err) return reject(err);
|
|
303
|
+
resolve(result);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
}
|
|
175
307
|
return cloudinary.uploader.upload(fileOrBuffer, uploadOpts);
|
|
176
308
|
},
|
|
177
|
-
{ operationName: "uploadImage", maxAttempts
|
|
309
|
+
{ operationName: "uploadImage", maxAttempts }
|
|
178
310
|
);
|
|
179
311
|
}
|
|
180
312
|
async function uploadVideo(fileOrBuffer, options = {}) {
|
|
@@ -185,11 +317,18 @@ async function uploadImages(files, options = {}) {
|
|
|
185
317
|
return Promise.all(files.map((file) => uploadImage(file, options)));
|
|
186
318
|
}
|
|
187
319
|
async function uploadRawFile(buffer, options = {}) {
|
|
188
|
-
ensureConfigured();
|
|
320
|
+
await ensureConfigured();
|
|
189
321
|
const { folder, filename, tags = [] } = options;
|
|
190
322
|
if (!buffer || !Buffer.isBuffer(buffer)) throw new Error("Buffer is required for uploadRawFile");
|
|
191
|
-
const uploadFolder = getFolderPath(_appName, folder);
|
|
192
323
|
const baseName = filename ? filename.replace(/\.[^.]+$/, "") : "file";
|
|
324
|
+
if (isProxyMode()) {
|
|
325
|
+
return withRetry(
|
|
326
|
+
() => gatewayUpload(buffer, { folder: folder || "", resourceType: "raw", tags: ["raw", _appName, ...tags], publicId: baseName, overwrite: true, filename }),
|
|
327
|
+
{ operationName: "uploadRawFile", maxAttempts: 3 }
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
331
|
+
const uploadFolder = getFolderPath(_appName, folder);
|
|
193
332
|
return withRetry(
|
|
194
333
|
() => new Promise((resolve, reject) => {
|
|
195
334
|
const stream = cloudinary.uploader.upload_stream(
|
|
@@ -211,8 +350,14 @@ async function uploadRawFile(buffer, options = {}) {
|
|
|
211
350
|
);
|
|
212
351
|
}
|
|
213
352
|
async function deleteImage(urlOrPublicId, options = {}) {
|
|
214
|
-
|
|
353
|
+
var _a;
|
|
354
|
+
await ensureConfigured();
|
|
215
355
|
const { resourceType = "image" } = options;
|
|
356
|
+
if (isProxyMode()) {
|
|
357
|
+
const result = await gatewayPost("/api/v1/delete", { urls: [urlOrPublicId], options: { resourceType } });
|
|
358
|
+
return ((_a = result == null ? void 0 : result.results) == null ? void 0 : _a[0]) || result;
|
|
359
|
+
}
|
|
360
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
216
361
|
const publicId = isCloudinaryUrl(urlOrPublicId) ? extractPublicId(urlOrPublicId) : urlOrPublicId;
|
|
217
362
|
if (!publicId) throw new CloudinaryError("Cannot extract public ID", "deleteImage", { urlOrPublicId });
|
|
218
363
|
return withRetry(
|
|
@@ -221,14 +366,22 @@ async function deleteImage(urlOrPublicId, options = {}) {
|
|
|
221
366
|
);
|
|
222
367
|
}
|
|
223
368
|
async function deleteImages(urls, options = {}) {
|
|
224
|
-
ensureConfigured();
|
|
369
|
+
await ensureConfigured();
|
|
225
370
|
const { fast = false, resourceType = "image" } = options;
|
|
226
|
-
if (!Array.isArray(urls) || urls.length === 0)
|
|
371
|
+
if (!Array.isArray(urls) || urls.length === 0) {
|
|
372
|
+
return { success: true, total: 0, successful: 0, failed: 0 };
|
|
373
|
+
}
|
|
374
|
+
if (isProxyMode()) {
|
|
375
|
+
return gatewayPost("/api/v1/delete", { urls, options: { fast, resourceType } });
|
|
376
|
+
}
|
|
377
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
227
378
|
const BATCH_SIZE = fast ? 15 : 10;
|
|
228
379
|
const DELAY = fast ? 100 : 500;
|
|
229
380
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
230
381
|
const publicIds = urls.map((u) => isCloudinaryUrl(u) ? extractPublicId(u) : u).filter(Boolean);
|
|
231
|
-
if (publicIds.length === 0)
|
|
382
|
+
if (publicIds.length === 0) {
|
|
383
|
+
return { success: true, total: urls.length, successful: 0, failed: 0, invalidUrls: urls.length };
|
|
384
|
+
}
|
|
232
385
|
const allResults = [];
|
|
233
386
|
for (let i = 0; i < publicIds.length; i += BATCH_SIZE) {
|
|
234
387
|
const batch = publicIds.slice(i, i + BATCH_SIZE);
|
|
@@ -250,9 +403,115 @@ async function deleteImages(urls, options = {}) {
|
|
|
250
403
|
return { success: failed === 0, total: urls.length, processed: allResults.length, successful, failed };
|
|
251
404
|
}
|
|
252
405
|
async function pingCloudinary() {
|
|
253
|
-
ensureConfigured();
|
|
406
|
+
await ensureConfigured();
|
|
407
|
+
if (isProxyMode()) return gatewayGet("/api/v1/ping");
|
|
408
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
254
409
|
return cloudinary.api.ping();
|
|
255
410
|
}
|
|
411
|
+
async function getCloudinaryUsage(options = {}) {
|
|
412
|
+
await ensureConfigured();
|
|
413
|
+
const { resourceType } = options;
|
|
414
|
+
if (isProxyMode()) return gatewayGet("/api/v1/usage", resourceType ? { resourceType } : void 0);
|
|
415
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
416
|
+
return cloudinary.api.usage(resourceType ? { resource_type: resourceType } : {});
|
|
417
|
+
}
|
|
418
|
+
async function getCloudinarySdkVersion() {
|
|
419
|
+
if (isProxyMode()) return "gateway";
|
|
420
|
+
const cloudinary = await loadLegacyCloudinary();
|
|
421
|
+
return cloudinary.CLOUDINARY_VERSION;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/cloudinaryDiagnostics.js
|
|
425
|
+
import crypto from "crypto";
|
|
426
|
+
function testCloudinaryWebhookSignature(apiSecret, options = {}) {
|
|
427
|
+
try {
|
|
428
|
+
if (!apiSecret) return { success: false, error: "apiSecret is required" };
|
|
429
|
+
const testPayload = { test: "data", timestamp: Math.floor(Date.now() / 1e3) };
|
|
430
|
+
const testPayloadString = JSON.stringify(testPayload);
|
|
431
|
+
const testTimestamp = testPayload.timestamp.toString();
|
|
432
|
+
const signatureData = testTimestamp + testPayloadString;
|
|
433
|
+
const signature = crypto.createHmac("sha1", apiSecret).update(signatureData).digest("hex");
|
|
434
|
+
return {
|
|
435
|
+
success: true,
|
|
436
|
+
testPayload,
|
|
437
|
+
testTimestamp,
|
|
438
|
+
signature,
|
|
439
|
+
headers: { "X-Cld-Timestamp": testTimestamp, "X-Cld-Signature": signature },
|
|
440
|
+
testEndpoint: options.testEndpoint || "/api/webhooks/cloudinary/debug"
|
|
441
|
+
};
|
|
442
|
+
} catch (error) {
|
|
443
|
+
return { success: false, error: error.message };
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
async function runCloudinaryDiagnostics(options = {}) {
|
|
447
|
+
if (isProxyMode()) {
|
|
448
|
+
return gatewayPost("/api/v1/diagnostics", options || {});
|
|
449
|
+
}
|
|
450
|
+
const { testWebhook = false, webhookSecret, testFolder = "diagnostics" } = options;
|
|
451
|
+
const report = {
|
|
452
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
453
|
+
cloudinaryVersion: await getCloudinarySdkVersion(),
|
|
454
|
+
nodeVersion: process.version,
|
|
455
|
+
mode: "legacy",
|
|
456
|
+
configuration: {
|
|
457
|
+
cloudName: process.env.CLOUDINARY_CLOUD_NAME || process.env.CLOUDINARY_NAME ? "set" : "missing",
|
|
458
|
+
apiKey: process.env.CLOUDINARY_API_KEY ? "set" : "missing",
|
|
459
|
+
apiSecret: process.env.CLOUDINARY_API_SECRET ? "set" : "missing",
|
|
460
|
+
uploadPreset: process.env.CLOUDINARY_UPLOAD_PRESET ? "set" : "missing"
|
|
461
|
+
},
|
|
462
|
+
tests: {}
|
|
463
|
+
};
|
|
464
|
+
report.tests.ping = await pingCloudinary().then(
|
|
465
|
+
(result) => ({ success: true, result }),
|
|
466
|
+
(error) => ({ success: false, error: error.message })
|
|
467
|
+
);
|
|
468
|
+
const testBuffer = Buffer.from("Cloudinary diagnostic test");
|
|
469
|
+
report.tests.upload = await uploadImage(testBuffer, {
|
|
470
|
+
folder: testFolder,
|
|
471
|
+
resourceType: "raw",
|
|
472
|
+
tags: ["test", "diagnostics"],
|
|
473
|
+
publicId: `diagnostic_${Date.now()}`,
|
|
474
|
+
overwrite: true
|
|
475
|
+
}).then(
|
|
476
|
+
(result) => ({ success: true, publicId: result.public_id, url: result.secure_url, bytes: result.bytes }),
|
|
477
|
+
(error) => ({ success: false, error: error.message, httpCode: error.http_code, name: error.name })
|
|
478
|
+
);
|
|
479
|
+
report.tests.videoLimits = await getCloudinaryUsage({ resourceType: "video" }).then(
|
|
480
|
+
(result) => ({ success: true, result }),
|
|
481
|
+
(error) => ({ success: false, error: error.message })
|
|
482
|
+
);
|
|
483
|
+
if (testWebhook) {
|
|
484
|
+
report.tests.webhook = testCloudinaryWebhookSignature(webhookSecret);
|
|
485
|
+
}
|
|
486
|
+
return report;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/webhookSignature.js
|
|
490
|
+
import crypto2 from "crypto";
|
|
491
|
+
function verifySanvikaWebhookSignature({ headers, rawBody, secret, toleranceSec = 300 } = {}) {
|
|
492
|
+
const getHeader = (name) => {
|
|
493
|
+
if (!headers) return "";
|
|
494
|
+
if (typeof headers.get === "function") return headers.get(name) || headers.get(name.toLowerCase()) || "";
|
|
495
|
+
return headers[name] || headers[name.toLowerCase()] || "";
|
|
496
|
+
};
|
|
497
|
+
const sig = String(getHeader("x-sanvika-signature") || "").trim();
|
|
498
|
+
const ts = String(getHeader("x-sanvika-timestamp") || "").trim();
|
|
499
|
+
const token = secret || process.env.SANVIKA_CLOUDINARY_TOKEN;
|
|
500
|
+
if (!token) return { valid: false, reason: "missing_secret" };
|
|
501
|
+
if (!sig) return { valid: false, reason: "missing_signature" };
|
|
502
|
+
if (!ts) return { valid: false, reason: "missing_timestamp" };
|
|
503
|
+
const tsNum = Number(ts);
|
|
504
|
+
if (!Number.isFinite(tsNum)) return { valid: false, reason: "invalid_timestamp" };
|
|
505
|
+
const nowSec = Math.floor(Date.now() / 1e3);
|
|
506
|
+
if (Math.abs(nowSec - tsNum) > toleranceSec) return { valid: false, reason: "stale_timestamp" };
|
|
507
|
+
const body = Buffer.isBuffer(rawBody) ? rawBody.toString("utf8") : String(rawBody || "");
|
|
508
|
+
const expected = crypto2.createHmac("sha256", token).update(ts + body).digest("hex");
|
|
509
|
+
const a = Buffer.from(sig, "hex");
|
|
510
|
+
const b = Buffer.from(expected, "hex");
|
|
511
|
+
if (a.length !== b.length) return { valid: false, reason: "length_mismatch" };
|
|
512
|
+
const ok = crypto2.timingSafeEqual(a, b);
|
|
513
|
+
return { valid: ok, reason: ok ? void 0 : "signature_mismatch" };
|
|
514
|
+
}
|
|
256
515
|
export {
|
|
257
516
|
CloudinaryError,
|
|
258
517
|
TRANSFORM_PRESETS,
|
|
@@ -260,16 +519,25 @@ export {
|
|
|
260
519
|
deleteImage,
|
|
261
520
|
deleteImages,
|
|
262
521
|
extractPublicId,
|
|
522
|
+
gatewayGet,
|
|
523
|
+
gatewayPost,
|
|
524
|
+
gatewayUpload,
|
|
263
525
|
getAppName,
|
|
526
|
+
getCloudinarySdkVersion,
|
|
527
|
+
getCloudinaryUsage,
|
|
264
528
|
getFolderPath,
|
|
265
529
|
getOptimizedUrl,
|
|
266
530
|
isCloudinaryUrl,
|
|
531
|
+
isProxyMode,
|
|
267
532
|
isRetriableError,
|
|
268
533
|
pingCloudinary,
|
|
534
|
+
runCloudinaryDiagnostics,
|
|
535
|
+
testCloudinaryWebhookSignature,
|
|
269
536
|
uploadImage,
|
|
270
537
|
uploadImages,
|
|
271
538
|
uploadRawFile,
|
|
272
539
|
uploadVideo,
|
|
273
540
|
validatePublicId,
|
|
541
|
+
verifySanvikaWebhookSignature,
|
|
274
542
|
withRetry
|
|
275
543
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanvika/cloudinary",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Centralized Cloudinary SDK for the Sanvika ecosystem —
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Centralized Cloudinary SDK for the Sanvika ecosystem — proxy gateway mode (zero credentials) + legacy direct mode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -34,7 +34,8 @@
|
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=18.0.0",
|
|
37
|
-
"react-dom": ">=18.0.0"
|
|
37
|
+
"react-dom": ">=18.0.0",
|
|
38
|
+
"cloudinary": "^2.5.0"
|
|
38
39
|
},
|
|
39
40
|
"peerDependenciesMeta": {
|
|
40
41
|
"react": {
|
|
@@ -42,6 +43,9 @@
|
|
|
42
43
|
},
|
|
43
44
|
"react-dom": {
|
|
44
45
|
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"cloudinary": {
|
|
48
|
+
"optional": true
|
|
45
49
|
}
|
|
46
50
|
},
|
|
47
51
|
"engines": {
|