angular / angularfire

Angular + Firebase = ❤️
https://firebaseopensource.com/projects/angular/angularfire2
MIT License
7.66k stars 2.19k forks source link

storage module often fails to save uploads #2263

Closed pdemilly closed 3 years ago

pdemilly commented 4 years ago

Version info

Angular: 8.2.2 Firebase: 6.6.2 AngularFire: 5.2.3

Other (e.g. Ionic/Cordova, Node, browser, operating system): google chrome 78.0.3904.108 but happens on many different chrome/os combo

How to reproduce these conditions

sometimes it works sometimes no. I also make sure to disable the experimental protocol QUIC as it seems to accentuate the problem.

Here is the code. I am using NGXS as state management but it is pretty straightforward

`export class UploadsState {

constructor(
    private storage: AngularFireStorage) {
}

@Action(UploadFile)
uploadFile(ctx: StateContext<StateModel>, action: UploadFile): Observable<any> {

    const fullPath = `${action.orgId}/${action.path}`;
    const storageRef = this.storage.ref(fullPath);

    const task = this.storage.upload(fullPath, action.file, { contentType: action.file.type });
    const percentages$ = task.percentageChanges();
    const snapshot$ = task.snapshotChanges();

    let totalSize = 0;
    const completed$ = new Subject();

    return combineLatest ([ percentages$, snapshot$ ]).pipe (
        takeUntil (completed$),
        tap (([ percent, snap ]) => {

            produce (ctx, draft => {
                draft.uploads[action.id] = { 
                    ...draft.uploads[action.id], 
                    progress: percent,
                    state: snap.state, 
                    bytesTransferred: snap.bytesTransferred, 
                    totalBytes: snap.totalBytes 
                };
            });

            ctx.dispatch (new UploadShowProgress (action.id, snap.state, percent, snap.bytesTransferred, snap.totalBytes));

            if (snap.bytesTransferred === snap.totalBytes) {
                totalSize = snap.totalBytes;
                completed$.next();
            }

        }),
        finalize (() => {
            console.log ('upload is completed. waiting 10 sec for things to settle');
            setTimeout (() => ctx.dispatch (new UploadGenerateDownloadURL (action.id, storageRef, fullPath, totalSize) ), 10000);
        })
    );
}

@Action(UploadGenerateDownloadURL)
generateDownloadUrl (ctx: StateContext<StateModel>, action: UploadGenerateDownloadURL): Observable<any> {

    return timer(5000).pipe (

        switchMap (() => {
            return action.storageRef.getDownloadURL().pipe (
                catchError (err => {
                    ctx.dispatch (new UploadFailed (action.id, action.path, err));
                    return of(null);
                }),
            );
        }),
        tap (url => {

            if (url) {
                console.log ('download url is: ', url);
                ctx.dispatch (new UploadCompleted (action.id, action.path, url, action.size) );
            } else {
                return ctx.dispatch (new UploadFailed (action.id, action.path, 'Upload failed! Try again'));
            }
        })
    );

}

@Action(UploadDataUrl)
async uploadDataUrl (ctx: StateContext<StateModel>, action: UploadDataUrl): Promise<string> {

    const fullPath = `${action.orgId}/${action.path}`;
    const storageRef = this.storage.ref(fullPath);

    try {

        return storageRef.putString(action.dataUrl, 'data_url').then(async (snapshot) => {
            const url = await storageRef.getDownloadURL().toPromise();
            return url;
        });
    } catch (err) {
        ctx.dispatch (new UploadFailed (action.id, fullPath, `${err.message || err.code || err}`));
    }
}

@Action(UploadCompleted)
uploadCompleted (ctx: StateContext<StateModel>, action: UploadCompleted): void {

    produce (ctx, draft => {
        draft.uploads[action.id] = {
            ...draft.uploads[action.id],
            state: 'completed',
            downloadURL: action.downloadURL
        };
    });

}

@Action(UploadFailed)
uploadFailed (ctx: StateContext<StateModel>, action: UploadFailed): Observable<any> {
    const filename = last(action.path.split('/'));
    return ctx.dispatch (new ToasterShowMessage (
        action.id, 
        `${filename || 'Upload failed' }`, 
        `${ action.error || 'Error: Try again later'}`, 
        ToasterType.error
    ));
}

}`

And here is the trace of when I get an error

main-es2015.369594528b5501ef1c9f.js:1 upload progress: running 0% 0/437248 main-es2015.369594528b5501ef1c9f.js:1 upload progress: running 59.95316159250586% 0/437248 main-es2015.369594528b5501ef1c9f.js:1 upload progress: running 59.95316159250586% 262144/437248 main-es2015.369594528b5501ef1c9f.js:1 upload progress: running 100% 262144/437248 main-es2015.369594528b5501ef1c9f.js:1 upload progress: running 100% 437248/437248 main-es2015.369594528b5501ef1c9f.js:1 upload is completed. waiting 10 sec for things to settle zone-evergreen.js:2952 GET https://firebasestorage.googleapis.com/v0/b/***********appspot.com/o/testco%2Fimport-files%2F2019-12-10%2FCando%20Query%20AV%2012-10.xls 404

jamesdaniels commented 3 years ago

doesn't seem to be a AngularFire issue? if still experiencing consider filing an issue with the Firebase JS SDK