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-fetchpackage
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;
}
}