NativeScript / NativeScript/plugins

[background-http][Android] onProgress/onSuccess throw TypeError when the upload service outlives the JS runtime (Task.fromId returns undefined)

未關閉 適合新手
#671 0 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

主要語言
TypeScript
星號
206
分支
123
平均合併
2 天 1 小時
30 天內合併 PR
1

描述

Which package(s)

@nativescript/background-http@6.0.2 — Android only.

Environment

  • @nativescript/core 9.0.20, CLI 9.0.6, @nativescript/android 9.0.4
  • net.gotev:uploadservice 4.9.2 (the plugin's default)
  • Reported by production users on Android; not reproducible on iOS (see below)

Issue

Task.cache lives only in the JS runtime, but the gotev upload service is a
foreground service that outlives it. If the OS kills the app process while an
upload is in flight, the service keeps going (or is restarted) and eventually
broadcasts progress/completion. The GlobalRequestObserver registered by
init() then dispatches into a fresh runtime whose Task.cache is empty,
Task.fromId() returns undefined, and the handlers dereference it right away.

Two reports from the same upload, in the order they arrived:

Calling js method onProgress failed
TypeError: Cannot read properties of undefined (reading 'setTotalUpload')
  at onProgressReceiverProgress(vendor.mjs)
  at onProgress(vendor.mjs)
  at com.tns.gen.net.gotev.uploadservice.observer.request.RequestObserverDelegate.onProgress(RequestObserverDelegate.java:21)
  at net.gotev.uploadservice.observer.request.BaseRequestObserver.onReceive(BaseRequestObserver.kt:29)
Calling js method onSuccess failed
TypeError: Cannot read properties of undefined (reading 'setUpload')
  at onProgressReceiverCompleted(vendor.mjs)
  at onSuccess(vendor.mjs)
  at com.tns.gen.net.gotev.uploadservice.observer.request.RequestObserverDelegate.onSuccess(RequestObserverDelegate.java:32)
  at net.gotev.uploadservice.observer.request.BaseRequestObserver.onReceive(BaseRequestObserver.kt:31)

Note the upload itself succeeded — only the JS callback blew up.

Root cause

packages/background-http/index.android.ts on main:

function onProgressReceiverProgress(context: Context, uploadInfo: UploadInfo) {
      const uploadId = uploadInfo.getUploadId();
      const task = Task.fromId(uploadId);   // may be undefined
      const totalBytes = uploadInfo.getTotalBytes();
      const currentBytes = uploadInfo.getUploadedBytes();
      task.setTotalUpload(totalBytes);

Task.fromId (L228) is a plain Task.cache[id] lookup, and all four handlers
use the result unguarded: onProgressReceiverProgress (L42),
onProgressReceiverCompleted (L58), onProgressReceiverCancelled (L90) and
onProgressReceiverError (L97).

iOS is not affected: Task.getTask() (index.ios.ts L277-287) creates a Task
when the native task isn't in the map, instead of returning undefined.

Impact

Without discardUncaughtJsExceptions this is a hard crash — the exception
escapes a native callback. With the flag it is "only" a discarded exception, but
since progress fires repeatedly, a single orphaned upload produces a burst of
them (in our case, one error report per broadcast).

Steps to reproduce

  1. Start a multipartUpload large enough to take several seconds.
  2. Kill the app process while it runs (adb shell am force-stop <pkg>, or let
    the OS reclaim it) — the upload service survives.
  3. Reopen the app so a fresh runtime calls init() and registers the observer.
  4. The service reports the resumed/finished upload and the TypeError is thrown.

Suggested fix

Guard the lookup in the four handlers, e.g.:

 function onProgressReceiverProgress(context: Context, uploadInfo: UploadInfo) {
      const uploadId = uploadInfo.getUploadId();
      const task = Task.fromId(uploadId);
+     if (!task) {
+             // Upload started by a previous process: there is no JS task to notify.
+             return;
+     }

Related (bigger, happy to file separately)

Even with the guard, the outcome of an upload started before the restart is
simply lost — the app can never learn whether it succeeded. Two API additions
would make it recoverable: letting the caller supply the upload id (it is
currently generated internally as session.id + '{n}'), and a way to re-attach
listeners to an in-flight or finished upload by id. Today the only workaround is
reconciling against the server after the fact.

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

從 packages/background-http/index.android.ts 開始,檢查 Task.fromId 以及 onProgressReceiverProgress、onProgressReceiverCompleted、onProgressReceiverCancelled 和 onProgressReceiverError。如有需要,使用一個較長的 multipartUpload 和程序終止來重現。完成的標準是:對於 Task.cache 中缺失的上傳,其 broadcasts 能夠安全返回而不會拋出例外,同時正常的 Task 回呼繼續執行。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
android, typescript
領域
mobile-dev
Issue 類型
缺陷
難度
2/5
預估耗時
1-3 小時
活躍度
冷清
描述清晰度
描述清楚
新手友好度
82/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。