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 Icon from '#components/icon';
import { on } from '@ember/modifier'; import { on } from '@ember/modifier';
import { fn } from '@ember/helper'; import { fn } from '@ember/helper';
import { isMobile } from '../utils/device';
const MAX_IMAGE_DIMENSION = 1920; const MAX_IMAGE_DIMENSION = 1920;
const IMAGE_QUALITY = 0.94; const IMAGE_QUALITY = 0.94;
@@ -62,17 +63,29 @@ export default class PlacePhotoItem extends Component {
false false
); );
// 3. Upload main image (to all servers concurrently) // 3. Upload main image
const mainUploadPromise = this.blossom.upload(mainData.blob); // 4. Upload thumbnail
let mainResult, thumbResult;
const isMobileDevice = isMobile();
// 4. Upload thumbnail (to all servers concurrently) if (isMobileDevice) {
const thumbUploadPromise = this.blossom.upload(thumbData.blob); // 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 [mainResult, thumbResult] = await Promise.all([
const [mainResult, thumbResult] = await Promise.all([ mainUploadPromise,
mainUploadPromise, thumbUploadPromise,
thumbUploadPromise, ]);
]); }
this.isUploaded = true; this.isUploaded = true;

View File

@@ -75,7 +75,7 @@ export default class BlossomService extends Service {
return response.json(); return response.json();
} }
async upload(file) { async upload(file, options = { sequential: false }) {
if (!this.nostrAuth.isConnected) throw new Error('Not connected'); if (!this.nostrAuth.isConnected) throw new Error('Not connected');
const buffer = await file.arrayBuffer(); const buffer = await file.arrayBuffer();
@@ -97,28 +97,48 @@ export default class BlossomService extends Service {
const mainServer = servers[0]; const mainServer = servers[0];
const fallbackServers = servers.slice(1); 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 = []; const fallbackUrls = [];
let mainResult;
for (let i = 0; i < fallbackResults.length; i++) { if (options.sequential) {
const result = fallbackResults[i]; // Sequential upload logic
if (result.status === 'fulfilled') { mainResult = await this._uploadToServer(file, payloadHash, mainServer);
fallbackUrls.push(result.value.url);
} else { for (const serverUrl of fallbackServers) {
console.warn( try {
`Fallback upload to ${fallbackServers[i]} failed:`, const result = await this._uploadToServer(
result.reason 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'; const DEFAULT_CONNECT_RELAY = 'wss://relay.nsec.app';
import { isMobile } from '../utils/device';
export default class NostrAuthService extends Service { export default class NostrAuthService extends Service {
@service nostrRelay; @service nostrRelay;
@service nostrData; @service nostrData;
@@ -73,7 +75,7 @@ export default class NostrAuthService extends Service {
} }
get isMobile() { get isMobile() {
return /Mobi|Android|iPhone|iPad/i.test(navigator.userAgent); return isMobile();
} }
get isConnected() { 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);
}