Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -361,15 +361,15 @@ public void downloadUpdate(final ReadableMap updatePackage, final boolean notify
public void run() {
try {
JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
final DownloadProgressEventDispatcher progressEventDispatcher = new DownloadProgressEventDispatcher(
new DownloadProgressEventDispatcher.EventEmitter() {
@Override
public void emit(DownloadProgress downloadProgress) {
emitDownloadProgressEvent(downloadProgress);
}
});
JSONObject updateArchiveResult = mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
private volatile boolean hasScheduledNextFrame = false;
private final DownloadProgressEventDispatcher progressEventDispatcher = new DownloadProgressEventDispatcher(
new DownloadProgressEventDispatcher.EventEmitter() {
@Override
public void emit(DownloadProgress downloadProgress) {
emitDownloadProgressEvent(downloadProgress);
}
});

@Override
public void onDownloadStart() {
Expand All @@ -378,11 +378,14 @@ public void onDownloadStart() {

@Override
public void call(DownloadProgress downloadProgress) {
if (!notifyProgress || downloadProgress == null) {
if (downloadProgress == null) {
return;
}

progressEventDispatcher.record(downloadProgress);
if (!notifyProgress) {
return;
}

// If the download is completed, synchronously send the last event.
if (downloadProgress.isCompleted()) {
progressEventDispatcher.dispatchLatest();
Expand Down Expand Up @@ -414,6 +417,7 @@ public void doFrame(long frameTimeNanos) {

JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
WritableMap downloadedPackage = CodePushUtils.convertJsonObjectToWritable(newPackage);
downloadedPackage.putDouble("downloadBytes", progressEventDispatcher.getDownloadBytes());
if (updateArchiveResult != null) {
// The result describes this download rather than the update, so it is
// put on what this call resolves with and nowhere else: the metadata
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ synchronized void record(DownloadProgress downloadProgress) {
mLatestDownloadProgress = downloadProgress;
}

synchronized long getDownloadBytes() {
return mLatestDownloadProgress == null ? 0 : mLatestDownloadProgress.getReceivedBytes();
}

synchronized void dispatchLatest() {
if (mLatestDownloadProgress == null || mLatestDownloadProgress.getReceivedBytes() <= mLastDispatchedBytes) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,65 @@ public void call(DownloadProgress downloadProgress) {
Arrays.asList((long) ERROR_PAGE.length, (long) fullArchive.length), completedBytes);
}

@Test
public void reportsTheFullFileSizeWithoutProgressEvents() throws IOException {
byte[] fullArchive = zipOf(fullArchiveContents());
String fullUrl = serve("/full.zip", fullArchive);
assertDownloadBytes(fullUpdatePackage(mPackageHash, fullUrl), fullArchive.length);
}

@Test
public void reportsThePatchFileSizeWithoutProgressEvents() throws IOException {
byte[] patchArchive = zipOf(patchArchiveContents());
String patchUrl = serve("/patch.zip", patchArchive);
String fullUrl = serve("/full.zip", zipOf(fullArchiveContents()));
assertDownloadBytes(updatePackage(fullUrl, patchUrl), patchArchive.length);
assertEquals(Arrays.asList("/patch.zip"), mServer.requestedPaths());
}

@Test
public void reportsOnlyTheFullFileSizeAfterFallbackWithoutProgressEvents() throws IOException {
byte[] fullArchive = zipOf(fullArchiveContents());
String patchUrl = serve("/patch.zip", ERROR_PAGE);
String fullUrl = serve("/full.zip", fullArchive);
assertDownloadBytes(updatePackage(fullUrl, patchUrl), fullArchive.length);
assertEquals(Arrays.asList("/patch.zip", "/full.zip"), mServer.requestedPaths());
}

@Test
public void reportsTheFileSizeWithoutContentLength() throws IOException {
byte[] fullArchive = zipOf(fullArchiveContents());
String fullUrl = mServer.serveWithoutContentLength("/full.zip", fullArchive);
assertDownloadBytes(fullUpdatePackage(mPackageHash, fullUrl), fullArchive.length);
}

/**
* The callback below mirrors the one the native module passes, without its JS event
* scheduling: the module needs a React context and native maps this suite cannot create.
* So this pins what the update manager reports, not that the module records the progress
* before checking whether JS asked for events.
*/
private void assertDownloadBytes(JSONObject updatePackage, long expectedBytes) throws IOException {
DownloadProgressEventDispatcher dispatcher = new DownloadProgressEventDispatcher(progress -> {
fail("measuring download size must not require progress events");
});
updateManager(applierWriting(TARGET_BUNDLE)).downloadPackage(
updatePackage, BUNDLE_FILE_NAME, new DownloadProgressCallback() {
@Override
public void onDownloadStart() {
dispatcher.reset();
}

@Override
public void call(DownloadProgress progress) {
dispatcher.record(progress);
}
});

assertEquals(expectedBytes, dispatcher.getDownloadBytes());
assertInstalledContents();
}

/** The result of a download the update had to be downloaded in full for. */
private static void assertFallbackResult(JSONObject patchResult, String expectedReason) {
assertEquals("fallback", patchResult.optString("status", null));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,15 @@ public void progressRecordedBeforeAResetIsNotEmittedAfterIt() {

assertEquals(Collections.emptyList(), mEmittedBytes);
}

@Test
public void reportsTheLastFileSizeWithoutEmittingProgress() {
mDispatcher.record(new DownloadProgress(1000, 1000));
mDispatcher.reset();
mDispatcher.record(new DownloadProgress(-1, 250));
mDispatcher.record(new DownloadProgress(-1, 500));

assertEquals(500L, mDispatcher.getDownloadBytes());
assertEquals(Collections.emptyList(), mEmittedBytes);
}
}
5 changes: 3 additions & 2 deletions docs/api-js.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ The `codePush` decorator accepts an "options" object that allows you to customiz

* __onDownloadStart__ *((label: String) => void | Promise&lt;void&gt;)* - Called when the download of an available update begins, with the label of the release being downloaded.

* __onDownloadSuccess__ *((label: String) => void | Promise&lt;void&gt;)* - Called when the download of an update has completed successfully, with the label of the release that was downloaded. The install happens afterwards, so this says nothing about whether the update could be installed.
* __onDownloadSuccess__ *((label: String, details: DownloadSuccessDetails) => void | Promise&lt;void&gt;)* - Called when the download of an update has completed successfully, with the target release label and `{ baseVersion, downloadBytes }`. `baseVersion` is the previously running OTA label (the native app version for the embedded bundle). `downloadBytes` is the final downloaded file size in bytes, excluding earlier fallback attempts (`null` on older native code). No progress listener is required. The install happens afterwards, so this says nothing about whether the update could be installed.

* __onRolloutSkipped__ *((label: String) => void | Promise&lt;void&gt;)* - Called when the device falls outside the latest release's active rollout, with the label of that release. The update check then leaves that release out of the candidates: it either resolves against the release below it, or - when the release left as the latest one is the binary version - clears the downloaded updates and restarts the app onto the binary. *NOTE: the typings declare a second `error` parameter, but the runtime only ever passes the label.*

Expand Down Expand Up @@ -557,6 +557,7 @@ Contains details about an update that has been downloaded locally or already ins
- __appVersion__: The app binary version that this update is dependent on. This is the value that was specified via the `appStoreVersion` parameter when calling the CLI's `release` command. *(String)*
- __deploymentKey__: The deployment key that was used to originally download this update. *(String)*
- __description__: The description of the update. This is the same value that you specified in the CLI when you released the update. *(String)*
- __downloadBytes__: The size in bytes of the final file the download used: the patch file when a patch was applied, or the full file after a fallback, excluding earlier attempts. It is only set on the package `RemotePackage.download` resolves with, and is not saved with the update, so packages returned by `getCurrentPackage` and `getUpdateMetadata` do not have it. Undefined on native code that predates download size reporting. *(Number, optional)*
- __failedInstall__: Indicates whether this update has been previously installed but was rolled back. The `sync` method will automatically ignore updates which have previously failed, so you only need to worry about this property if using `checkForUpdate`. *(Boolean)*
- __isFirstRun__: Indicates whether this is the first time the update has been run after being installed. This is useful for determining whether you would like to show a "What's New?" UI to the end user after installing an update. *(Boolean)*
- __isMandatory__: Indicates whether the update is considered mandatory. This is the value that was specified in the CLI when the update was released. *(Boolean)*
Expand Down Expand Up @@ -589,7 +590,7 @@ A diff that fails falls back to the patch archive, and a patch archive that fail

###### Methods

- __download(downloadProgressCallback?: Function, updateArchiveResultCallback?: Function): Promise&lt;LocalPackage&gt;__: Downloads the available update from the CodePush service. If a `downloadProgressCallback` is specified, it will be called periodically with a `DownloadProgress` object (`{ totalBytes: Number, receivedBytes: Number }`) that reports the progress of the download until it completes. If an `updateArchiveResultCallback` is specified, it will be called once with an `UpdateArchiveResult` object when the update was published with a binary patch, reporting which archive the update was downloaded from and why each archive that was given up on was. It only runs after the download has succeeded, and what it says nothing about is whether installing the resolved `LocalPackage` succeeds: an archive that could not be applied is reported there and the update is downloaded in full. Returns a Promise that resolves with the `LocalPackage`.
- __download(downloadProgressCallback?: Function, updateArchiveResultCallback?: Function): Promise&lt;LocalPackage&gt;__: Downloads the available update from the CodePush service. If a `downloadProgressCallback` is specified, it will be called periodically with a `DownloadProgress` object (`{ totalBytes: Number, receivedBytes: Number }`) that reports the progress of the download until it completes. If an `updateArchiveResultCallback` is specified, it will be called once with an `UpdateArchiveResult` object when the update was published with a binary patch, reporting which archive the update was downloaded from and why each archive that was given up on was. It only runs after the download has succeeded, and what it says nothing about is whether installing the resolved `LocalPackage` succeeds: an archive that could not be applied is reported there and the update is downloaded in full. Returns a Promise that resolves with the `LocalPackage`, whose `downloadBytes` reports the size of the downloaded file whether or not a `downloadProgressCallback` is specified.

#### Enums

Expand Down
19 changes: 18 additions & 1 deletion docs/telemetry-callbacks.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
| 콜백 | 호출 시점 | 전달값 | 주의할 점 |
| --- | --- | --- | --- |
| `onDownloadStart` | 사용 가능한 업데이트 다운로드를 시작할 때 | `(label)` | 다운로드 자체의 시작을 의미합니다. |
| `onDownloadSuccess` | 사용 가능한 업데이트 다운로드를 마쳤을 때 | `(label)` | 아직 설치 또는 정상 실행이 보장된 것은 아닙니다. |
| `onDownloadSuccess` | 사용 가능한 업데이트 다운로드를 마쳤을 때 | `(label, details)` | 아직 설치 또는 정상 실행이 보장된 것은 아닙니다. |
| `onUpdateArchiveResult` | binary patch / asset diff 업데이트 다운로드가 끝난 뒤, 설치 전에 | `(label, result)` | 어떤 아카이브를 사용했고 patch fallback이 있었는지 확인합니다. |
| `onUpdateSuccess` | 설치한 업데이트가 실행되고 `notifyAppReady`로 성공을 확정했을 때 | `(label)` | 실제 업데이트 성공을 기록할 시점입니다. |
| `onUpdateRollback` | 설치한 업데이트 실행에 실패해 이전 버전으로 롤백할 때 | `(label)` | 다운로드 성공이나 설치 성공과는 별개입니다. |
Expand All @@ -28,6 +28,23 @@

> **참고:** `onRolloutSkipped`의 타입 정의에는 두 번째 `error` 매개변수가 존재하지만, 실제로는 아무것도 전달하지 않아 항상 `undefined` 값을 갖습니다.

## 버전별 다운로드 파일 크기 기록하기

`onDownloadSuccess`의 첫 번째 인자는 목표 OTA 버전입니다. 두 번째 인자에는 다운로드 전 실행 중이던 OTA 버전인 `baseVersion`과, 최종적으로 사용한 파일 하나의 크기인 `downloadBytes`가 전달됩니다.

```ts
onDownloadSuccess: (targetVersion, { baseVersion, downloadBytes }) => {
console.log("codepush_download_success", {
baseVersion,
targetVersion,
downloadBytes,
});
},
```

- `downloadBytes`는 바이트 단위입니다. patch 성공이면 patch 파일, full fallback이면 최종 full 파일의 크기이며 앞서 실패한 파일은 합산하지 않습니다. 번들만의 크기가 아니라 다운로드한 아카이브 전체의 크기입니다.
- 내장 번들을 실행 중이었다면 `baseVersion`은 네이티브 앱 버전(`appVersion`)입니다.

## 오류를 기록하는 방법

오류는 `error.message`가 아니라 `error.code`로 묶어 기록하세요.
Expand Down
20 changes: 19 additions & 1 deletion docs/telemetry-callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ See `CodePushOptions` for the other available options.
| Callback | Called when | Receives | Notes |
| --- | --- | --- | --- |
| `onDownloadStart` | The download of an available update begins | `(label)` | Indicates only that the download has started. |
| `onDownloadSuccess` | The download of an available update finishes | `(label)` | Installation and successful execution are not guaranteed yet. |
| `onDownloadSuccess` | The download of an available update finishes | `(label, details)` | Installation and successful execution are not guaranteed yet. |
| `onUpdateArchiveResult` | A binary patch or asset diff update has finished downloading, before installation | `(label, result)` | Shows which archive was used and whether patch fallback occurred. |
| `onUpdateSuccess` | An installed update runs and is confirmed successful through `notifyAppReady` | `(label)` | This is the point to record an actual update success. |
| `onUpdateRollback` | An installed update fails to run and rolls back to the previous version | `(label)` | This is separate from download or installation success. |
Expand All @@ -28,6 +28,24 @@ See `CodePushOptions` for the other available options.

> **Note:** The type definition for `onRolloutSkipped` includes a second `error` parameter, but the runtime passes no value for it, so it is always `undefined`.

## Recording download file size by version

`onDownloadSuccess` receives the target OTA label as its first argument and `{ baseVersion, downloadBytes }` as its second:

```ts
onDownloadSuccess: (targetVersion, { baseVersion, downloadBytes }) => {
console.log("codepush_download_success", {
baseVersion,
targetVersion,
downloadBytes,
});
},
```

`downloadBytes` is the size in bytes of the final downloaded file used for the update, including any assets in the archive. A successful patch reports its patch file size; a full fallback reports only the full file size, excluding earlier attempts.

`baseVersion` is the OTA label running before the download, or the native app version (`appVersion`) for the embedded bundle.

## How to record errors

Group errors by `error.code`, not `error.message`.
Expand Down
8 changes: 5 additions & 3 deletions ios/CodePush/CodePush.mm
Original file line number Diff line number Diff line change
Expand Up @@ -788,12 +788,14 @@ -(void)loadBundleOnTick:(NSTimer *)timer {
_lastProgressEventTime = 0;
}

__block long long downloadBytes = 0;
[CodePushPackage
downloadPackage:mutableUpdatePackage
expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension]
operationQueue:_methodQueue
// The download is progressing forward
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
downloadBytes = receivedContentLength;
// Received bytes only run backwards when a new download starts a stream of its
// own - the next archive after a binary patch fallback. The pause left over
// from the finished download would swallow that whole stream, so frame
Expand Down Expand Up @@ -830,17 +832,17 @@ -(void)loadBundleOnTick:(NSTimer *)timer {
return reject([NSString stringWithFormat: @"%ld", (long)err.code], err.localizedDescription, err);
}

NSMutableDictionary *downloadedPackage = [newPackage mutableCopy];
downloadedPackage[@"downloadBytes"] = @(downloadBytes);
if (updateArchiveResult) {
// The result describes this download rather than the update, so it is put on
// what this call resolves with and nowhere else: the metadata that was
// written for the update knows nothing about it, and whoever asked for the
// download decides what the result is worth.
NSMutableDictionary *downloadedPackage = [newPackage mutableCopy];
downloadedPackage[UpdateArchiveResultKey] = updateArchiveResult;
return resolve(downloadedPackage);
}

resolve(newPackage);
resolve(downloadedPackage);
}
// The download failed
failCallback:^(NSError *err) {
Expand Down
Loading
Loading