n8n-nodes-binary-to-url 0.2.1 → 0.2.3

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.
@@ -66,7 +66,7 @@ export declare class MemoryStorage {
66
66
  * Format: {timestamp}-{16-char-hex}
67
67
  */
68
68
  static generateFileKey(): string;
69
- static upload(workflowId: string, data: Buffer, contentType: string, ttl?: number): Promise<{
69
+ static upload(workflowId: string, data: Buffer, contentType: string, fileName: string, ttl?: number): Promise<{
70
70
  fileKey: string;
71
71
  contentType: string;
72
72
  }>;
@@ -74,6 +74,7 @@ export declare class MemoryStorage {
74
74
  static download(workflowId: string, fileKey: string): Promise<{
75
75
  data: Buffer;
76
76
  contentType: string;
77
+ fileName: string;
77
78
  } | null>;
78
79
  static delete(workflowId: string, fileKey: string): Promise<boolean>;
79
80
  static cleanupWorkflowExpired(workflowId: string): void;
@@ -148,7 +148,7 @@ class MemoryStorage {
148
148
  static generateFileKey() {
149
149
  return (0, crypto_1.randomUUID)();
150
150
  }
151
- static async upload(workflowId, data, contentType, ttl) {
151
+ static async upload(workflowId, data, contentType, fileName, ttl) {
152
152
  // Wait for any existing upload for this workflow to complete
153
153
  const existingLock = this.uploadLocks.get(workflowId);
154
154
  if (existingLock) {
@@ -165,7 +165,7 @@ class MemoryStorage {
165
165
  // Create new lock for this upload
166
166
  const lockPromise = (async () => {
167
167
  try {
168
- return await this.uploadInternal(workflowId, data, contentType, ttl);
168
+ return await this.uploadInternal(workflowId, data, contentType, fileName, ttl);
169
169
  }
170
170
  finally {
171
171
  // Release lock
@@ -175,7 +175,7 @@ class MemoryStorage {
175
175
  this.uploadLocks.set(workflowId, lockPromise);
176
176
  return lockPromise;
177
177
  }
178
- static async uploadInternal(workflowId, data, contentType, ttl) {
178
+ static async uploadInternal(workflowId, data, contentType, fileName, ttl) {
179
179
  const fileKey = this.generateFileKey();
180
180
  const now = Date.now();
181
181
  const expiresAt = now + (ttl || this.DEFAULT_TTL);
@@ -184,6 +184,7 @@ class MemoryStorage {
184
184
  const file = {
185
185
  data,
186
186
  contentType,
187
+ fileName,
187
188
  uploadedAt: now,
188
189
  expiresAt,
189
190
  };
@@ -235,6 +236,7 @@ class MemoryStorage {
235
236
  return {
236
237
  data: file.data,
237
238
  contentType: file.contentType,
239
+ fileName: file.fileName,
238
240
  };
239
241
  }
240
242
  static async delete(workflowId, fileKey) {
@@ -90,9 +90,10 @@ class BinaryToUrl {
90
90
  }
91
91
  // Return binary file directly
92
92
  const isDownload = constants_js_1.DOWNLOAD_MIME_TYPES.includes(result.contentType);
93
- const disposition = isDownload
93
+ const dispositionType = isDownload
94
94
  ? constants_js_1.HTTP_HEADERS.DISPOSITION_ATTACHMENT
95
95
  : constants_js_1.HTTP_HEADERS.DISPOSITION_INLINE;
96
+ const disposition = `${dispositionType}; filename="${result.fileName}"`;
96
97
  // Get origin from headers for CORS
97
98
  const headers = this.getHeaderData();
98
99
  const origin = headers?.origin;
@@ -213,8 +214,24 @@ async function handleUpload(context, items) {
213
214
  const binaryData = context.helpers.assertBinaryData(i, binaryPropertyName);
214
215
  buffer = await context.helpers.getBinaryDataBuffer(i, binaryPropertyName);
215
216
  contentType = binaryData.mimeType || 'application/octet-stream';
216
- const ext = item.json?.fileExtension;
217
- fileExtension = typeof ext === 'string' ? ext : (contentType.split('/')[1] || '');
217
+ // Auto-detect file extension:
218
+ // 1. From original filename in binary data
219
+ // 2. From MIME type
220
+ if (binaryData.fileName && typeof binaryData.fileName === 'string') {
221
+ const lastDot = binaryData.fileName.lastIndexOf('.');
222
+ if (lastDot > 0 && lastDot < binaryData.fileName.length - 1) {
223
+ fileExtension = binaryData.fileName.slice(lastDot + 1);
224
+ debugLog(context.logger, `Extracted extension from filename ${binaryData.fileName}: ${fileExtension}`);
225
+ }
226
+ else {
227
+ fileExtension = contentType.split('/')[1] || '';
228
+ debugLog(context.logger, `No extension in filename, using MIME type: ${fileExtension}`);
229
+ }
230
+ }
231
+ else {
232
+ fileExtension = contentType.split('/')[1] || '';
233
+ debugLog(context.logger, `No filename available, using MIME type: ${fileExtension}`);
234
+ }
218
235
  debugLog(context.logger, `Got binary data from property: size=${buffer.length}, mimeType=${contentType}, extension=${fileExtension}`);
219
236
  }
220
237
  catch {
@@ -226,8 +243,24 @@ async function handleUpload(context, items) {
226
243
  const stream = await context.helpers.getBinaryStream(fileId);
227
244
  buffer = await context.helpers.binaryToBuffer(stream);
228
245
  contentType = String(item.json?.mimeType || 'application/octet-stream');
229
- const ext = item.json?.fileExtension;
230
- fileExtension = typeof ext === 'string' ? ext : (contentType.split('/')[1] || '');
246
+ // Auto-detect file extension:
247
+ // 1. From original filename in item data
248
+ // 2. From MIME type
249
+ if (item.json?.fileName && typeof item.json.fileName === 'string') {
250
+ const lastDot = item.json.fileName.lastIndexOf('.');
251
+ if (lastDot > 0 && lastDot < item.json.fileName.length - 1) {
252
+ fileExtension = item.json.fileName.slice(lastDot + 1);
253
+ debugLog(context.logger, `Extracted extension from filename ${item.json.fileName}: ${fileExtension}`);
254
+ }
255
+ else {
256
+ fileExtension = contentType.split('/')[1] || '';
257
+ debugLog(context.logger, `No extension in filename, using MIME type: ${fileExtension}`);
258
+ }
259
+ }
260
+ else {
261
+ fileExtension = contentType.split('/')[1] || '';
262
+ debugLog(context.logger, `No filename available, using MIME type: ${fileExtension}`);
263
+ }
231
264
  debugLog(context.logger, `File fetched via getBinaryStream: size=${buffer.length}, mimeType=${contentType}, extension=${fileExtension}`);
232
265
  }
233
266
  catch (streamError) {
@@ -248,11 +281,12 @@ async function handleUpload(context, items) {
248
281
  if (fileSize > constants_js_1.CACHE_LIMITS.MAX_FILE_SIZE) {
249
282
  throw new n8n_workflow_1.NodeOperationError(context.getNode(), `File size exceeds maximum limit of ${constants_js_1.CACHE_LIMITS.MAX_FILE_SIZE / 1024 / 1024}MB`);
250
283
  }
251
- // Upload to memory storage
252
- const result = await MemoryStorage_js_1.MemoryStorage.upload(workflowId, buffer, contentType, ttlMs);
253
- const proxyUrl = `${webhookUrlBase}?fileKey=${result.fileKey}`;
254
284
  // Generate random filename with extension
255
285
  const randomFileName = fileExtension ? `${(0, crypto_1.randomUUID)()}.${fileExtension}` : (0, crypto_1.randomUUID)();
286
+ debugLog(context.logger, `Generated random filename: ${randomFileName}`);
287
+ // Upload to memory storage
288
+ const result = await MemoryStorage_js_1.MemoryStorage.upload(workflowId, buffer, contentType, randomFileName, ttlMs);
289
+ const proxyUrl = `${webhookUrlBase}?fileKey=${result.fileKey}`;
256
290
  debugLog(context.logger, `File uploaded: fileKey=${result.fileKey}, fileName=${randomFileName}`);
257
291
  returnData.push({
258
292
  json: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-binary-to-url",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "n8n community node for creating temporary URLs for binary files within workflow execution",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",