s3-client-dtb 0.0.1 → 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.
- package/README.md +527 -147
- package/dist/client/index.d.ts +74 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +125 -3
- package/dist/client/index.js.map +1 -1
- package/dist/client/local-storage-client.d.ts +97 -0
- package/dist/client/local-storage-client.d.ts.map +1 -0
- package/dist/client/local-storage-client.js +108 -0
- package/dist/client/local-storage-client.js.map +1 -0
- package/dist/client/storage-client.d.ts +28 -3
- package/dist/client/storage-client.d.ts.map +1 -1
- package/dist/client/storage-client.js +270 -27
- package/dist/client/storage-client.js.map +1 -1
- package/dist/config/config-reader.d.ts +67 -0
- package/dist/config/config-reader.d.ts.map +1 -0
- package/dist/config/config-reader.js +201 -0
- package/dist/config/config-reader.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -4
- package/dist/index.js.map +1 -1
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +11 -6
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Almacena archivos directamente en el sistema de archivos de tu servidor. **Ideal
|
|
|
13
13
|
- Cuando no necesitas un backend separado
|
|
14
14
|
|
|
15
15
|
### 🌐 Modo Cliente (StorageClient)
|
|
16
|
-
Se conecta a un backend remoto mediante HTTP. **Ideal para
|
|
16
|
+
Se conecta a un backend remoto mediante HTTP. **Ideal para:**
|
|
17
17
|
- Producción con backend separado
|
|
18
18
|
- Múltiples aplicaciones compartiendo almacenamiento
|
|
19
19
|
- Escalabilidad y distribución
|
|
@@ -22,7 +22,10 @@ Se conecta a un backend remoto mediante HTTP. **Ideal para:
|
|
|
22
22
|
|
|
23
23
|
- ✅ Almacenamiento local de archivos (StorageCore)
|
|
24
24
|
- ✅ Cliente HTTP para backend remoto (StorageClient)
|
|
25
|
-
- ✅ Autenticación con
|
|
25
|
+
- ✅ Autenticación con JWT y TOTP (Time-based OTP)
|
|
26
|
+
- ✅ Generación automática de tokens con CLIENT_SECRET + TOTP
|
|
27
|
+
- ✅ Configuración mediante variables de entorno
|
|
28
|
+
- ✅ Tiempos de expiración configurables por cliente
|
|
26
29
|
- ✅ Validación de tipos MIME
|
|
27
30
|
- ✅ Límites de tamaño de archivo
|
|
28
31
|
- ✅ Sanitización de rutas (protección contra path traversal)
|
|
@@ -47,13 +50,88 @@ npm install @nestjs/common
|
|
|
47
50
|
|
|
48
51
|
### Elegir el Modo Correcto
|
|
49
52
|
|
|
50
|
-
**¿Necesitas almacenar archivos localmente?** → Usa `StorageCore`
|
|
51
|
-
**¿Necesitas conectarte a un backend remoto?** → Usa `StorageClient`
|
|
53
|
+
**¿Necesitas almacenar archivos localmente?** → Usa `StorageCore` o modo `offline`
|
|
54
|
+
**¿Necesitas conectarte a un backend remoto?** → Usa `StorageClient` o modo `online`
|
|
52
55
|
|
|
53
56
|
Ambos tienen la **misma API**, solo cambia la inicialización.
|
|
54
57
|
|
|
55
58
|
---
|
|
56
59
|
|
|
60
|
+
## 🔄 Modo Offline/Online (Recomendado para desarrollo)
|
|
61
|
+
|
|
62
|
+
El paquete soporta un **modo automático** que detecta si estás en desarrollo (offline) o producción (online) basándose en la variable de entorno `STORAGE_MODE`.
|
|
63
|
+
|
|
64
|
+
### ¿Por qué usar esto?
|
|
65
|
+
|
|
66
|
+
- **En desarrollo**: No necesitas tener un servidor de almacenamiento corriendo
|
|
67
|
+
- **En producción**: Usa el servidor remoto sin cambiar código
|
|
68
|
+
- **Mismo código**: La API es idéntica, solo cambian las variables de entorno
|
|
69
|
+
|
|
70
|
+
### Configuración Rápida
|
|
71
|
+
|
|
72
|
+
**Para desarrollo (offline):**
|
|
73
|
+
```bash
|
|
74
|
+
# .env
|
|
75
|
+
STORAGE_MODE=offline
|
|
76
|
+
STORAGE_ROOT_PATH=./uploads
|
|
77
|
+
STORAGE_MAX_FILE_SIZE=50mb # Opcional
|
|
78
|
+
STORAGE_ALLOWED_MIME_TYPES=image/*,video/* # Opcional
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Para producción (online):**
|
|
82
|
+
```bash
|
|
83
|
+
# .env
|
|
84
|
+
STORAGE_MODE=online
|
|
85
|
+
STORAGE_BASE_URL=https://storage.example.com
|
|
86
|
+
STORAGE_CLIENT_ID=mi-app
|
|
87
|
+
STORAGE_CLIENT_NAME=Mi Aplicacion
|
|
88
|
+
STORAGE_PERMISSIONS=read,write,delete
|
|
89
|
+
STORAGE_ALLOWED_PATHS=*
|
|
90
|
+
STORAGE_CLIENT_SECRET=tu-clave-secreta...
|
|
91
|
+
STORAGE_TOTP_SECRET=tu-semilla-totp...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Uso en Código
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import 'dotenv/config';
|
|
98
|
+
import { createClientFromConfig, isOfflineClient } from 's3-client-dtb';
|
|
99
|
+
|
|
100
|
+
// ✅ Detecta automáticamente el modo según STORAGE_MODE
|
|
101
|
+
const client = createClientFromConfig();
|
|
102
|
+
|
|
103
|
+
// La API es idéntica en ambos modos
|
|
104
|
+
const path = await client.uploadFile('images', buffer, 'image/png');
|
|
105
|
+
const stream = await client.downloadFile(path);
|
|
106
|
+
const exists = await client.fileExists(path);
|
|
107
|
+
await client.deleteFile(path);
|
|
108
|
+
|
|
109
|
+
// Si necesitas saber en qué modo estás:
|
|
110
|
+
if (isOfflineClient(client)) {
|
|
111
|
+
console.log('Modo offline - archivos guardados localmente');
|
|
112
|
+
} else {
|
|
113
|
+
console.log('Modo online - usando servidor remoto');
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Funciones Auxiliares
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import {
|
|
121
|
+
createClientFromConfig, // Detecta modo automáticamente
|
|
122
|
+
createOfflineClient, // Forzar modo offline
|
|
123
|
+
createOnlineClient, // Forzar modo online (deprecated, usa createClientFromConfig)
|
|
124
|
+
getStorageMode, // Obtener el modo actual
|
|
125
|
+
isOfflineClient, // Verificar si es cliente offline
|
|
126
|
+
isOnlineClient, // Verificar si es cliente online
|
|
127
|
+
} from 's3-client-dtb';
|
|
128
|
+
|
|
129
|
+
// Obtener el modo configurado
|
|
130
|
+
const mode = getStorageMode(); // 'online' | 'offline'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
57
135
|
## 📁 Modo Local (StorageCore)
|
|
58
136
|
|
|
59
137
|
### Configuración
|
|
@@ -95,51 +173,363 @@ const exists = await storage.fileExists(path);
|
|
|
95
173
|
|
|
96
174
|
## 🌐 Modo Cliente (StorageClient)
|
|
97
175
|
|
|
98
|
-
### Configuración
|
|
176
|
+
### Configuración con Variables de Entorno
|
|
177
|
+
|
|
178
|
+
El cliente se configura mediante variables de entorno. Crea un archivo `.env` basado en `.env.example`:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Modo de almacenamiento (online para servidor remoto)
|
|
182
|
+
STORAGE_MODE=online
|
|
183
|
+
|
|
184
|
+
# Variables requeridas para modo online
|
|
185
|
+
STORAGE_CLIENT_ID=mi-cliente-123
|
|
186
|
+
STORAGE_CLIENT_NAME=Mi Aplicacion
|
|
187
|
+
STORAGE_BASE_URL=http://localhost:3000
|
|
188
|
+
STORAGE_PERMISSIONS=["read","write","delete"]
|
|
189
|
+
STORAGE_ALLOWED_PATHS=["*"]
|
|
190
|
+
STORAGE_CLIENT_SECRET=tu-clave-secreta-compartida-minimo-32-caracteres
|
|
191
|
+
STORAGE_TOTP_SECRET=tu-semilla-totp-compartida-minimo-16-caracteres
|
|
192
|
+
|
|
193
|
+
# Variables opcionales
|
|
194
|
+
STORAGE_TOKEN_EXPIRES_IN=5m
|
|
195
|
+
STORAGE_TOTP_PERIOD=30
|
|
196
|
+
STORAGE_RATE_LIMIT_REQUESTS=1000
|
|
197
|
+
STORAGE_RATE_LIMIT_WINDOW=1h
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**IMPORTANTE**:
|
|
201
|
+
- Los valores de `STORAGE_CLIENT_SECRET` y `STORAGE_TOTP_SECRET` deben ser los mismos que están en las variables de entorno del servidor (`CLIENT_SECRET` y `TOTP_SECRET`).
|
|
202
|
+
- Si el servidor usa `TOTP_PERIOD` distinto de 30, configura `STORAGE_TOTP_PERIOD` con el mismo valor (15–300 segundos).
|
|
203
|
+
- Para `STORAGE_PERMISSIONS` y `STORAGE_ALLOWED_PATHS` puedes usar JSON array (`["read","write","delete"]`) o valores separados por comas (`read,write,delete`).
|
|
204
|
+
|
|
205
|
+
### 🔗 Conectando con el Backend
|
|
206
|
+
|
|
207
|
+
Para que el cliente se conecte correctamente al backend, necesitas asegurarte de que ciertos valores coincidan exactamente entre ambos.
|
|
208
|
+
|
|
209
|
+
#### Checklist de Configuración
|
|
210
|
+
|
|
211
|
+
Antes de usar el cliente, verifica que:
|
|
212
|
+
|
|
213
|
+
- [ ] **`STORAGE_CLIENT_SECRET`** (cliente) = **`CLIENT_SECRET`** (servidor)
|
|
214
|
+
- [ ] **`STORAGE_TOTP_SECRET`** (cliente) = **`TOTP_SECRET`** (servidor)
|
|
215
|
+
- [ ] **`STORAGE_TOTP_PERIOD`** (cliente) = **`TOTP_PERIOD`** (servidor) - Si el servidor usa un valor distinto de 30
|
|
216
|
+
- [ ] **`STORAGE_BASE_URL`** apunta a la URL correcta del servidor (ej: `http://localhost:3000`)
|
|
217
|
+
|
|
218
|
+
#### Ejemplo de Configuración Completa
|
|
219
|
+
|
|
220
|
+
**Backend (s3-package/.env):**
|
|
221
|
+
```env
|
|
222
|
+
CLIENT_SECRET=mi-clave-secreta-compartida-minimo-32-caracteres-1234567890
|
|
223
|
+
TOTP_SECRET=mi-semilla-totp-compartida-minimo-16-caracteres
|
|
224
|
+
TOTP_PERIOD=30
|
|
225
|
+
PORT=3000
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Cliente (tu-app/.env):**
|
|
229
|
+
```env
|
|
230
|
+
STORAGE_CLIENT_ID=mi-cliente-123
|
|
231
|
+
STORAGE_CLIENT_NAME=Mi Aplicacion
|
|
232
|
+
STORAGE_BASE_URL=http://localhost:3000
|
|
233
|
+
STORAGE_PERMISSIONS=["read","write","delete"]
|
|
234
|
+
STORAGE_ALLOWED_PATHS=["*"]
|
|
235
|
+
STORAGE_CLIENT_SECRET=mi-clave-secreta-compartida-minimo-32-caracteres-1234567890
|
|
236
|
+
STORAGE_TOTP_SECRET=mi-semilla-totp-compartida-minimo-16-caracteres
|
|
237
|
+
STORAGE_TOTP_PERIOD=30
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**⚠️ Importante**: Los valores de `CLIENT_SECRET` y `TOTP_SECRET` deben ser **exactamente iguales** en ambos archivos `.env`.
|
|
241
|
+
|
|
242
|
+
#### Verificar la Conexión
|
|
243
|
+
|
|
244
|
+
Una vez configurado, puedes verificar que la conexión funciona correctamente:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import 'dotenv/config';
|
|
248
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
249
|
+
|
|
250
|
+
const client = createClientFromConfig();
|
|
251
|
+
|
|
252
|
+
// Verificar que puedes generar códigos TOTP correctos
|
|
253
|
+
try {
|
|
254
|
+
await client.verifyTotp();
|
|
255
|
+
console.log('✅ Conexión con el backend verificada correctamente');
|
|
256
|
+
} catch (error) {
|
|
257
|
+
console.error('❌ Error al conectar con el backend:', error);
|
|
258
|
+
// Verifica que los secrets coincidan y que el servidor esté corriendo
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Solución de Problemas de Conexión
|
|
263
|
+
|
|
264
|
+
**Error: "UNAUTHORIZED" o "TOTP_VERIFY_ERROR"**
|
|
265
|
+
- Verifica que `STORAGE_CLIENT_SECRET` y `STORAGE_TOTP_SECRET` coincidan exactamente con los del servidor
|
|
266
|
+
- Verifica que `STORAGE_TOTP_PERIOD` coincida con `TOTP_PERIOD` del servidor
|
|
267
|
+
- Asegúrate de que el servidor esté corriendo y accesible en `STORAGE_BASE_URL`
|
|
268
|
+
|
|
269
|
+
**Error: "Timeout al conectar con el servidor"**
|
|
270
|
+
- Verifica que `STORAGE_BASE_URL` sea correcta y el servidor esté corriendo
|
|
271
|
+
- Verifica la conectividad de red
|
|
272
|
+
- Aumenta el `timeout` en la configuración manual si es necesario
|
|
273
|
+
|
|
274
|
+
**Error: "FORBIDDEN"**
|
|
275
|
+
- Verifica que los `STORAGE_PERMISSIONS` incluyan la operación que intentas realizar
|
|
276
|
+
- Verifica que el `STORAGE_ALLOWED_PATHS` permita la ruta que intentas usar
|
|
277
|
+
|
|
278
|
+
### Cargar Variables de Entorno
|
|
279
|
+
|
|
280
|
+
Si usas Node.js, puedes usar `dotenv` para cargar el archivo `.env`:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
npm install dotenv
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import 'dotenv/config';
|
|
288
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
289
|
+
|
|
290
|
+
const client = createClientFromConfig();
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
O cargar manualmente:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
import { config } from 'dotenv';
|
|
297
|
+
config(); // Carga .env desde el directorio actual
|
|
298
|
+
|
|
299
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
300
|
+
const client = createClientFromConfig();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Crear Cliente desde Variables de Entorno
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
307
|
+
|
|
308
|
+
// Lee las variables de entorno automáticamente
|
|
309
|
+
const client = createClientFromConfig();
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Configuración Manual
|
|
99
313
|
|
|
100
314
|
```typescript
|
|
101
315
|
import { StorageClient } from 's3-client-dtb';
|
|
316
|
+
import type { ClientConfig } from 's3-client-dtb';
|
|
317
|
+
|
|
318
|
+
const clientConfig: ClientConfig = {
|
|
319
|
+
clientId: 'mi-cliente-123',
|
|
320
|
+
name: 'Mi Aplicacion',
|
|
321
|
+
permissions: ['read', 'write', 'delete'],
|
|
322
|
+
allowedPaths: ['*'],
|
|
323
|
+
rateLimit: {
|
|
324
|
+
requests: 1000,
|
|
325
|
+
window: '1h'
|
|
326
|
+
},
|
|
327
|
+
tokenExpiresIn: '5m'
|
|
328
|
+
};
|
|
102
329
|
|
|
103
|
-
// ✅ Configuración simple - Solo necesitas URL y API key
|
|
104
330
|
const client = new StorageClient({
|
|
105
|
-
baseUrl: 'https://api.midominio.com',
|
|
106
|
-
|
|
107
|
-
|
|
331
|
+
baseUrl: 'https://api.midominio.com',
|
|
332
|
+
clientConfig,
|
|
333
|
+
clientSecret: 'tu-clave-secreta-compartida-minimo-32-caracteres',
|
|
334
|
+
totpSecret: 'tu-semilla-totp-compartida-minimo-16-caracteres',
|
|
335
|
+
totpPeriodSeconds: 30, // opcional; debe coincidir con TOTP_PERIOD del servidor
|
|
108
336
|
});
|
|
109
337
|
```
|
|
110
338
|
|
|
339
|
+
### Autenticación Automática
|
|
340
|
+
|
|
341
|
+
El cliente genera tokens automáticamente usando TOTP:
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
345
|
+
|
|
346
|
+
const client = createClientFromConfig();
|
|
347
|
+
|
|
348
|
+
// El cliente genera tokens automáticamente usando CLIENT_SECRET + TOTP
|
|
349
|
+
// No necesitas llamar ningún método de autenticación manualmente
|
|
350
|
+
|
|
351
|
+
// Opcional: Verificar que puedes generar códigos TOTP correctos
|
|
352
|
+
await client.verifyTotp();
|
|
353
|
+
|
|
354
|
+
// Ahora puedes usar el cliente normalmente
|
|
355
|
+
// Los tokens se generan y renuevan automáticamente
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Variables de Entorno
|
|
359
|
+
|
|
360
|
+
#### Requeridas
|
|
361
|
+
|
|
362
|
+
- `STORAGE_CLIENT_ID` (string): Identificador único del cliente
|
|
363
|
+
- `STORAGE_CLIENT_NAME` (string): Nombre descriptivo de la aplicación
|
|
364
|
+
- `STORAGE_BASE_URL` (string): URL del backend (ej: `http://localhost:3000`)
|
|
365
|
+
- `STORAGE_PERMISSIONS` (string): Permisos del cliente. Puede ser JSON array (`["read","write","delete"]`) o valores separados por comas (`read,write,delete`)
|
|
366
|
+
- `STORAGE_ALLOWED_PATHS` (string): Paths permitidos. Puede ser JSON array (`["*"]` o `["images/*","documents/*"]`) o valores separados por comas (`*` o `images/*,documents/*`)
|
|
367
|
+
- `STORAGE_CLIENT_SECRET` (string): Clave secreta compartida con el servidor (mínimo 32 caracteres)
|
|
368
|
+
- `STORAGE_TOTP_SECRET` (string): Semilla TOTP compartida con el servidor (mínimo 16 caracteres)
|
|
369
|
+
|
|
370
|
+
#### Opcionales
|
|
371
|
+
|
|
372
|
+
- `STORAGE_TOKEN_EXPIRES_IN` (string): Duración del token JWT (ej: `"5m"`, `"1h"`, `"30m"`). Si no se especifica, se usa el default del servidor (`TOKEN_EXPIRES_IN`)
|
|
373
|
+
- `STORAGE_TOTP_PERIOD` (number): Período TOTP en segundos (15–300, default 30). Debe coincidir con `TOTP_PERIOD` del servidor.
|
|
374
|
+
- `STORAGE_RATE_LIMIT_REQUESTS` (number): Número de peticiones permitidas (solo se usa si también defines `STORAGE_RATE_LIMIT_WINDOW`)
|
|
375
|
+
- `STORAGE_RATE_LIMIT_WINDOW` (string): Ventana de tiempo para el rate limit (ej: `"1h"`, `"30m"`). Solo se usa si también defines `STORAGE_RATE_LIMIT_REQUESTS`
|
|
376
|
+
|
|
377
|
+
#### Tabla de Referencia Rápida
|
|
378
|
+
|
|
379
|
+
| Variable | Requerida | Tipo | Default | Descripción |
|
|
380
|
+
|----------|-----------|------|---------|-------------|
|
|
381
|
+
| `STORAGE_CLIENT_ID` | ✅ | string | - | Identificador único del cliente |
|
|
382
|
+
| `STORAGE_CLIENT_NAME` | ✅ | string | - | Nombre descriptivo de la aplicación |
|
|
383
|
+
| `STORAGE_BASE_URL` | ✅ | string | - | URL del backend (ej: `http://localhost:3000`) |
|
|
384
|
+
| `STORAGE_PERMISSIONS` | ✅ | string | - | Permisos: JSON array o comma-separated (`["read","write","delete"]` o `read,write,delete`) |
|
|
385
|
+
| `STORAGE_ALLOWED_PATHS` | ✅ | string | - | Paths permitidos: JSON array o comma-separated (`["*"]` o `*`) |
|
|
386
|
+
| `STORAGE_CLIENT_SECRET` | ✅ | string | - | Clave secreta compartida (mínimo 32 caracteres). **Debe coincidir con `CLIENT_SECRET` del servidor** |
|
|
387
|
+
| `STORAGE_TOTP_SECRET` | ✅ | string | - | Semilla TOTP compartida (mínimo 16 caracteres). **Debe coincidir con `TOTP_SECRET` del servidor** |
|
|
388
|
+
| `STORAGE_TOKEN_EXPIRES_IN` | ❌ | string | `5m` | Duración del token JWT (`30s`, `5m`, `1h`, `7d`) |
|
|
389
|
+
| `STORAGE_TOTP_PERIOD` | ❌ | number | `30` | Período TOTP en segundos (15-300). **Debe coincidir con `TOTP_PERIOD` del servidor** |
|
|
390
|
+
| `STORAGE_RATE_LIMIT_REQUESTS` | ❌ | number | - | Número de peticiones permitidas (requiere `STORAGE_RATE_LIMIT_WINDOW`) |
|
|
391
|
+
| `STORAGE_RATE_LIMIT_WINDOW` | ❌ | string | - | Ventana de tiempo para rate limit (`1h`, `30m`, `7d`) |
|
|
392
|
+
|
|
111
393
|
### Ejemplos de baseUrl
|
|
112
394
|
|
|
113
395
|
```typescript
|
|
114
396
|
// Con dominio
|
|
115
|
-
new StorageClient({
|
|
397
|
+
new StorageClient({
|
|
398
|
+
baseUrl: 'https://api.midominio.com',
|
|
399
|
+
clientConfig
|
|
400
|
+
});
|
|
116
401
|
|
|
117
402
|
// Con IP directa
|
|
118
|
-
new StorageClient({
|
|
403
|
+
new StorageClient({
|
|
404
|
+
baseUrl: 'http://192.168.1.100:3000',
|
|
405
|
+
clientConfig
|
|
406
|
+
});
|
|
119
407
|
|
|
120
408
|
// Con puerto personalizado
|
|
121
|
-
new StorageClient({
|
|
409
|
+
new StorageClient({
|
|
410
|
+
baseUrl: 'http://localhost:8080',
|
|
411
|
+
clientConfig
|
|
412
|
+
});
|
|
122
413
|
```
|
|
123
414
|
|
|
124
415
|
### Uso Completo
|
|
125
416
|
|
|
126
417
|
```typescript
|
|
127
|
-
import
|
|
418
|
+
import 'dotenv/config'; // Cargar variables de entorno
|
|
419
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
420
|
+
import {
|
|
421
|
+
FileNotFoundError,
|
|
422
|
+
UploadError,
|
|
423
|
+
StorageError
|
|
424
|
+
} from 's3-client-dtb';
|
|
128
425
|
import { readFileSync } from 'fs';
|
|
129
426
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
apiKey: 'sk_live_xxx'
|
|
133
|
-
});
|
|
427
|
+
// Inicializar cliente desde variables de entorno
|
|
428
|
+
const client = createClientFromConfig();
|
|
134
429
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
430
|
+
// Opcional: Verificar que la configuración TOTP es correcta
|
|
431
|
+
try {
|
|
432
|
+
await client.verifyTotp();
|
|
433
|
+
console.log('✅ Cliente configurado correctamente');
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error('❌ Error en configuración:', error);
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Ejemplo: Subir un archivo
|
|
440
|
+
try {
|
|
441
|
+
const buffer = readFileSync('./mi-imagen.jpg');
|
|
442
|
+
const path = await client.uploadFile(
|
|
443
|
+
'images/photo.jpg', // Ruta donde se guardará
|
|
444
|
+
buffer, // Buffer del archivo
|
|
445
|
+
'image/jpeg' // Tipo MIME
|
|
446
|
+
);
|
|
447
|
+
console.log('✅ Archivo subido en:', path);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
if (error instanceof UploadError) {
|
|
450
|
+
console.error('❌ Error al subir:', error.message);
|
|
451
|
+
} else {
|
|
452
|
+
console.error('❌ Error inesperado:', error);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Ejemplo: Descargar un archivo
|
|
457
|
+
try {
|
|
458
|
+
const stream = await client.downloadFile('images/photo.jpg');
|
|
459
|
+
|
|
460
|
+
// Convertir stream a buffer si es necesario
|
|
461
|
+
const chunks: Buffer[] = [];
|
|
462
|
+
for await (const chunk of stream) {
|
|
463
|
+
chunks.push(chunk);
|
|
464
|
+
}
|
|
465
|
+
const downloadedBuffer = Buffer.concat(chunks);
|
|
466
|
+
console.log('✅ Archivo descargado:', downloadedBuffer.length, 'bytes');
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (error instanceof FileNotFoundError) {
|
|
469
|
+
console.error('❌ Archivo no encontrado');
|
|
470
|
+
} else {
|
|
471
|
+
console.error('❌ Error al descargar:', error);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Ejemplo: Obtener metadata
|
|
476
|
+
try {
|
|
477
|
+
const metadata = await client.getFileMetadata('images/photo.jpg');
|
|
478
|
+
console.log('Metadata:', {
|
|
479
|
+
nombre: metadata.name,
|
|
480
|
+
tamaño: metadata.size,
|
|
481
|
+
tipo: metadata.mimeType,
|
|
482
|
+
creado: metadata.createdAt
|
|
483
|
+
});
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error('❌ Error al obtener metadata:', error);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Ejemplo: Listar archivos
|
|
489
|
+
try {
|
|
490
|
+
const { files, total } = await client.listFiles('images', {
|
|
491
|
+
limit: 10, // Máximo 10 archivos
|
|
492
|
+
offset: 0, // Desde el inicio
|
|
493
|
+
prefix: 'photo' // Solo archivos que empiecen con "photo"
|
|
494
|
+
});
|
|
495
|
+
console.log(`✅ Encontrados ${files.length} de ${total} archivos`);
|
|
496
|
+
files.forEach(file => {
|
|
497
|
+
console.log(` - ${file.name} (${file.size} bytes)`);
|
|
498
|
+
});
|
|
499
|
+
} catch (error) {
|
|
500
|
+
console.error('❌ Error al listar archivos:', error);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Ejemplo: Verificar existencia
|
|
504
|
+
const exists = await client.fileExists('images/photo.jpg');
|
|
505
|
+
console.log('Archivo existe:', exists);
|
|
506
|
+
|
|
507
|
+
// Ejemplo: Eliminar archivo
|
|
508
|
+
try {
|
|
509
|
+
await client.deleteFile('images/photo.jpg');
|
|
510
|
+
console.log('✅ Archivo eliminado');
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (error instanceof FileNotFoundError) {
|
|
513
|
+
console.error('❌ Archivo no encontrado para eliminar');
|
|
514
|
+
} else {
|
|
515
|
+
console.error('❌ Error al eliminar:', error);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Gestión de Tokens
|
|
521
|
+
|
|
522
|
+
El cliente genera tokens automáticamente usando TOTP:
|
|
523
|
+
|
|
524
|
+
- **Generación automática**: El cliente genera tokens JWT usando `CLIENT_SECRET + TOTP_CODE`
|
|
525
|
+
- **TOTP dinámico**: El código TOTP cambia cada 30 segundos, proporcionando seguridad adicional
|
|
526
|
+
- **Renovación automática**: Los tokens se regeneran automáticamente cuando expiran o cuando cambia el código TOTP
|
|
527
|
+
- **Caché inteligente**: Los tokens se cachean y se regeneran solo cuando es necesario
|
|
528
|
+
- **Errores 401**: El cliente regenera el token automáticamente si recibe un 401
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
// El cliente siempre está listo para usar, genera tokens automáticamente
|
|
532
|
+
// No necesitas métodos de autenticación manual
|
|
143
533
|
```
|
|
144
534
|
|
|
145
535
|
---
|
|
@@ -151,20 +541,23 @@ Como ambos tienen la misma API, cambiar de modo es **súper simple**:
|
|
|
151
541
|
### Opción 1: Variable de Entorno
|
|
152
542
|
|
|
153
543
|
```typescript
|
|
154
|
-
import { StorageCore,
|
|
544
|
+
import { StorageCore, createClientFromConfig } from 's3-client-dtb';
|
|
155
545
|
|
|
156
546
|
// Usar variable de entorno para decidir el modo
|
|
157
547
|
const useRemote = process.env.STORAGE_MODE === 'remote';
|
|
158
548
|
|
|
159
549
|
const storage = useRemote
|
|
160
|
-
?
|
|
161
|
-
baseUrl: process.env.STORAGE_URL!,
|
|
162
|
-
apiKey: process.env.STORAGE_API_KEY!
|
|
163
|
-
})
|
|
550
|
+
? createClientFromConfig() // Lee variables de entorno automáticamente
|
|
164
551
|
: new StorageCore({
|
|
165
552
|
rootPath: process.env.STORAGE_PATH || './uploads'
|
|
166
553
|
});
|
|
167
554
|
|
|
555
|
+
// Si es remoto, el cliente genera tokens automáticamente
|
|
556
|
+
// Opcional: Verificar TOTP
|
|
557
|
+
if (useRemote && storage instanceof StorageClient) {
|
|
558
|
+
await storage.verifyTotp();
|
|
559
|
+
}
|
|
560
|
+
|
|
168
561
|
// El resto del código es idéntico
|
|
169
562
|
await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
|
|
170
563
|
```
|
|
@@ -172,15 +565,18 @@ await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
|
|
|
172
565
|
### Opción 2: Factory Function
|
|
173
566
|
|
|
174
567
|
```typescript
|
|
175
|
-
import { StorageCore,
|
|
176
|
-
|
|
177
|
-
function createStorage() {
|
|
178
|
-
if (process.env.STORAGE_URL
|
|
179
|
-
// Modo remoto
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
568
|
+
import { StorageCore, createClientFromConfig } from 's3-client-dtb';
|
|
569
|
+
|
|
570
|
+
async function createStorage() {
|
|
571
|
+
if (process.env.STORAGE_URL) {
|
|
572
|
+
// Modo remoto - usa variables de entorno
|
|
573
|
+
const client = createClientFromConfig();
|
|
574
|
+
|
|
575
|
+
// El cliente genera tokens automáticamente
|
|
576
|
+
// Opcional: Verificar TOTP
|
|
577
|
+
await client.verifyTotp();
|
|
578
|
+
|
|
579
|
+
return client;
|
|
184
580
|
}
|
|
185
581
|
|
|
186
582
|
// Modo local (por defecto)
|
|
@@ -189,35 +585,12 @@ function createStorage() {
|
|
|
189
585
|
});
|
|
190
586
|
}
|
|
191
587
|
|
|
192
|
-
const storage = createStorage();
|
|
588
|
+
const storage = await createStorage();
|
|
193
589
|
// Usar normalmente
|
|
194
590
|
await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
|
|
195
591
|
```
|
|
196
592
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
import { StorageCore, StorageClient } from 's3-client-dtb';
|
|
201
|
-
import type { FileMetadata, ListFilesOptions } from 's3-client-dtb';
|
|
202
|
-
|
|
203
|
-
// Interfaz común para ambos modos
|
|
204
|
-
interface Storage {
|
|
205
|
-
uploadFile(path: string, buffer: Buffer, mimeType: string, prefix?: string): Promise<string>;
|
|
206
|
-
downloadFile(path: string): Promise<Readable>;
|
|
207
|
-
deleteFile(path: string): Promise<void>;
|
|
208
|
-
getFileMetadata(path: string): Promise<FileMetadata>;
|
|
209
|
-
listFiles(directoryPath?: string, options?: ListFilesOptions): Promise<{files: FileMetadata[], total: number}>;
|
|
210
|
-
fileExists(path: string): Promise<boolean>;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Crear instancia según configuración
|
|
214
|
-
const storage: Storage = process.env.STORAGE_MODE === 'remote'
|
|
215
|
-
? new StorageClient({ baseUrl: process.env.STORAGE_URL!, apiKey: process.env.STORAGE_API_KEY! })
|
|
216
|
-
: new StorageCore({ rootPath: './uploads' });
|
|
217
|
-
|
|
218
|
-
// Código tipo-seguro
|
|
219
|
-
await storage.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
|
|
220
|
-
```
|
|
593
|
+
---
|
|
221
594
|
|
|
222
595
|
## Uso desde NestJS
|
|
223
596
|
|
|
@@ -282,6 +655,8 @@ export class FilesService {
|
|
|
282
655
|
}
|
|
283
656
|
```
|
|
284
657
|
|
|
658
|
+
---
|
|
659
|
+
|
|
285
660
|
## 📖 API Completa
|
|
286
661
|
|
|
287
662
|
### ⚡ Métodos Comunes (Ambos Modos)
|
|
@@ -325,84 +700,40 @@ interface StorageCoreOptions {
|
|
|
325
700
|
#### StorageClient (Modo Remoto)
|
|
326
701
|
|
|
327
702
|
```typescript
|
|
703
|
+
// Opción 1: Desde variables de entorno (recomendado)
|
|
704
|
+
createClientFromConfig(): StorageClient
|
|
705
|
+
|
|
706
|
+
// Opción 2: Manual
|
|
328
707
|
new StorageClient(options: StorageClientOptions)
|
|
329
708
|
|
|
330
709
|
interface StorageClientOptions {
|
|
331
|
-
baseUrl: string;
|
|
332
|
-
|
|
333
|
-
|
|
710
|
+
baseUrl: string; // URL del backend
|
|
711
|
+
clientConfig: ClientConfig; // Configuración del cliente
|
|
712
|
+
clientSecret: string; // Clave secreta compartida (mínimo 32 caracteres). Debe coincidir con CLIENT_SECRET del servidor
|
|
713
|
+
totpSecret: string; // Semilla TOTP compartida (mínimo 16 caracteres). Debe coincidir con TOTP_SECRET del servidor
|
|
714
|
+
totpPeriodSeconds?: number; // Opcional: Período TOTP en segundos (15-300, default: 30). Debe coincidir con TOTP_PERIOD del servidor
|
|
715
|
+
timeout?: number; // Opcional: timeout en ms (default: 30000)
|
|
334
716
|
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
**Nota:** El cliente automáticamente agrega el prefijo `/apifiles` a las URLs y maneja la autenticación con API keys.
|
|
338
|
-
|
|
339
|
-
#### Constructor
|
|
340
717
|
|
|
341
|
-
|
|
342
|
-
|
|
718
|
+
interface ClientConfig {
|
|
719
|
+
clientId: string;
|
|
720
|
+
name: string;
|
|
721
|
+
permissions: ('read' | 'write' | 'delete')[];
|
|
722
|
+
allowedPaths: string[];
|
|
723
|
+
rateLimit?: {
|
|
724
|
+
requests: number;
|
|
725
|
+
window: string;
|
|
726
|
+
};
|
|
727
|
+
tokenExpiresIn?: string; // Opcional: "30m", "1h", "5m", "7d"
|
|
728
|
+
}
|
|
343
729
|
```
|
|
344
730
|
|
|
345
|
-
**
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
|
|
350
|
-
#### Métodos
|
|
351
|
-
|
|
352
|
-
##### `uploadFile(path, buffer, mimeType, prefix?)`
|
|
353
|
-
|
|
354
|
-
Sube un archivo al almacenamiento.
|
|
355
|
-
|
|
356
|
-
- `path` (string): Ruta relativa donde se guardará el archivo
|
|
357
|
-
- `buffer` (Buffer): Contenido del archivo
|
|
358
|
-
- `mimeType` (string): Tipo MIME del archivo
|
|
359
|
-
- `prefix` (string, opcional): Prefijo para el nombre del archivo (se genera un UUID)
|
|
360
|
-
|
|
361
|
-
**Retorna:** `Promise<string>` - Ruta relativa del archivo guardado
|
|
362
|
-
|
|
363
|
-
##### `downloadFile(path)`
|
|
364
|
-
|
|
365
|
-
Descarga un archivo como stream.
|
|
366
|
-
|
|
367
|
-
- `path` (string): Ruta relativa del archivo
|
|
368
|
-
|
|
369
|
-
**Retorna:** `Promise<Readable>` - Stream de Node.js
|
|
370
|
-
|
|
371
|
-
##### `deleteFile(path)`
|
|
372
|
-
|
|
373
|
-
Elimina un archivo.
|
|
374
|
-
|
|
375
|
-
- `path` (string): Ruta relativa del archivo
|
|
376
|
-
|
|
377
|
-
**Retorna:** `Promise<void>`
|
|
378
|
-
|
|
379
|
-
##### `getFileMetadata(path)`
|
|
380
|
-
|
|
381
|
-
Obtiene metadata de un archivo.
|
|
382
|
-
|
|
383
|
-
- `path` (string): Ruta relativa del archivo
|
|
384
|
-
|
|
385
|
-
**Retorna:** `Promise<FileMetadata>`
|
|
386
|
-
|
|
387
|
-
##### `listFiles(directoryPath?, options?)`
|
|
388
|
-
|
|
389
|
-
Lista archivos en un directorio.
|
|
390
|
-
|
|
391
|
-
- `directoryPath` (string, opcional): Ruta del directorio (vacío para raíz)
|
|
392
|
-
- `options` (ListFilesOptions, opcional):
|
|
393
|
-
- `limit` (number): Número máximo de archivos
|
|
394
|
-
- `offset` (number): Offset para paginación
|
|
395
|
-
- `prefix` (string): Filtrar por prefijo en el nombre
|
|
396
|
-
|
|
397
|
-
**Retorna:** `Promise<{ files: FileMetadata[], total: number }>`
|
|
398
|
-
|
|
399
|
-
##### `fileExists(path)`
|
|
400
|
-
|
|
401
|
-
Verifica si un archivo existe.
|
|
731
|
+
**Nota:**
|
|
732
|
+
- El cliente automáticamente agrega el prefijo `/apifiles` a las URLs
|
|
733
|
+
- Genera tokens automáticamente usando `CLIENT_SECRET + TOTP` (no requiere autenticación manual)
|
|
734
|
+
- Regenera tokens automáticamente cuando expiran o cuando cambia el código TOTP
|
|
402
735
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
**Retorna:** `Promise<boolean>`
|
|
736
|
+
---
|
|
406
737
|
|
|
407
738
|
## Tipos
|
|
408
739
|
|
|
@@ -420,32 +751,41 @@ interface FileMetadata {
|
|
|
420
751
|
}
|
|
421
752
|
```
|
|
422
753
|
|
|
754
|
+
### ListFilesOptions
|
|
755
|
+
|
|
756
|
+
```typescript
|
|
757
|
+
interface ListFilesOptions {
|
|
758
|
+
limit?: number;
|
|
759
|
+
offset?: number;
|
|
760
|
+
prefix?: string;
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
423
766
|
## 📋 Comparación Rápida
|
|
424
767
|
|
|
425
768
|
| Característica | StorageCore (Local) | StorageClient (Remoto) |
|
|
426
769
|
|---------------|---------------------|------------------------|
|
|
427
|
-
| **Inicialización** | `new StorageCore({ rootPath })` | `new StorageClient({ baseUrl,
|
|
770
|
+
| **Inicialización** | `new StorageCore({ rootPath })` | `createClientFromConfig()` o `new StorageClient({ baseUrl, clientConfig })` |
|
|
428
771
|
| **Almacenamiento** | Sistema de archivos local | Backend remoto vía HTTP |
|
|
429
772
|
| **API** | ✅ Idéntica | ✅ Idéntica |
|
|
430
|
-
| **Autenticación** | ❌ No requiere | ✅
|
|
773
|
+
| **Autenticación** | ❌ No requiere | ✅ Genera tokens JWT con TOTP (automático) |
|
|
774
|
+
| **Configuración** | Opciones en constructor | Variables de entorno |
|
|
775
|
+
| **Tiempos de expiración** | ❌ No aplica | ✅ Configurables mediante variables de entorno |
|
|
431
776
|
| **Ideal para** | Desarrollo, mismo servidor | Producción, múltiples apps |
|
|
432
|
-
|
|
777
|
+
|
|
778
|
+
---
|
|
433
779
|
|
|
434
780
|
## 💡 Recomendaciones
|
|
435
781
|
|
|
436
782
|
- **Desarrollo**: Usa `StorageCore` con `rootPath: './uploads'`
|
|
437
|
-
- **Producción**: Usa `StorageClient` con variables de entorno
|
|
783
|
+
- **Producción**: Usa `StorageClient` con variables de entorno y autenticación OTP
|
|
438
784
|
- **Cambio fácil**: Ambos tienen la misma API, solo cambia la inicialización
|
|
785
|
+
- **Seguridad**: El cliente renueva tokens automáticamente según los tiempos configurados
|
|
786
|
+
- **Configuración**: Usa `createClientFromConfig()` para leer automáticamente las variables de entorno
|
|
439
787
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
```typescript
|
|
443
|
-
interface ListFilesOptions {
|
|
444
|
-
limit?: number;
|
|
445
|
-
offset?: number;
|
|
446
|
-
prefix?: string;
|
|
447
|
-
}
|
|
448
|
-
```
|
|
788
|
+
---
|
|
449
789
|
|
|
450
790
|
## Errores
|
|
451
791
|
|
|
@@ -459,6 +799,7 @@ El paquete lanza errores personalizados que extienden `StorageError`:
|
|
|
459
799
|
- `DeleteError`: Error al eliminar archivo
|
|
460
800
|
- `MetadataError`: Error al obtener metadata
|
|
461
801
|
- `ListError`: Error al listar archivos
|
|
802
|
+
- `StorageError`: Error genérico
|
|
462
803
|
|
|
463
804
|
Todos los errores tienen una propiedad `code` para identificación:
|
|
464
805
|
|
|
@@ -474,10 +815,49 @@ try {
|
|
|
474
815
|
|
|
475
816
|
## Seguridad
|
|
476
817
|
|
|
818
|
+
- **Autenticación JWT con OTP**: Sistema seguro de autenticación de dos factores
|
|
819
|
+
- **Renovación automática de tokens**: Configurable por cliente mediante variables de entorno
|
|
820
|
+
- **Validación en cadena**: Cada refresh token valida el anterior
|
|
477
821
|
- **Sanitización de rutas**: Protección contra path traversal (`..`)
|
|
478
822
|
- **Validación de caracteres**: Solo permite caracteres seguros en rutas
|
|
479
823
|
- **Validación de MIME types**: Verifica tipos de archivo permitidos
|
|
480
824
|
- **Límites de tamaño**: Previene archivos demasiado grandes
|
|
825
|
+
- **Permisos granulares**: Cada cliente tiene permisos específicos (read, write, delete)
|
|
826
|
+
- **Paths permitidos**: Cada cliente solo puede acceder a paths configurados
|
|
827
|
+
|
|
828
|
+
## Ejemplo Completo con Variables de Entorno
|
|
829
|
+
|
|
830
|
+
1. **Crear archivo `.env`:**
|
|
831
|
+
```bash
|
|
832
|
+
STORAGE_CLIENT_ID=mi-app-prod
|
|
833
|
+
STORAGE_CLIENT_NAME=Mi Aplicacion
|
|
834
|
+
STORAGE_BASE_URL=http://localhost:3000
|
|
835
|
+
STORAGE_PERMISSIONS=["read","write","delete"]
|
|
836
|
+
STORAGE_ALLOWED_PATHS=["*"]
|
|
837
|
+
STORAGE_CLIENT_SECRET=tu-clave-secreta-compartida-minimo-32-caracteres
|
|
838
|
+
STORAGE_TOTP_SECRET=tu-semilla-totp-compartida-minimo-16-caracteres
|
|
839
|
+
STORAGE_TOKEN_EXPIRES_IN=5m
|
|
840
|
+
STORAGE_TOTP_PERIOD=30
|
|
841
|
+
STORAGE_RATE_LIMIT_REQUESTS=1000
|
|
842
|
+
STORAGE_RATE_LIMIT_WINDOW=1h
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
2. **Usar el cliente:**
|
|
846
|
+
```typescript
|
|
847
|
+
import 'dotenv/config'; // Cargar variables de entorno
|
|
848
|
+
import { createClientFromConfig } from 's3-client-dtb';
|
|
849
|
+
import { readFileSync } from 'fs';
|
|
850
|
+
|
|
851
|
+
const client = createClientFromConfig(); // Lee variables de entorno
|
|
852
|
+
|
|
853
|
+
// El cliente genera tokens automáticamente
|
|
854
|
+
// Opcional: Verificar TOTP
|
|
855
|
+
await client.verifyTotp();
|
|
856
|
+
|
|
857
|
+
// Usar normalmente
|
|
858
|
+
const buffer = readFileSync('./imagen.jpg');
|
|
859
|
+
const path = await client.uploadFile('images/photo.jpg', buffer, 'image/jpeg');
|
|
860
|
+
```
|
|
481
861
|
|
|
482
862
|
## Licencia
|
|
483
863
|
|