JavaScript SDK

A TypeScript/JavaScript client for Node.js and browser environments.

Requirements

  • Node.js 18+ (for native fetch) or any modern browser
  • For older Node.js: node-fetch package

Complete Client Class

Save this as eds-client.ts (or .js):

eds-client.ts
/**
 * EDS (Exstra Decentralized Storage) JavaScript Client
 * =====================================================
 * 
 * Usage:
 *   import { EDSClient } from './eds-client';
 *   const client = new EDSClient('exstr_live_...');
 *   await client.upload(file);
 */

export interface UploadResult {
  success: boolean;
  filename: string;
  size: number;
  folder?: string;
  nodeId?: string;
}

export interface UploadOptions {
  filename?: string;
  mimeType?: string;
  folderPath?: string;
  onProgress?: (progress: number) => void;
}

export class EDSError extends Error {
  constructor(message: string, public statusCode?: number) {
    super(message);
    this.name = 'EDSError';
  }
}

export class EDSClient {
  private static DEFAULT_BASE_URL = 'https://data.eggisatria.dev/api/v1/storage';
  
  private apiKey: string;
  private baseUrl: string;
  private maxRetries: number;
  
  constructor(
    apiKey: string,
    options: {
      baseUrl?: string;
      maxRetries?: number;
    } = {}
  ) {
    if (!apiKey?.startsWith('exstr_live_')) {
      throw new Error("Invalid API key format. Must start with 'exstr_live_'");
    }
    
    this.apiKey = apiKey;
    this.baseUrl = options.baseUrl || EDSClient.DEFAULT_BASE_URL;
    this.maxRetries = options.maxRetries ?? 3;
  }
  
  /**
   * Upload a file to EDS
   */
  async upload(
    file: File | Blob | Buffer,
    options: UploadOptions = {}
  ): Promise<UploadResult> {
    const filename = options.filename || (file instanceof File ? file.name : 'file');
    const mimeType = options.mimeType || (file instanceof File ? file.type : 'application/octet-stream');
    const size = file instanceof Buffer ? file.length : file.size;
    
    // Step 1: Request upload URL
    const response = await fetch(`${this.baseUrl}/upload`, {
      method: 'POST',
      headers: {
        'x-api-key': this.apiKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        filename,
        mimeType,
        size,
        ...(options.folderPath && { folderPath: options.folderPath }),
      }),
    });
    
    if (!response.ok) {
      const data = await response.json().catch(() => ({}));
      throw new EDSError(
        data.error || `API error: ${response.status}`,
        response.status
      );
    }
    
    const result = await response.json();
    if (!result.success) {
      throw new EDSError(result.error || 'Unknown error');
    }
    
    const uploadUrl = result.data.uploadUrl;
    
    // Step 2: Upload to Google Drive with retry
    let lastError: Error | null = null;
    
    for (let attempt = 0; attempt < this.maxRetries; attempt++) {
      try {
        const uploadResponse = await fetch(uploadUrl, {
          method: 'PUT',
          body: file,
        });
        
        if (uploadResponse.ok) {
          return {
            success: true,
            filename,
            size,
            folder: options.folderPath,
            nodeId: result.data._meta?.nodeId,
          };
        }
        
        lastError = new Error(`Google upload failed: ${uploadResponse.status}`);
      } catch (e) {
        lastError = e as Error;
      }
      
      // Exponential backoff
      if (attempt < this.maxRetries - 1) {
        await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
      }
    }
    
    throw new EDSError(
      `Upload failed after ${this.maxRetries} attempts: ${lastError?.message}`
    );
  }
  
  /**
   * Upload a string as a file
   */
  async uploadString(
    content: string,
    filename: string,
    options: Omit<UploadOptions, 'filename'> = {}
  ): Promise<UploadResult> {
    const blob = new Blob([content], {
      type: options.mimeType || 'text/plain',
    });
    return this.upload(blob, { ...options, filename });
  }
  
  /**
   * Upload JSON data
   */
  async uploadJSON(
    data: unknown,
    filename: string,
    options: Omit<UploadOptions, 'filename' | 'mimeType'> = {}
  ): Promise<UploadResult> {
    const json = JSON.stringify(data, null, 2);
    return this.uploadString(json, filename, {
      ...options,
      mimeType: 'application/json',
    });
  }
}

Usage Examples

Browser: File Input Upload

React Component
import { EDSClient } from './eds-client';

function FileUploader() {
  const [uploading, setUploading] = useState(false);
  
  const client = new EDSClient(process.env.NEXT_PUBLIC_EDS_API_KEY!);
  
  async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    
    setUploading(true);
    try {
      const result = await client.upload(file, {
        folderPath: '/uploads'
      });
      console.log('Uploaded:', result);
    } catch (err) {
      console.error('Upload failed:', err);
    } finally {
      setUploading(false);
    }
  }
  
  return (
    <input
      type="file"
      onChange={handleUpload}
      disabled={uploading}
    />
  );
}

Node.js: Upload Local File

Node.js
import { readFile } from 'fs/promises';
import { basename } from 'path';
import { EDSClient } from './eds-client';

const client = new EDSClient(process.env.EDS_API_KEY!);

async function uploadFile(filePath: string) {
  const buffer = await readFile(filePath);
  const filename = basename(filePath);
  
  // Create a Blob-like object for Node.js
  const blob = new Blob([buffer]);
  
  const result = await client.upload(blob, {
    filename,
    folderPath: '/backups'
  });
  
  console.log('✅ Uploaded:', result);
}

uploadFile('./backup.zip');

Upload JSON Data

TypeScript
const client = new EDSClient(process.env.EDS_API_KEY!);

// Upload JSON directly
const result = await client.uploadJSON(
  { users: [{ id: 1, name: 'Alice' }] },
  'users-export.json',
  { folderPath: '/exports' }
);

Error Handling

TypeScript
import { EDSClient, EDSError } from './eds-client';

const client = new EDSClient(process.env.EDS_API_KEY!);

try {
  await client.upload(file);
} catch (err) {
  if (err instanceof EDSError) {
    if (err.statusCode === 401) {
      console.error('🔑 Invalid API key');
    } else if (err.statusCode === 507) {
      console.error('💾 Storage full');
    } else {
      console.error('❌ Upload error:', err.message);
    }
  } else {
    throw err;
  }
}

Next Steps