# R2 Presigned URL Generator

A lightweight, zero-dependency TypeScript library for generating presigned URLs for Cloudflare R2 storage using AWS Signature Version 4. Compatible with Cloudflare Workers and Node.js environments.

## Features

- ✅ **Zero Dependencies** - Uses only Web Crypto API and built-in browser/Node.js APIs
- ✅ **Cloudflare Workers Compatible** - No Node.js-specific dependencies
- ✅ **TypeScript Support** - Full type definitions included
- ✅ **AWS Signature Version 4** - Standard-compliant signature generation
- ✅ **Multiple HTTP Methods** - Support for PUT, GET, and POST requests
- ✅ **Comprehensive Validation** - Input validation and error handling
- ✅ **Testing Utilities** - Built-in test function for debugging

## Installation

```bash
npm install r2-presigned-url
```

## Quick Start

```typescript
import { generateR2PresignedUrl, R2Credentials } from 'r2-presigned-url';

const credentials: R2Credentials = {
  R2_ACCESS_KEY_ID: 'your-access-key',
  R2_SECRET_ACCESS_KEY: 'your-secret-key',
  R2_ACCOUNT_ID: 'your-account-id',
  R2_BUCKET: 'my-bucket'
};

const url = await generateR2PresignedUrl(
  {
    key: 'uploads/file.jpg',
    contentType: 'image/jpeg',
    expiresIn: 3600 // 1 hour
  },
  credentials
);

console.log('Presigned URL:', url);
```

## API Reference

### `generateR2PresignedUrl(options, credentials)`

Generates a presigned URL for R2 storage operations.

#### Parameters

- `options` (PresignedUrlOptions):
  - `key` (string): The object key (path) in the R2 bucket
  - `contentType` (string): MIME type of the file
  - `expiresIn` (number): URL expiration time in seconds (max 604800 = 7 days)
  - `method` (optional): HTTP method - 'PUT' (default), 'GET', or 'POST'

- `credentials` (R2Credentials):
  - `R2_ACCESS_KEY_ID` (string): Cloudflare R2 Access Key ID
  - `R2_SECRET_ACCESS_KEY` (string): Cloudflare R2 Secret Access Key
  - `R2_ACCOUNT_ID` (string): Cloudflare Account ID
  - `R2_BUCKET` (optional): R2 Bucket name (defaults to 'development')

#### Returns

- `Promise<string>`: The presigned URL

### `testPresignedUrlGeneration(credentials)`

Tests the presigned URL generation with sample data.

#### Parameters

- `credentials` (R2Credentials): R2 credentials to test with

#### Returns

- `Promise<TestResult>`: Test results with success status and debug info

## Usage Examples

### Upload a File

```typescript
import { generateR2PresignedUrl } from 'r2-presigned-url';

const uploadUrl = await generateR2PresignedUrl(
  {
    key: 'uploads/document.pdf',
    contentType: 'application/pdf',
    expiresIn: 1800 // 30 minutes
  },
  credentials
);

// Use the URL to upload the file
const response = await fetch(uploadUrl, {
  method: 'PUT',
  body: file,
  headers: {
    'Content-Type': 'application/pdf'
  }
});
```

### Download a File

```typescript
const downloadUrl = await generateR2PresignedUrl(
  {
    key: 'uploads/document.pdf',
    contentType: 'application/pdf',
    expiresIn: 3600,
    method: 'GET'
  },
  credentials
);

// Use the URL to download the file
const response = await fetch(downloadUrl);
const file = await response.blob();
```

### Testing

```typescript
import { testPresignedUrlGeneration } from 'r2-presigned-url';

const result = await testPresignedUrlGeneration(credentials);

if (result.success) {
  console.log('✅ Test passed!');
  console.log('Generated URL:', result.url);
  console.log('Signature:', result.debug?.signature);
} else {
  console.error('❌ Test failed:', result.error);
}
```

## Cloudflare Workers Example

```typescript
// workers/index.ts
import { generateR2PresignedUrl } from 'r2-presigned-url';

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method === 'POST') {
      const { key, contentType } = await request.json();
      
      const credentials = {
        R2_ACCESS_KEY_ID: env.R2_ACCESS_KEY_ID,
        R2_SECRET_ACCESS_KEY: env.R2_SECRET_ACCESS_KEY,
        R2_ACCOUNT_ID: env.R2_ACCOUNT_ID,
        R2_BUCKET: env.R2_BUCKET
      };

      try {
        const url = await generateR2PresignedUrl(
          {
            key,
            contentType,
            expiresIn: 3600
          },
          credentials
        );

        return new Response(JSON.stringify({ url }), {
          headers: { 'Content-Type': 'application/json' }
        });
      } catch (error) {
        return new Response(
          JSON.stringify({ error: error.message }), 
          { status: 400, headers: { 'Content-Type': 'application/json' } }
        );
      }
    }

    return new Response('Method not allowed', { status: 405 });
  }
};
```

## Environment Variables

For Cloudflare Workers, add these to your `wrangler.toml`:

```toml
[vars]
R2_ACCESS_KEY_ID = "your-access-key"
R2_SECRET_ACCESS_KEY = "your-secret-key"
R2_ACCOUNT_ID = "your-account-id"
R2_BUCKET = "your-bucket-name"
```

## Error Handling

The library throws descriptive errors for common issues:

```typescript
try {
  const url = await generateR2PresignedUrl(options, credentials);
} catch (error) {
  if (error.message.includes('Missing required R2 credentials')) {
    // Handle missing credentials
  } else if (error.message.includes('Invalid parameters')) {
    // Handle invalid parameters
  } else {
    // Handle other errors
  }
}
```

## Browser Compatibility

This library uses the Web Crypto API, which is supported in:

- Chrome 37+
- Firefox 34+
- Safari 11+
- Edge 12+
- Node.js 15+

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request

## License

MIT License - see [LICENSE](LICENSE) file for details.

## Changelog

### 1.0.0
- Initial release
- AWS Signature Version 4 implementation
- Cloudflare Workers compatibility
- TypeScript support
- Comprehensive error handling 