Do sequential image processing/uploads on mobile

Uploading multiple large files at once can fail easily
This commit is contained in:
2026-04-20 19:37:24 +04:00
parent ec31d1a59b
commit 5cd384cf3a
4 changed files with 70 additions and 31 deletions

View File

@@ -6,6 +6,7 @@ import { task } from 'ember-concurrency';
import Icon from '#components/icon';
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
import { isMobile } from '../utils/device';
const MAX_IMAGE_DIMENSION = 1920;
const IMAGE_QUALITY = 0.94;
@@ -62,17 +63,29 @@ export default class PlacePhotoItem extends Component {
false
);
// 3. Upload main image (to all servers concurrently)
const mainUploadPromise = this.blossom.upload(mainData.blob);
// 3. Upload main image
// 4. Upload thumbnail
let mainResult, thumbResult;
const isMobileDevice = isMobile();
// 4. Upload thumbnail (to all servers concurrently)
const thumbUploadPromise = this.blossom.upload(thumbData.blob);
if (isMobileDevice) {
// Mobile: sequential uploads to preserve bandwidth and memory
mainResult = await this.blossom.upload(mainData.blob, {
sequential: true,
});
thumbResult = await this.blossom.upload(thumbData.blob, {
sequential: true,
});
} else {
// Desktop: concurrent uploads
const mainUploadPromise = this.blossom.upload(mainData.blob);
const thumbUploadPromise = this.blossom.upload(thumbData.blob);
// Await both uploads
const [mainResult, thumbResult] = await Promise.all([
mainUploadPromise,
thumbUploadPromise,
]);
[mainResult, thumbResult] = await Promise.all([
mainUploadPromise,
thumbUploadPromise,
]);
}
this.isUploaded = true;

View File

@@ -75,7 +75,7 @@ export default class BlossomService extends Service {
return response.json();
}
async upload(file) {
async upload(file, options = { sequential: false }) {
if (!this.nostrAuth.isConnected) throw new Error('Not connected');
const buffer = await file.arrayBuffer();
@@ -97,28 +97,48 @@ export default class BlossomService extends Service {
const mainServer = servers[0];
const fallbackServers = servers.slice(1);
// Start all uploads concurrently
const mainPromise = this._uploadToServer(file, payloadHash, mainServer);
const fallbackPromises = fallbackServers.map((serverUrl) =>
this._uploadToServer(file, payloadHash, serverUrl)
);
// Main server MUST succeed
const mainResult = await mainPromise;
// Fallback servers can fail, but we log the warnings
const fallbackResults = await Promise.allSettled(fallbackPromises);
const fallbackUrls = [];
let mainResult;
for (let i = 0; i < fallbackResults.length; i++) {
const result = fallbackResults[i];
if (result.status === 'fulfilled') {
fallbackUrls.push(result.value.url);
} else {
console.warn(
`Fallback upload to ${fallbackServers[i]} failed:`,
result.reason
);
if (options.sequential) {
// Sequential upload logic
mainResult = await this._uploadToServer(file, payloadHash, mainServer);
for (const serverUrl of fallbackServers) {
try {
const result = await this._uploadToServer(
file,
payloadHash,
serverUrl
);
fallbackUrls.push(result.url);
} catch (error) {
console.warn(`Fallback upload to ${serverUrl} failed:`, error);
}
}
} else {
// Concurrent upload logic
const mainPromise = this._uploadToServer(file, payloadHash, mainServer);
const fallbackPromises = fallbackServers.map((serverUrl) =>
this._uploadToServer(file, payloadHash, serverUrl)
);
// Main server MUST succeed
mainResult = await mainPromise;
// Fallback servers can fail, but we log the warnings
const fallbackResults = await Promise.allSettled(fallbackPromises);
for (let i = 0; i < fallbackResults.length; i++) {
const result = fallbackResults[i];
if (result.status === 'fulfilled') {
fallbackUrls.push(result.value.url);
} else {
console.warn(
`Fallback upload to ${fallbackServers[i]} failed:`,
result.reason
);
}
}
}

View File

@@ -14,6 +14,8 @@ const STORAGE_KEY_CONNECT_RELAY = 'marco:nostr_connect_relay';
const DEFAULT_CONNECT_RELAY = 'wss://relay.nsec.app';
import { isMobile } from '../utils/device';
export default class NostrAuthService extends Service {
@service nostrRelay;
@service nostrData;
@@ -73,7 +75,7 @@ export default class NostrAuthService extends Service {
}
get isMobile() {
return /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);
return isMobile();
}
get isConnected() {

4
app/utils/device.js Normal file
View File

@@ -0,0 +1,4 @@
export function isMobile() {
if (typeof navigator === 'undefined') return false;
return /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent);
}