@capgo/capacitor-uploader
Version:
Upload file natively
218 lines (211 loc) • 8.01 kB
JavaScript
;
var core = require('@capacitor/core');
var idb = require('idb');
const Uploader = core.registerPlugin('Uploader', {
web: () => Promise.resolve().then(function () { return web; }).then((m) => new m.UploaderWeb()),
});
class PathHelper {
// Check if the path follows the idb://[databaseName]/[storeName]/[key] format
static isIndexedDBPath(path) {
const regex = /^idb:\/\/([^/]+)\/([^/]+)\/(.+)$/;
return regex.test(path);
}
// Parse the path to extract database, storeName, and key
static parseIndexedDBPath(path) {
const regex = /^idb:\/\/([^/]+)\/([^/]+)\/(.+)$/;
const match = path.match(regex);
if (!match) {
throw new Error('Invalid IndexedDB path format');
}
return {
database: match[1],
storeName: match[2],
key: match[3],
};
}
}
class UploaderWeb extends core.WebPlugin {
constructor() {
super(...arguments);
this.uploads = new Map();
}
async startUpload(options) {
console.log('startUpload', options);
const id = Math.random().toString(36).substring(2, 15);
const controller = new AbortController();
const maxRetries = options.maxRetries || 3;
this.uploads.set(id, { controller, retries: maxRetries });
this.doUpload(id, options);
return { id };
}
async uploadMultipart(options) {
var _a, _b;
return this.startUpload({
filePath: options.filePath,
serverUrl: options.url,
headers: (_a = options.headers) !== null && _a !== void 0 ? _a : {},
method: 'POST',
uploadType: 'multipart',
fileField: options.fieldName,
parameters: (_b = options.fields) !== null && _b !== void 0 ? _b : {},
});
}
async removeUpload(options) {
console.log('removeUpload', options);
const upload = this.uploads.get(options.id);
if (upload) {
upload.controller.abort();
this.uploads.delete(options.id);
this.notifyListeners('events', {
name: 'cancelled',
id: options.id,
payload: {},
});
}
}
async doUpload(id, options) {
var _a, _b, _c;
const { serverUrl, headers = {}, method = 'POST', parameters = {} } = options;
const upload = this.uploads.get(id);
if (!upload)
return;
try {
const files = this.normalizeFiles(options);
if (files.length === 0)
throw new Error('Missing required parameter: filePath or files');
const resolvedMethod = method.toUpperCase();
const uploadType = (_a = options.uploadType) !== null && _a !== void 0 ? _a : (resolvedMethod === 'PUT' ? 'binary' : 'multipart');
let body;
if (resolvedMethod === 'PUT' || uploadType === 'binary') {
if (files.length !== 1)
throw new Error('Binary uploads only support a single file');
const file = await this.getFileFromPath(files[0].filePath);
if (!file)
throw new Error('File not found');
body = file;
}
else {
const formData = new FormData();
for (const fileOption of files) {
const file = await this.getFileFromPath(fileOption.filePath);
if (!file)
throw new Error('File not found');
const fieldName = (_c = (_b = fileOption.fieldName) !== null && _b !== void 0 ? _b : options.fileField) !== null && _c !== void 0 ? _c : 'file';
formData.append(fieldName, file);
}
for (const [key, value] of Object.entries(parameters)) {
formData.append(key, value);
}
body = formData;
}
const response = await fetch(serverUrl, {
method: resolvedMethod,
headers,
body,
signal: upload.controller.signal,
});
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
this.notifyListeners('events', {
name: 'completed',
id,
payload: { statusCode: response.status },
});
this.uploads.delete(id);
}
catch (error) {
if (error.name === 'AbortError')
return;
if (upload.retries > 0) {
upload.retries--;
console.log(`Retrying upload (retries left: ${upload.retries})`);
setTimeout(() => this.doUpload(id, options), 1000);
}
else {
this.notifyListeners('events', {
name: 'failed',
id,
payload: { error: error.message },
});
this.uploads.delete(id);
}
}
}
normalizeFiles(options) {
if (options.files && options.files.length > 0)
return options.files;
if (options.filePath) {
return [
{
filePath: options.filePath,
fieldName: options.fileField,
mimeType: options.mimeType,
},
];
}
return [];
}
async getFileFromPath(filePath) {
// Check if the path is an IndexedDB path
if (PathHelper.isIndexedDBPath(filePath)) {
return this.getFileFromIndexedDB(filePath);
}
// Otherwise, treat it as a file path from the system
return this.getFileFromSystem(filePath);
}
// Retrieve the file from IndexedDB
async getFileFromIndexedDB(filePath) {
// Parse the path to get the database, store name, and key
const { database, storeName, key } = PathHelper.parseIndexedDBPath(filePath);
try {
// Open the IndexedDB database and access the object store
const db = await idb.openDB(database, 1, {
upgrade(db) {
if (!db.objectStoreNames.contains(storeName)) {
db.createObjectStore(storeName);
}
},
});
// Get the blob from the store
const blob = await db.get(storeName, key);
if (!blob) {
console.error(`File with key "${key}" not found in store "${storeName}" in database "${database}"`);
return null;
}
// Convert the Blob to a File object
return new File([blob], key, { type: blob.type });
}
catch (error) {
console.error('Error retrieving file from IndexedDB:', error);
return null;
}
}
// Retrieve the file from the system (local file)
async getFileFromSystem(filePath) {
try {
// This is a simplified version. In a real-world scenario,
// you might need to handle different types of paths or use a file system API.
const response = await fetch(filePath);
const blob = await response.blob();
return new File([blob], filePath.split('/').pop() || 'file', {
type: blob.type,
});
}
catch (error) {
console.error('Error getting file from system:', error);
return null;
}
}
async acknowledgeEvent(_options) {
// Web uploads run in-process; events are not persisted for replay.
}
async getPluginVersion() {
return { version: 'web' };
}
}
var web = /*#__PURE__*/Object.freeze({
__proto__: null,
UploaderWeb: UploaderWeb
});
exports.Uploader = Uploader;
//# sourceMappingURL=plugin.cjs.js.map