blaaiz-nodejs-sdk 1.1.0 → 1.1.1

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/README.md CHANGED
@@ -128,7 +128,7 @@ const result = await blaaiz.customers.uploadFileComplete('customer-id', {
128
128
  file: fileBuffer, // Buffer or Uint8Array
129
129
  file_category: 'identity', // identity, proof_of_address, liveness_check
130
130
  filename: 'passport.jpg', // Optional
131
- contentType: 'image/jpeg' // Optional
131
+ content_type: 'image/jpeg' // Optional
132
132
  });
133
133
 
134
134
  // Option B: Upload from Base64 string
package/index.d.ts CHANGED
@@ -52,6 +52,8 @@ export interface FileUploadOptions {
52
52
  file: Buffer | Uint8Array | string;
53
53
  file_category: 'identity' | 'proof_of_address' | 'liveness_check';
54
54
  filename?: string;
55
+ content_type?: string;
56
+ /** @deprecated Use content_type instead */
55
57
  contentType?: string;
56
58
  }
57
59
 
@@ -331,7 +333,7 @@ export declare class Blaaiz {
331
333
 
332
334
  testConnection(): Promise<boolean>;
333
335
 
334
- createCompleteePayout(payoutConfig: {
336
+ createCompletePayout(payoutConfig: {
335
337
  customerData?: CustomerData;
336
338
  payoutData: PayoutData;
337
339
  }): Promise<{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blaaiz-nodejs-sdk",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Official Node.js SDK for Blaaiz RaaS (Remittance as a Service) API",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
package/src/index.js CHANGED
@@ -39,7 +39,7 @@ class Blaaiz {
39
39
  }
40
40
  }
41
41
 
42
- async createCompleteePayout (payoutConfig) {
42
+ async createCompletePayout (payoutConfig) {
43
43
  const { customerData, payoutData } = payoutConfig
44
44
 
45
45
  try {
@@ -84,7 +84,8 @@ class CustomerService {
84
84
  }
85
85
 
86
86
  const { file, file_category } = fileOptions // eslint-disable-line camelcase
87
- let { filename, contentType } = fileOptions
87
+ let { filename } = fileOptions
88
+ let contentType = fileOptions.content_type || fileOptions.contentType // eslint-disable-line camelcase
88
89
 
89
90
  if (!file) {
90
91
  throw new Error('File is required')
@@ -129,6 +130,15 @@ class CustomerService {
129
130
  if (file.startsWith('data:')) {
130
131
  // Handle data URL format: data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAX/xAAUEQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIRAxEAPwCdABmX/9k=
131
132
  const base64Data = file.split(',')[1]
133
+ if (!base64Data || !base64Data.trim()) {
134
+ throw new Error('Invalid data URL: no base64 data found after the comma')
135
+ }
136
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data.trim())) {
137
+ throw new Error(
138
+ 'The base64 portion of the data URL does not appear to be valid base64. ' +
139
+ 'Ensure the string after the comma contains only valid base64 characters.'
140
+ )
141
+ }
132
142
  fileBuffer = Buffer.from(base64Data, 'base64')
133
143
 
134
144
  // Extract content type from data URL if not provided
@@ -154,6 +164,15 @@ class CustomerService {
154
164
  }
155
165
  } else {
156
166
  // Handle plain base64 string
167
+ if (!file.trim()) {
168
+ throw new Error('The file string is empty')
169
+ }
170
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(file.trim())) {
171
+ throw new Error(
172
+ 'The file string does not appear to be valid base64. ' +
173
+ 'If you meant to pass a file path, Buffer, or URL, use the appropriate format instead.'
174
+ )
175
+ }
157
176
  fileBuffer = Buffer.from(file, 'base64')
158
177
  }
159
178
  } else {
@@ -161,6 +180,19 @@ class CustomerService {
161
180
  fileBuffer = Buffer.from(file)
162
181
  }
163
182
 
183
+ // Auto-detect content type if not provided
184
+ if (!contentType) {
185
+ contentType = this._detectContentTypeFromBytes(fileBuffer)
186
+ }
187
+ if (!contentType && filename) {
188
+ contentType = this._getContentTypeFromFilename(filename)
189
+ }
190
+ if (!contentType) {
191
+ throw new Error(
192
+ 'Could not determine file content type. Please provide a content_type (e.g., "image/jpeg", "image/png", "application/pdf") in fileOptions.'
193
+ )
194
+ }
195
+
164
196
  await this._uploadToS3(presignedUrl, fileBuffer, contentType, filename)
165
197
 
166
198
  // Map file category to the correct field name expected by Laravel API
@@ -353,6 +385,66 @@ class CustomerService {
353
385
  })
354
386
  }
355
387
 
388
+ _detectContentTypeFromBytes (buffer) {
389
+ if (!buffer || buffer.length < 4) return null
390
+
391
+ // JPEG: FF D8 FF
392
+ if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF) {
393
+ return 'image/jpeg'
394
+ }
395
+ // PNG: 89 50 4E 47
396
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47) {
397
+ return 'image/png'
398
+ }
399
+ // GIF: 47 49 46 38
400
+ if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x38) {
401
+ return 'image/gif'
402
+ }
403
+ // PDF: 25 50 44 46 (%PDF)
404
+ if (buffer[0] === 0x25 && buffer[1] === 0x50 && buffer[2] === 0x44 && buffer[3] === 0x46) {
405
+ return 'application/pdf'
406
+ }
407
+ // WEBP: starts with RIFF....WEBP
408
+ if (buffer.length >= 12 &&
409
+ buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
410
+ buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50) {
411
+ return 'image/webp'
412
+ }
413
+ // BMP: 42 4D
414
+ if (buffer[0] === 0x42 && buffer[1] === 0x4D) {
415
+ return 'image/bmp'
416
+ }
417
+ // TIFF: 49 49 2A 00 (little-endian) or 4D 4D 00 2A (big-endian)
418
+ if ((buffer[0] === 0x49 && buffer[1] === 0x49 && buffer[2] === 0x2A && buffer[3] === 0x00) ||
419
+ (buffer[0] === 0x4D && buffer[1] === 0x4D && buffer[2] === 0x00 && buffer[3] === 0x2A)) {
420
+ return 'image/tiff'
421
+ }
422
+
423
+ return null
424
+ }
425
+
426
+ _getContentTypeFromFilename (filename) {
427
+ if (!filename) return null
428
+
429
+ const ext = filename.toLowerCase().split('.').pop()
430
+ const extToMime = {
431
+ jpg: 'image/jpeg',
432
+ jpeg: 'image/jpeg',
433
+ png: 'image/png',
434
+ gif: 'image/gif',
435
+ webp: 'image/webp',
436
+ bmp: 'image/bmp',
437
+ tiff: 'image/tiff',
438
+ tif: 'image/tiff',
439
+ pdf: 'application/pdf',
440
+ txt: 'text/plain',
441
+ doc: 'application/msword',
442
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
443
+ }
444
+
445
+ return extToMime[ext] || null
446
+ }
447
+
356
448
  _getExtensionFromContentType (contentType) {
357
449
  const mimeToExt = {
358
450
  'image/jpeg': '.jpg',