import * as FileSystem from 'expo-file-system';
import { decode as decodeBase64 } from 'base64-arraybuffer';
import { StorageAdapter } from '@powersync/attachments';
import { AppConfig } from '../supabase/AppConfig';
import { SupabaseClient } from '@supabase/supabase-js';
interface S3Upload {
message: string;
uploadUrl: string;
}
interface S3Download {
message: string;
downloadUrl: string;
}
interface S3Delete {
message: string;
}
export class AWSStorageAdapter implements StorageAdapter {
constructor( public client: SupabaseClient ) {}
async uploadFile(
filename: string,
data: ArrayBuffer,
options?: {
mediaType?: string;
}
): Promise<void> {
const response = await this.client.functions.invoke<S3Upload>('s3-upload', {
body: {
fileName: filename,
mediaType: options?.mediaType
}
});
if (response.error || !response.data) {
throw new Error(`Failed to reach upload edge function, code=${response.error}`);
}
const { uploadUrl } = response.data;
try {
const body = new Uint8Array(data);
const response = await fetch(uploadUrl, {
method: "PUT",
headers: {
"Content-Length": body.length.toString(),
"Content-Type": options?.mediaType,
},
body: body,
});
console.log(`File: ${filename} uploaded successfully.`);
} catch (error) {
console.error('Error uploading file:', error);
throw error;
}
}
async downloadFile(filePath: string): Promise<Blob> {
const response = await this.client.functions.invoke<S3Download>('s3-download', {
body: {
fileName: filePath
}
});
if (response.error || !response.data) {
throw new Error(`Failed to reach download edge function, code=${response.error}`);
}
const { downloadUrl } = response.data;
try {
const downloadResponse = await fetch(downloadUrl, {
method: "GET",
});
return await downloadResponse.blob();
} catch (error) {
console.error('Error downloading file:', error);
throw error;
}
}
async deleteFile(uri: string, options?: { filename?: string }): Promise<void> {
if (await this.fileExists(uri)) {
await FileSystem.deleteAsync(uri);
}
const { filename } = options ?? {};
if (!filename) {
return;
}
try {
const response = await this.client.functions.invoke<S3Delete>('s3-delete', {
body: {
fileName: options?.filename
}
});
if (response.error || !response.data) {
throw new Error(`Failed to reach delete edge function, code=${response.error}`);
}
const { message } = response.data;
console.log(message);
} catch (error) {
console.error(`Error deleting ${filename}:`, error);
}
}
async readFile(
fileURI: string,
options?: { encoding?: FileSystem.EncodingType; mediaType?: string }
): Promise<ArrayBuffer> {
const { encoding = FileSystem.EncodingType.UTF8 } = options ?? {};
const { exists } = await FileSystem.getInfoAsync(fileURI);
if (!exists) {
throw new Error(`File does not exist: ${fileURI}`);
}
const fileContent = await FileSystem.readAsStringAsync(fileURI, options);
if (encoding === FileSystem.EncodingType.Base64) {
return this.base64ToArrayBuffer(fileContent);
}
return this.stringToArrayBuffer(fileContent);
}
async writeFile(
fileURI: string,
base64Data: string,
options?: {
encoding?: FileSystem.EncodingType;
}
): Promise<void> {
const { encoding = FileSystem.EncodingType.UTF8 } = options ?? {};
await FileSystem.writeAsStringAsync(fileURI, base64Data, { encoding });
}
async fileExists(fileURI: string): Promise<boolean> {
const { exists } = await FileSystem.getInfoAsync(fileURI);
return exists;
}
async makeDir(uri: string): Promise<void> {
const { exists } = await FileSystem.getInfoAsync(uri);
if (!exists) {
await FileSystem.makeDirectoryAsync(uri, { intermediates: true });
}
}
async copyFile(sourceUri: string, targetUri: string): Promise<void> {
await FileSystem.copyAsync({ from: sourceUri, to: targetUri });
}
getUserStorageDirectory(): string {
return FileSystem.documentDirectory!;
}
async stringToArrayBuffer(str: string): Promise<ArrayBuffer> {
const encoder = new TextEncoder();
return encoder.encode(str).buffer;
}
/**
* Converts a base64 string to an ArrayBuffer
*/
async base64ToArrayBuffer(base64: string): Promise<ArrayBuffer> {
return decodeBase64(base64);
}
}